httpi 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +10 -10
- data/README.md +40 -9
- data/lib/httpi.rb +9 -0
- data/lib/httpi/adapter/curb.rb +15 -6
- data/lib/httpi/adapter/httpclient.rb +20 -11
- data/lib/httpi/adapter/net_http.rb +14 -10
- data/lib/httpi/auth/config.rb +68 -0
- data/lib/httpi/auth/ssl.rb +76 -0
- data/lib/httpi/request.rb +32 -48
- data/lib/httpi/version.rb +1 -1
- data/spec/fixtures/client_cert.pem +16 -0
- data/spec/fixtures/client_key.pem +15 -0
- data/spec/httpi/adapter/curb_spec.rb +136 -10
- data/spec/httpi/adapter/httpclient_spec.rb +99 -33
- data/spec/httpi/adapter/net_http_spec.rb +99 -28
- data/spec/httpi/auth/config_spec.rb +117 -0
- data/spec/httpi/auth/ssl_spec.rb +112 -0
- data/spec/httpi/httpi_spec.rb +45 -37
- data/spec/httpi/request_spec.rb +38 -73
- data/spec/integration/request_spec.rb +3 -3
- metadata +14 -10
data/Gemfile.lock
CHANGED
@@ -14,16 +14,16 @@ GEM
|
|
14
14
|
mocha (0.9.8)
|
15
15
|
rake
|
16
16
|
rake (0.8.7)
|
17
|
-
rspec (2.0.0
|
18
|
-
rspec-core (= 2.0.0
|
19
|
-
rspec-expectations (= 2.0.0
|
20
|
-
rspec-mocks (= 2.0.0
|
21
|
-
rspec-core (2.0.0
|
22
|
-
rspec-expectations (2.0.0
|
17
|
+
rspec (2.0.0)
|
18
|
+
rspec-core (= 2.0.0)
|
19
|
+
rspec-expectations (= 2.0.0)
|
20
|
+
rspec-mocks (= 2.0.0)
|
21
|
+
rspec-core (2.0.0)
|
22
|
+
rspec-expectations (2.0.0)
|
23
23
|
diff-lcs (>= 1.1.2)
|
24
|
-
rspec-mocks (2.0.0
|
25
|
-
rspec-core (= 2.0.0
|
26
|
-
rspec-expectations (= 2.0.0
|
24
|
+
rspec-mocks (2.0.0)
|
25
|
+
rspec-core (= 2.0.0)
|
26
|
+
rspec-expectations (= 2.0.0)
|
27
27
|
webmock (1.3.5)
|
28
28
|
addressable (>= 2.1.1)
|
29
29
|
crack (>= 0.1.7)
|
@@ -36,5 +36,5 @@ DEPENDENCIES
|
|
36
36
|
httpclient (~> 2.1.5)
|
37
37
|
httpi!
|
38
38
|
mocha (~> 0.9.8)
|
39
|
-
rspec (
|
39
|
+
rspec (~> 2.0.0)
|
40
40
|
webmock (~> 1.3.5)
|
data/README.md
CHANGED
@@ -17,7 +17,7 @@ Some examples
|
|
17
17
|
|
18
18
|
Executing a POST request with the most basic request object:
|
19
19
|
|
20
|
-
request = HTTPI::Request.new
|
20
|
+
request = HTTPI::Request.new "http://example.com"
|
21
21
|
HTTPI.get request
|
22
22
|
|
23
23
|
Here's a POST request with a request object:
|
@@ -32,11 +32,11 @@ And a GET request using HTTP basic auth and the Curb adapter:
|
|
32
32
|
|
33
33
|
request = HTTPI::Request.new
|
34
34
|
request.url = "http://auth.example.com"
|
35
|
-
request.
|
35
|
+
request.auth.basic "username", "password"
|
36
36
|
|
37
37
|
HTTPI.get request, :curb
|
38
38
|
|
39
|
-
HTTPI also comes
|
39
|
+
HTTPI also comes shortcuts. This executes a PUT request:
|
40
40
|
|
41
41
|
HTTPI.put "http://example.com", "<some>xml</some>"
|
42
42
|
|
@@ -92,23 +92,54 @@ HTTPI::Request
|
|
92
92
|
--------------
|
93
93
|
|
94
94
|
The `HTTPI::Request` serves as a common denominator of options that HTTPI adapters need to support.
|
95
|
-
It represents an HTTP request and lets you customize various settings through
|
95
|
+
It represents an HTTP request and lets you customize various settings through the following methods:
|
96
96
|
|
97
97
|
#url # the URL to access
|
98
98
|
#proxy # the proxy server to use
|
99
|
+
#ssl # whether to use SSL
|
99
100
|
#headers # a Hash of HTTP headers
|
100
101
|
#body # the HTTP request body
|
101
102
|
#open_timeout # the open timeout (sec)
|
102
103
|
#read_timeout # the read timeout (sec)
|
103
104
|
|
104
|
-
|
105
|
+
### Usage example
|
105
106
|
|
106
|
-
|
107
|
-
|
107
|
+
request = HTTPI::Request.new
|
108
|
+
request.url = "http://example.com"
|
109
|
+
request.read_timeout = 30
|
110
|
+
|
111
|
+
HTTPI::Auth
|
112
|
+
-----------
|
113
|
+
|
114
|
+
`HTTPI::Auth` supports HTTP basic and digest authentication.
|
115
|
+
|
116
|
+
#basic(username, password) # HTTP basic auth credentials
|
117
|
+
#digest(username, password) # HTTP digest auth credentials
|
118
|
+
|
119
|
+
### Usage example
|
120
|
+
|
121
|
+
request = HTTPI::Request.new
|
122
|
+
request.auth.basic "username", "password"
|
123
|
+
|
124
|
+
HTTPI::Auth::SSL
|
125
|
+
----------------
|
126
|
+
|
127
|
+
`HTTPI::Auth::SSL` manages SSL client authentication.
|
128
|
+
|
129
|
+
#cert_key_file # the private key file to use
|
130
|
+
#cert_file # the certificate file to use
|
131
|
+
#ca_cert_file # the ca certificate file to use
|
132
|
+
#verify_mode # one of [:none, :peer, :fail_if_no_peer_cert, :client_once]
|
133
|
+
|
134
|
+
### Usage example
|
135
|
+
|
136
|
+
request = HTTPI::Request.new
|
137
|
+
request.auth.ssl.cert_key_file = "client_key.pem"
|
138
|
+
request.auth.ssl.cert_file = "client_cert.pem"
|
139
|
+
request.auth.ssl.verify_mode = :none
|
108
140
|
|
109
141
|
### TODO
|
110
142
|
|
111
|
-
* Add support for SSL client authentication
|
112
143
|
* Add support for NTLM authentication
|
113
144
|
|
114
145
|
HTTPI::Adapter
|
@@ -145,4 +176,4 @@ It contains the response code, headers and body.
|
|
145
176
|
Participate
|
146
177
|
-----------
|
147
178
|
|
148
|
-
|
179
|
+
Any help and feedback appreciated. So please get in touch!
|
data/lib/httpi.rb
CHANGED
@@ -69,6 +69,9 @@ require "httpi/adapter"
|
|
69
69
|
# http.follow_redirect_count = 3 # HTTPClient example
|
70
70
|
# end
|
71
71
|
module HTTPI
|
72
|
+
|
73
|
+
REQUEST_METHODS = [:get, :post, :head, :put, :delete]
|
74
|
+
|
72
75
|
class << self
|
73
76
|
|
74
77
|
# Executes an HTTP GET request.
|
@@ -121,6 +124,12 @@ module HTTPI
|
|
121
124
|
end
|
122
125
|
end
|
123
126
|
|
127
|
+
# Executes an HTTP request for the given +method+.
|
128
|
+
def request(method, request, adapter = nil)
|
129
|
+
raise ArgumentError, "Invalid request method: #{method}" unless REQUEST_METHODS.include? method
|
130
|
+
send method, request, adapter
|
131
|
+
end
|
132
|
+
|
124
133
|
private
|
125
134
|
|
126
135
|
# Checks whether +args+ contains of an <tt>HTTPI::Request</tt> or a URL
|
data/lib/httpi/adapter/curb.rb
CHANGED
@@ -59,20 +59,29 @@ module HTTPI
|
|
59
59
|
|
60
60
|
def setup_client(request)
|
61
61
|
basic_setup request
|
62
|
-
|
62
|
+
setup_http_auth request if request.auth.http?
|
63
|
+
setup_ssl_auth request.auth.ssl if request.auth.ssl?
|
63
64
|
end
|
64
65
|
|
65
66
|
def basic_setup(request)
|
66
67
|
client.url = request.url.to_s
|
67
|
-
client.
|
68
|
-
client.
|
68
|
+
client.proxy_url = request.proxy.to_s if request.proxy
|
69
|
+
client.timeout = request.read_timeout if request.read_timeout
|
70
|
+
client.connect_timeout = request.open_timeout if request.open_timeout
|
69
71
|
client.headers = request.headers
|
70
72
|
client.verbose = false
|
71
73
|
end
|
72
74
|
|
73
|
-
def
|
74
|
-
client.http_auth_types = request.
|
75
|
-
client.username, client.password = *request.credentials
|
75
|
+
def setup_http_auth(request)
|
76
|
+
client.http_auth_types = request.auth.type
|
77
|
+
client.username, client.password = *request.auth.credentials
|
78
|
+
end
|
79
|
+
|
80
|
+
def setup_ssl_auth(ssl)
|
81
|
+
client.cert_key = ssl.cert_key_file
|
82
|
+
client.cert = ssl.cert_file
|
83
|
+
client.cacert = ssl.ca_cert_file if ssl.ca_cert_file
|
84
|
+
client.ssl_verify_peer = ssl.verify_mode == :peer
|
76
85
|
end
|
77
86
|
|
78
87
|
def respond_with(client)
|
@@ -22,7 +22,7 @@ module HTTPI
|
|
22
22
|
# Executes an HTTP GET request.
|
23
23
|
# @see HTTPI.get
|
24
24
|
def get(request)
|
25
|
-
do_request request do |
|
25
|
+
do_request request do |url, headers|
|
26
26
|
client.get url, nil, headers
|
27
27
|
end
|
28
28
|
end
|
@@ -30,7 +30,7 @@ module HTTPI
|
|
30
30
|
# Executes an HTTP POST request.
|
31
31
|
# @see HTTPI.post
|
32
32
|
def post(request)
|
33
|
-
do_request request do |
|
33
|
+
do_request request do |url, headers, body|
|
34
34
|
client.post url, body, headers
|
35
35
|
end
|
36
36
|
end
|
@@ -38,7 +38,7 @@ module HTTPI
|
|
38
38
|
# Executes an HTTP HEAD request.
|
39
39
|
# @see HTTPI.head
|
40
40
|
def head(request)
|
41
|
-
do_request request do |
|
41
|
+
do_request request do |url, headers|
|
42
42
|
client.head url, nil, headers
|
43
43
|
end
|
44
44
|
end
|
@@ -46,7 +46,7 @@ module HTTPI
|
|
46
46
|
# Executes an HTTP PUT request.
|
47
47
|
# @see HTTPI.put
|
48
48
|
def put(request)
|
49
|
-
do_request request do |
|
49
|
+
do_request request do |url, headers, body|
|
50
50
|
client.put url, body, headers
|
51
51
|
end
|
52
52
|
end
|
@@ -54,30 +54,39 @@ module HTTPI
|
|
54
54
|
# Executes an HTTP DELETE request.
|
55
55
|
# @see HTTPI.delete
|
56
56
|
def delete(request)
|
57
|
-
do_request request do |
|
57
|
+
do_request request do |url, headers|
|
58
58
|
client.delete url, headers
|
59
59
|
end
|
60
60
|
end
|
61
|
+
|
61
62
|
private
|
62
63
|
|
63
64
|
def do_request(request)
|
64
65
|
setup_client request
|
65
|
-
respond_with yield(
|
66
|
+
respond_with yield(request.url, request.headers, request.body)
|
66
67
|
end
|
67
68
|
|
68
69
|
def setup_client(request)
|
69
70
|
basic_setup request
|
70
|
-
|
71
|
+
setup_http_auth request if request.auth.http?
|
72
|
+
setup_ssl_auth request.auth.ssl if request.auth.ssl?
|
71
73
|
end
|
72
74
|
|
73
75
|
def basic_setup(request)
|
74
76
|
client.proxy = request.proxy if request.proxy
|
75
|
-
client.connect_timeout = request.open_timeout
|
76
|
-
client.receive_timeout = request.read_timeout
|
77
|
+
client.connect_timeout = request.open_timeout if request.open_timeout
|
78
|
+
client.receive_timeout = request.read_timeout if request.read_timeout
|
79
|
+
end
|
80
|
+
|
81
|
+
def setup_http_auth(request)
|
82
|
+
client.set_auth request.url, *request.auth.credentials
|
77
83
|
end
|
78
84
|
|
79
|
-
def
|
80
|
-
client.
|
85
|
+
def setup_ssl_auth(ssl)
|
86
|
+
client.ssl_config.client_cert = ssl.cert
|
87
|
+
client.ssl_config.client_key = ssl.cert_key
|
88
|
+
client.ssl_config.client_ca = ssl.ca_cert if ssl.ca_cert_file
|
89
|
+
client.ssl_config.verify_mode = ssl.openssl_verify_mode
|
81
90
|
end
|
82
91
|
|
83
92
|
def respond_with(response)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require "uri"
|
1
2
|
require "httpi/response"
|
2
3
|
|
3
4
|
module HTTPI
|
@@ -9,7 +10,7 @@ module HTTPI
|
|
9
10
|
# http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/
|
10
11
|
class NetHTTP
|
11
12
|
|
12
|
-
# Requires the "net/https" library.
|
13
|
+
# Requires the "net/https" library and sets up a new client.
|
13
14
|
def initialize(request)
|
14
15
|
require "net/https"
|
15
16
|
self.client = new_client request
|
@@ -70,6 +71,7 @@ module HTTPI
|
|
70
71
|
|
71
72
|
def do_request(type, request)
|
72
73
|
setup_client request
|
74
|
+
setup_ssl_auth request.auth.ssl if request.auth.ssl?
|
73
75
|
|
74
76
|
respond_with(client.start do |http|
|
75
77
|
yield http, request_client(type, request)
|
@@ -77,9 +79,16 @@ module HTTPI
|
|
77
79
|
end
|
78
80
|
|
79
81
|
def setup_client(request)
|
80
|
-
client.use_ssl =
|
81
|
-
client.open_timeout = request.open_timeout
|
82
|
-
client.read_timeout = request.read_timeout
|
82
|
+
client.use_ssl = request.ssl?
|
83
|
+
client.open_timeout = request.open_timeout if request.open_timeout
|
84
|
+
client.read_timeout = request.read_timeout if request.read_timeout
|
85
|
+
end
|
86
|
+
|
87
|
+
def setup_ssl_auth(ssl)
|
88
|
+
client.key = ssl.cert_key
|
89
|
+
client.cert = ssl.cert
|
90
|
+
client.ca_file = ssl.ca_cert_file if ssl.ca_cert_file
|
91
|
+
client.verify_mode = ssl.openssl_verify_mode
|
83
92
|
end
|
84
93
|
|
85
94
|
def request_client(type, request)
|
@@ -92,12 +101,7 @@ module HTTPI
|
|
92
101
|
end
|
93
102
|
|
94
103
|
request_client = request_class.new request.url.request_uri, request.headers
|
95
|
-
request_client
|
96
|
-
request_client
|
97
|
-
end
|
98
|
-
|
99
|
-
def auth_setup(request_client, request)
|
100
|
-
request_client.basic_auth *request.credentials if request.auth_type == :basic
|
104
|
+
request_client.basic_auth *request.auth.credentials if request.auth.basic?
|
101
105
|
request_client
|
102
106
|
end
|
103
107
|
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "httpi/auth/ssl"
|
2
|
+
|
3
|
+
module HTTPI
|
4
|
+
module Auth
|
5
|
+
|
6
|
+
# = HTTPI::Auth::Config
|
7
|
+
#
|
8
|
+
# Manages HTTP and SSL auth configuration. Currently supports HTTP basic/digest
|
9
|
+
# and SSL client authentication.
|
10
|
+
class Config
|
11
|
+
|
12
|
+
# Supported authentication types.
|
13
|
+
TYPES = [:basic, :digest, :ssl]
|
14
|
+
|
15
|
+
# Accessor for the HTTP basic auth credentials.
|
16
|
+
def basic(*args)
|
17
|
+
return @basic if args.empty?
|
18
|
+
|
19
|
+
self.type = :basic
|
20
|
+
@basic = args.flatten.compact
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns whether to use HTTP basic auth.
|
24
|
+
def basic?
|
25
|
+
type == :basic
|
26
|
+
end
|
27
|
+
|
28
|
+
# Accessor for the HTTP digest auth credentials.
|
29
|
+
def digest(*args)
|
30
|
+
return @digest if args.empty?
|
31
|
+
|
32
|
+
self.type = :digest
|
33
|
+
@digest = args.flatten.compact
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns whether to use HTTP digest auth.
|
37
|
+
def digest?
|
38
|
+
type == :digest
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns whether to use HTTP basic or dihest auth.
|
42
|
+
def http?
|
43
|
+
type == :basic || type == :digest
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the <tt>HTTPI::Auth::SSL</tt> object.
|
47
|
+
def ssl
|
48
|
+
@ssl ||= SSL.new
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns whether to use SSL client auth.
|
52
|
+
def ssl?
|
53
|
+
ssl.present?
|
54
|
+
end
|
55
|
+
|
56
|
+
# Shortcut method for returning the credentials for the authentication specified.
|
57
|
+
# Returns +nil+ unless any authentication credentials were specified.
|
58
|
+
def credentials
|
59
|
+
return unless type
|
60
|
+
send type
|
61
|
+
end
|
62
|
+
|
63
|
+
# Accessor for the authentication type in use.
|
64
|
+
attr_accessor :type
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "openssl"
|
2
|
+
|
3
|
+
module HTTPI
|
4
|
+
module Auth
|
5
|
+
|
6
|
+
# = HTTPI::Auth::SSL
|
7
|
+
#
|
8
|
+
# Provides SSL client authentication.
|
9
|
+
class SSL
|
10
|
+
|
11
|
+
VERIFY_MODES = [:none, :peer, :fail_if_no_peer_cert, :client_once]
|
12
|
+
|
13
|
+
# Returns whether SSL configuration is present.
|
14
|
+
def present?
|
15
|
+
cert && cert_key
|
16
|
+
rescue TypeError, Errno::ENOENT
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
# Accessor for the cert key file to validate SSL certificates.
|
21
|
+
attr_accessor :cert_key_file
|
22
|
+
|
23
|
+
# Accessor for the cert file to validate SSL connections.
|
24
|
+
attr_accessor :cert_file
|
25
|
+
|
26
|
+
# Accessor for the cacert file to validate SSL certificates.
|
27
|
+
attr_accessor :ca_cert_file
|
28
|
+
|
29
|
+
# Returns the SSL verify mode. Defaults to <tt>:peer</tt>.
|
30
|
+
def verify_mode
|
31
|
+
@verify_mode ||= :peer
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sets the SSL verify mode. Expects one of <tt>HTTPI::Auth::SSL::VERIFY_MODES</tt>.
|
35
|
+
def verify_mode=(mode)
|
36
|
+
raise ArgumentError, "Invalid SSL verify mode: #{mode}" unless VERIFY_MODES.include? mode
|
37
|
+
@verify_mode = mode
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns an <tt>OpenSSL::X509::Certificate</tt> for the +cert_file+.
|
41
|
+
def cert
|
42
|
+
@cert ||= OpenSSL::X509::Certificate.new File.read(cert_file)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Sets the +OpenSSL+ certificate.
|
46
|
+
attr_writer :cert
|
47
|
+
|
48
|
+
# Returns an <tt>OpenSSL::X509::Certificate</tt> for the +ca_cert_file+.
|
49
|
+
def ca_cert
|
50
|
+
@ca_cert ||= OpenSSL::X509::Certificate.new File.read(ca_cert_file)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Sets the +OpenSSL+ ca certificate.
|
54
|
+
attr_writer :ca_cert
|
55
|
+
|
56
|
+
# Returns an <tt>OpenSSL::PKey::RSA</tt> for the +cert_key_file+.
|
57
|
+
def cert_key
|
58
|
+
@cert_key ||= OpenSSL::PKey::RSA.new File.read(cert_key_file)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Sets the +OpenSSL+ certificate key.
|
62
|
+
attr_writer :cert_key
|
63
|
+
|
64
|
+
# Returns the SSL verify mode as a <tt>OpenSSL::SSL::VERIFY_*</tt> constant.
|
65
|
+
def openssl_verify_mode
|
66
|
+
case verify_mode
|
67
|
+
when :none then OpenSSL::SSL::VERIFY_NONE
|
68
|
+
when :peer then OpenSSL::SSL::VERIFY_PEER
|
69
|
+
when :fail_if_no_peer_cert then OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
70
|
+
when :client_once then OpenSSL::SSL::VERIFY_CLIENT_ONCE
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|