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.
@@ -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
- def initialize(request = nil)
13
- end
13
+ register :httpclient, :deps => %w(httpclient)
14
14
 
15
- # Returns a memoized <tt>HTTPClient</tt> instance.
16
- def client
17
- @client ||= ::HTTPClient.new
15
+ def initialize(request)
16
+ @request = request
17
+ @client = ::HTTPClient.new
18
18
  end
19
19
 
20
- # Executes an HTTP GET request.
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 an HTTP POST request.
29
- # @see HTTPI.post
30
- def post(request)
31
- do_request request do |url, headers, body|
32
- client.post url, body, headers
33
- end
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
- # Executes an HTTP HEAD request.
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
- # Executes an HTTP PUT request.
45
- # @see HTTPI.put
46
- def put(request)
47
- do_request request do |url, headers, body|
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
- # Executes an HTTP DELETE request.
53
- # @see HTTPI.delete
54
- def delete(request)
55
- do_request request do |url, headers|
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
- private
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 basic_setup(request)
74
- client.proxy = request.proxy if request.proxy
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.client_ca = ssl.ca_cert if ssl.ca_cert_file
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
- client.ssl_config.verify_mode = ssl.openssl_verify_mode
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
- self.client = new_client request
18
+ @request = request
19
+ @client = create_client
15
20
  end
16
21
 
17
22
  attr_reader :client
18
23
 
19
- # Executes an HTTP GET request.
20
- # @see HTTPI.get
21
- def get(request)
22
- do_request :get, request do |http, get|
23
- http.request get
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
- # Executes an HTTP DELETE request.
54
- # @see HTTPI.delete
55
- def delete(request)
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
- private
39
+ private
62
40
 
63
- attr_writer :client
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 request.url.host, request.url.port
44
+ proxy.new(@request.url.host, @request.url.port)
69
45
  end
70
46
 
71
- def do_request(type, request)
72
- setup_client request
73
- setup_ssl_auth request.auth.ssl if request.auth.ssl?
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, request)
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(request)
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(ssl)
87
- client.key = ssl.cert_key
88
- client.cert = ssl.cert
89
- client.ca_file = ssl.ca_cert_file if ssl.ca_cert_file
90
- client.verify_mode = ssl.openssl_verify_mode
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, request)
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 *request.auth.credentials if request.auth.basic?
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
@@ -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
- raise ArgumentError, "Invalid SSL cert type: #{type}" unless CERT_TYPES.include? type
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
- raise ArgumentError, "Invalid SSL verify mode: #{mode}" unless VERIFY_MODES.include? mode
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)