httpi 1.1.1 → 2.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.rvmrc +1 -0
- data/CHANGELOG.md +51 -26
- data/Gemfile +6 -4
- data/README.md +19 -205
- data/httpi.gemspec +7 -10
- data/lib/httpi.rb +54 -56
- data/lib/httpi/adapter.rb +25 -18
- data/lib/httpi/adapter/base.rb +35 -0
- data/lib/httpi/adapter/curb.rb +59 -60
- data/lib/httpi/adapter/em_http.rb +126 -0
- data/lib/httpi/adapter/httpclient.rb +33 -63
- data/lib/httpi/adapter/net_http.rb +44 -62
- data/lib/httpi/auth/ssl.rb +26 -2
- data/lib/httpi/dime.rb +45 -29
- data/lib/httpi/request.rb +1 -1
- data/lib/httpi/response.rb +6 -4
- data/lib/httpi/version.rb +1 -1
- data/spec/httpi/adapter/base_spec.rb +23 -0
- data/spec/httpi/adapter/curb_spec.rb +107 -67
- data/spec/httpi/adapter/em_http_spec.rb +168 -0
- data/spec/httpi/adapter/httpclient_spec.rb +67 -56
- data/spec/httpi/adapter/net_http_spec.rb +62 -47
- data/spec/httpi/adapter_spec.rb +15 -2
- data/spec/httpi/auth/ssl_spec.rb +34 -1
- data/spec/httpi/httpi_spec.rb +80 -115
- data/spec/integration/fixtures/ca.pem +23 -0
- data/spec/integration/fixtures/ca_all.pem +44 -0
- data/spec/integration/fixtures/htdigest +1 -0
- data/spec/integration/fixtures/htpasswd +2 -0
- data/spec/integration/fixtures/server.cert +19 -0
- data/spec/integration/fixtures/server.key +15 -0
- data/spec/integration/fixtures/subca.pem +21 -0
- data/spec/integration/request_spec.rb +15 -2
- data/spec/integration/ssl_server.rb +70 -0
- data/spec/integration/ssl_spec.rb +102 -0
- data/spec/support/fixture.rb +1 -1
- metadata +60 -73
@@ -0,0 +1,126 @@
|
|
1
|
+
require "httpi/adapter/base"
|
2
|
+
require "httpi/response"
|
3
|
+
|
4
|
+
module HTTPI
|
5
|
+
module Adapter
|
6
|
+
|
7
|
+
# An HTTPI adapter for `EventMachine::HttpRequest`. Due to limitations of
|
8
|
+
# the em-httprequest library, not all features are supported. In particular,
|
9
|
+
#
|
10
|
+
# * CA files,
|
11
|
+
# * certificate verification modes other than "none" and "peer,"
|
12
|
+
# * NTLM authentication,
|
13
|
+
# * digest authentication, and
|
14
|
+
# * password-protected certificate keys
|
15
|
+
#
|
16
|
+
# are supported by HTTPI but not em-httprequest.
|
17
|
+
#
|
18
|
+
# In addition, some features of em-httprequest are not represented in HTTPI
|
19
|
+
# and are therefore not supported. In particular,
|
20
|
+
#
|
21
|
+
# * SOCKS5 proxying,
|
22
|
+
# * automatic redirect following,
|
23
|
+
# * response streaming,
|
24
|
+
# * file body streaming,
|
25
|
+
# * keepalive,
|
26
|
+
# * pipelining, and
|
27
|
+
# * multi-request
|
28
|
+
#
|
29
|
+
# are supported by em-httprequest but not HTTPI.
|
30
|
+
class EmHttpRequest < Base
|
31
|
+
|
32
|
+
register :em_http, :deps => %w(em-synchrony em-synchrony/em-http em-http)
|
33
|
+
|
34
|
+
def initialize(request)
|
35
|
+
@request = request
|
36
|
+
@client = EventMachine::HttpRequest.new build_request_url(request.url)
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :client
|
40
|
+
|
41
|
+
def cert_directory
|
42
|
+
@cert_directory ||= "/tmp"
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_writer :cert_directory
|
46
|
+
|
47
|
+
# Executes arbitrary HTTP requests.
|
48
|
+
# @see HTTPI.request
|
49
|
+
def request(method)
|
50
|
+
_request { |options| @client.send method, options }
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def _request
|
56
|
+
options = client_options
|
57
|
+
setup_proxy(options) if @request.proxy
|
58
|
+
setup_http_auth(options) if @request.auth.http?
|
59
|
+
|
60
|
+
if @request.auth.ssl?
|
61
|
+
raise NotSupportedError, "EM-HTTP-Request does not support SSL client auth"
|
62
|
+
end
|
63
|
+
|
64
|
+
start_time = Time.now
|
65
|
+
respond_with yield(options), start_time
|
66
|
+
end
|
67
|
+
|
68
|
+
def client_options
|
69
|
+
{
|
70
|
+
:query => @request.url.query,
|
71
|
+
:connect_timeout => @request.open_timeout,
|
72
|
+
:inactivity_timeout => @request.read_timeout,
|
73
|
+
:head => @request.headers.to_hash,
|
74
|
+
:body => @request.body
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
def setup_proxy(options)
|
79
|
+
options[:proxy] = {
|
80
|
+
:host => @request.proxy.host,
|
81
|
+
:port => @request.proxy.port,
|
82
|
+
:authorization => [@request.proxy.user, @request.proxy.password]
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def setup_http_auth(options)
|
87
|
+
unless @request.auth.type == :basic
|
88
|
+
raise NotSupportedError, "EM-HTTP-Request does only support HTTP basic auth"
|
89
|
+
end
|
90
|
+
|
91
|
+
options[:head] ||= {}
|
92
|
+
options[:head][:authorization] = @request.auth.credentials
|
93
|
+
end
|
94
|
+
|
95
|
+
def respond_with(http, start_time)
|
96
|
+
raise TimeoutError, "EM-HTTP-Request connection timed out: #{Time.now - start_time} sec" if http.response_header.status.zero?
|
97
|
+
|
98
|
+
Response.new http.response_header.status,
|
99
|
+
convert_headers(http.response_header), http.response
|
100
|
+
end
|
101
|
+
|
102
|
+
def build_request_url(url)
|
103
|
+
"%s://%s:%s%s" % [url.scheme, url.host, url.port, url.path]
|
104
|
+
end
|
105
|
+
|
106
|
+
# Takes any header names with an underscore as a word separator and
|
107
|
+
# converts the name to camel case, where words are separated by a dash.
|
108
|
+
#
|
109
|
+
# E.g. CONTENT_TYPE becomes Content-Type.
|
110
|
+
def convert_headers(headers)
|
111
|
+
return headers unless headers.keys.any? { |k| k =~ /_/ }
|
112
|
+
|
113
|
+
result = {}
|
114
|
+
|
115
|
+
headers.each do |k, v|
|
116
|
+
words = k.split("_")
|
117
|
+
key = words.map { |w| w.downcase.capitalize }.join("-")
|
118
|
+
result[key] = v
|
119
|
+
end
|
120
|
+
|
121
|
+
result
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require "httpi/adapter/base"
|
1
2
|
require "httpi/response"
|
2
3
|
|
3
4
|
module HTTPI
|
@@ -7,86 +8,55 @@ module HTTPI
|
|
7
8
|
#
|
8
9
|
# Adapter for the HTTPClient client.
|
9
10
|
# http://rubygems.org/gems/httpclient
|
10
|
-
class HTTPClient
|
11
|
+
class HTTPClient < Base
|
11
12
|
|
12
|
-
|
13
|
-
end
|
13
|
+
register :httpclient, :deps => %w(httpclient)
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
@client
|
15
|
+
def initialize(request)
|
16
|
+
@request = request
|
17
|
+
@client = ::HTTPClient.new
|
18
18
|
end
|
19
19
|
|
20
|
-
|
21
|
-
# @see HTTPI.get
|
22
|
-
def get(request)
|
23
|
-
do_request request do |url, headers|
|
24
|
-
client.get url, nil, headers
|
25
|
-
end
|
26
|
-
end
|
20
|
+
attr_reader :client
|
27
21
|
|
28
|
-
# Executes
|
29
|
-
# @see HTTPI.
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
22
|
+
# Executes arbitrary HTTP requests.
|
23
|
+
# @see HTTPI.request
|
24
|
+
def request(method)
|
25
|
+
setup_client
|
26
|
+
respond_with @client.request(method, @request.url, nil, @request.body, @request.headers)
|
27
|
+
rescue OpenSSL::SSL::SSLError
|
28
|
+
raise SSLError
|
34
29
|
end
|
35
30
|
|
36
|
-
|
37
|
-
# @see HTTPI.head
|
38
|
-
def head(request)
|
39
|
-
do_request request do |url, headers|
|
40
|
-
client.head url, nil, headers
|
41
|
-
end
|
42
|
-
end
|
31
|
+
private
|
43
32
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
client.put url, body, headers
|
49
|
-
end
|
33
|
+
def setup_client
|
34
|
+
basic_setup
|
35
|
+
setup_auth if @request.auth.http?
|
36
|
+
setup_ssl_auth if @request.auth.ssl?
|
50
37
|
end
|
51
38
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
client.delete url, headers
|
57
|
-
end
|
39
|
+
def basic_setup
|
40
|
+
@client.proxy = @request.proxy if @request.proxy
|
41
|
+
@client.connect_timeout = @request.open_timeout if @request.open_timeout
|
42
|
+
@client.receive_timeout = @request.read_timeout if @request.read_timeout
|
58
43
|
end
|
59
44
|
|
60
|
-
|
61
|
-
|
62
|
-
def do_request(request)
|
63
|
-
setup_client request
|
64
|
-
respond_with yield(request.url, request.headers, request.body)
|
65
|
-
end
|
66
|
-
|
67
|
-
def setup_client(request)
|
68
|
-
basic_setup request
|
69
|
-
setup_auth request if request.auth.http?
|
70
|
-
setup_ssl_auth request.auth.ssl if request.auth.ssl?
|
45
|
+
def setup_auth
|
46
|
+
@client.set_auth @request.url, *@request.auth.credentials
|
71
47
|
end
|
72
48
|
|
73
|
-
def
|
74
|
-
|
75
|
-
client.connect_timeout = request.open_timeout if request.open_timeout
|
76
|
-
client.receive_timeout = request.read_timeout if request.read_timeout
|
77
|
-
end
|
49
|
+
def setup_ssl_auth
|
50
|
+
ssl = @request.auth.ssl
|
78
51
|
|
79
|
-
def setup_auth(request)
|
80
|
-
client.set_auth request.url, *request.auth.credentials
|
81
|
-
end
|
82
|
-
|
83
|
-
def setup_ssl_auth(ssl)
|
84
52
|
unless ssl.verify_mode == :none
|
85
|
-
client.ssl_config.client_cert = ssl.cert
|
86
|
-
client.ssl_config.client_key = ssl.cert_key
|
87
|
-
client.ssl_config.
|
53
|
+
@client.ssl_config.client_cert = ssl.cert
|
54
|
+
@client.ssl_config.client_key = ssl.cert_key
|
55
|
+
@client.ssl_config.add_trust_ca(ssl.ca_cert_file) if ssl.ca_cert_file
|
88
56
|
end
|
89
|
-
|
57
|
+
|
58
|
+
@client.ssl_config.verify_mode = ssl.openssl_verify_mode
|
59
|
+
@client.ssl_config.ssl_version = ssl.ssl_version if ssl.ssl_version
|
90
60
|
end
|
91
61
|
|
92
62
|
def respond_with(response)
|
@@ -1,4 +1,6 @@
|
|
1
1
|
require "uri"
|
2
|
+
|
3
|
+
require "httpi/adapter/base"
|
2
4
|
require "httpi/response"
|
3
5
|
|
4
6
|
module HTTPI
|
@@ -8,89 +10,69 @@ module HTTPI
|
|
8
10
|
#
|
9
11
|
# Adapter for the Net::HTTP client.
|
10
12
|
# http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/
|
11
|
-
class NetHTTP
|
13
|
+
class NetHTTP < Base
|
14
|
+
|
15
|
+
register :net_http, :deps => %w(net/https)
|
12
16
|
|
13
17
|
def initialize(request)
|
14
|
-
|
18
|
+
@request = request
|
19
|
+
@client = create_client
|
15
20
|
end
|
16
21
|
|
17
22
|
attr_reader :client
|
18
23
|
|
19
|
-
# Executes
|
20
|
-
# @see HTTPI.
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# Executes an HTTP POST request.
|
28
|
-
# @see HTTPI.post
|
29
|
-
def post(request)
|
30
|
-
do_request :post, request do |http, post|
|
31
|
-
post.body = request.body
|
32
|
-
http.request post
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
# Executes an HTTP HEAD request.
|
37
|
-
# @see HTTPI.head
|
38
|
-
def head(request)
|
39
|
-
do_request :head, request do |http, head|
|
40
|
-
http.request head
|
24
|
+
# Executes arbitrary HTTP requests.
|
25
|
+
# @see HTTPI.request
|
26
|
+
def request(method)
|
27
|
+
unless REQUEST_METHODS.include? method
|
28
|
+
raise NotSupportedError, "Net::HTTP does not support custom HTTP methods"
|
41
29
|
end
|
42
|
-
end
|
43
|
-
|
44
|
-
# Executes an HTTP PUT request.
|
45
|
-
# @see HTTPI.put
|
46
|
-
def put(request)
|
47
|
-
do_request :put, request do |http, put|
|
48
|
-
put.body = request.body
|
49
|
-
http.request put
|
50
|
-
end
|
51
|
-
end
|
52
30
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
do_request :delete, request do |http, delete|
|
57
|
-
http.request delete
|
31
|
+
do_request(method) do |http, http_request|
|
32
|
+
http_request.body = @request.body
|
33
|
+
http.request http_request
|
58
34
|
end
|
35
|
+
rescue OpenSSL::SSL::SSLError
|
36
|
+
raise SSLError
|
59
37
|
end
|
60
38
|
|
61
|
-
|
39
|
+
private
|
62
40
|
|
63
|
-
|
64
|
-
|
65
|
-
def new_client(request)
|
66
|
-
proxy_url = request.proxy || URI("")
|
41
|
+
def create_client
|
42
|
+
proxy_url = @request.proxy || URI("")
|
67
43
|
proxy = Net::HTTP::Proxy(proxy_url.host, proxy_url.port, proxy_url.user, proxy_url.password)
|
68
|
-
proxy.new
|
44
|
+
proxy.new(@request.url.host, @request.url.port)
|
69
45
|
end
|
70
46
|
|
71
|
-
def do_request(type
|
72
|
-
setup_client
|
73
|
-
setup_ssl_auth
|
47
|
+
def do_request(type)
|
48
|
+
setup_client
|
49
|
+
setup_ssl_auth if @request.auth.ssl?
|
74
50
|
|
75
|
-
respond_with(client.start do |http|
|
76
|
-
yield http, request_client(type
|
51
|
+
respond_with(@client.start do |http|
|
52
|
+
yield http, request_client(type)
|
77
53
|
end)
|
78
54
|
end
|
79
55
|
|
80
|
-
def setup_client
|
81
|
-
client.use_ssl = request.ssl?
|
82
|
-
client.open_timeout = request.open_timeout if request.open_timeout
|
83
|
-
client.read_timeout = request.read_timeout if request.read_timeout
|
56
|
+
def setup_client
|
57
|
+
@client.use_ssl = @request.ssl?
|
58
|
+
@client.open_timeout = @request.open_timeout if @request.open_timeout
|
59
|
+
@client.read_timeout = @request.read_timeout if @request.read_timeout
|
84
60
|
end
|
85
61
|
|
86
|
-
def setup_ssl_auth
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
62
|
+
def setup_ssl_auth
|
63
|
+
ssl = @request.auth.ssl
|
64
|
+
|
65
|
+
unless ssl.verify_mode == :none
|
66
|
+
@client.key = ssl.cert_key
|
67
|
+
@client.cert = ssl.cert
|
68
|
+
@client.ca_file = ssl.ca_cert_file if ssl.ca_cert_file
|
69
|
+
end
|
70
|
+
|
71
|
+
@client.verify_mode = ssl.openssl_verify_mode
|
72
|
+
@client.ssl_version = ssl.ssl_version if ssl.ssl_version
|
91
73
|
end
|
92
74
|
|
93
|
-
def request_client(type
|
75
|
+
def request_client(type)
|
94
76
|
request_class = case type
|
95
77
|
when :get then Net::HTTP::Get
|
96
78
|
when :post then Net::HTTP::Post
|
@@ -99,8 +81,8 @@ module HTTPI
|
|
99
81
|
when :delete then Net::HTTP::Delete
|
100
82
|
end
|
101
83
|
|
102
|
-
request_client = request_class.new request.url.request_uri, request.headers
|
103
|
-
request_client.basic_auth
|
84
|
+
request_client = request_class.new @request.url.request_uri, @request.headers
|
85
|
+
request_client.basic_auth *@request.auth.credentials if @request.auth.basic?
|
104
86
|
|
105
87
|
request_client
|
106
88
|
end
|
data/lib/httpi/auth/ssl.rb
CHANGED
@@ -10,6 +10,7 @@ module HTTPI
|
|
10
10
|
|
11
11
|
VERIFY_MODES = [:none, :peer, :fail_if_no_peer_cert, :client_once]
|
12
12
|
CERT_TYPES = [:pem, :der]
|
13
|
+
SSL_VERSIONS = [:TLSv1, :SSLv2, :SSLv3]
|
13
14
|
|
14
15
|
# Returns whether SSL configuration is present.
|
15
16
|
def present?
|
@@ -37,7 +38,11 @@ module HTTPI
|
|
37
38
|
|
38
39
|
# Sets the cert type to validate SSL certificates PEM|DER.
|
39
40
|
def cert_type=(type)
|
40
|
-
|
41
|
+
unless CERT_TYPES.include? type
|
42
|
+
raise ArgumentError, "Invalid SSL cert type #{type.inspect}\n" +
|
43
|
+
"Please specify one of #{CERT_TYPES.inspect}"
|
44
|
+
end
|
45
|
+
|
41
46
|
@cert_type = type
|
42
47
|
end
|
43
48
|
|
@@ -48,10 +53,29 @@ module HTTPI
|
|
48
53
|
|
49
54
|
# Sets the SSL verify mode. Expects one of <tt>HTTPI::Auth::SSL::VERIFY_MODES</tt>.
|
50
55
|
def verify_mode=(mode)
|
51
|
-
|
56
|
+
unless VERIFY_MODES.include? mode
|
57
|
+
raise ArgumentError, "Invalid SSL verify mode #{mode.inspect}\n" +
|
58
|
+
"Please specify one of #{VERIFY_MODES.inspect}"
|
59
|
+
end
|
60
|
+
|
52
61
|
@verify_mode = mode
|
53
62
|
end
|
54
63
|
|
64
|
+
# Returns the SSL version number. Defaults to <tt>nil</tt> (auto-negotiate).
|
65
|
+
def ssl_version
|
66
|
+
@ssl_version
|
67
|
+
end
|
68
|
+
|
69
|
+
# Sets the SSL version number. Expects one of <tt>HTTPI::Auth::SSL::SSL_VERSIONS</tt>.
|
70
|
+
def ssl_version=(version)
|
71
|
+
unless SSL_VERSIONS.include? version
|
72
|
+
raise ArgumentError, "Invalid SSL version #{version.inspect}\n" +
|
73
|
+
"Please specify one of #{SSL_VERSIONS.inspect}"
|
74
|
+
end
|
75
|
+
|
76
|
+
@ssl_version = version
|
77
|
+
end
|
78
|
+
|
55
79
|
# Returns an <tt>OpenSSL::X509::Certificate</tt> for the +cert_file+.
|
56
80
|
def cert
|
57
81
|
@cert ||= (OpenSSL::X509::Certificate.new File.read(cert_file) if cert_file)
|