/
request.rb
169 lines (152 loc) · 6.24 KB
/
request.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
require 'uri'
require 'net/http'
module REST
# Request holds a HTTP request
class Request
attr_accessor :verb, :url, :body, :headers, :options, :request
# * <tt>verb</tt>: The verb to use in the request, either :get, :head, :put, or :post
# * <tt>url</tt>: The URL to send the request to, must be a URI instance
# * <tt>body</tt>: The body to use in the request
# * <tt>headers</tt>: A hash of headers to add to the request
# * <tt>options</tt>: A hash of additional options
# * <tt>username</tt>: Username to use for basic authentication
# * <tt>password</tt>: Password to use for basic authentication
# * <tt>tls_verify/verify_ssl</tt>: Verify the server certificate against known CA's
# * <tt>tls_ca_file</tt>: Use a specific file for CA certificates instead of the built-in one
# this only works when <tt>:tls_verify</tt> is also set.
# * <tt>tls_key_and_certificate_file</tt>: The client key and certificate file to use for this request
# * <tt>tls_certificate</tt>: The client certficate to use for this request
# * <tt>tls_key</tt>: The client private key to use for this request
#
# == Examples
#
# request = REST::Request.new(:get, URI.parse('http://example.com/pigeons/1'))
#
# request = REST::Request.new(:head, URI.parse('http://example.com/pigeons/1'))
#
# request = REST::Request.new(:post,
# URI.parse('http://example.com/pigeons'),
# {'name' => 'Homr'}.to_json,
# {'Accept' => 'application/json, */*', 'Content-Type' => 'application/json; charset=utf-8'}
# )
#
# == Authentication example
#
# request = REST::Request.new(:put,
# URI.parse('http://example.com/pigeons/1'),
# {'name' => 'Homer'}.to_json,
# {'Accept' => 'application/json, */*', 'Content-Type' => 'application/json; charset=utf-8'},
# {:username => 'Admin', :password => 'secret'}
# )
#
# == TLS / SSL examples
#
# # Use a client key and certificate
# request = REST::Request.new(:get, URI.parse('https://example.com/pigeons/1'), nil, {}, {
# :tls_key_and_certificate_file => '/home/alice/keys/example.pem'
# })
#
# # Use a client certificate and key from a specific location
# key_and_certificate = File.read('/home/alice/keys/example.pem')
# request = REST::Request.new(:get, URI.parse('https://example.com/pigeons/1'), nil, {}, {
# :tls_key => OpenSSL::PKey::RSA.new(key_and_certificate),
# :tls_certificate => OpenSSL::X509::Certificate.new(key_and_certificate)
# })
#
# # Verify the server certificate against a specific certificate
# request = REST::Request.new(:get, URI.parse('https://example.com/pigeons/1'), nil, {}, {
# :tls_verify => true,
# :tls_ca_file => '/home/alice/keys/example.pem'
# })
def initialize(verb, url, body=nil, headers={}, options={})
@verb = verb
@url = url
@body = body
@headers = headers
@options = options
end
# Returns the path (including the query) for the request
def path
[url.path, url.query].compact.join('?')
end
def http_proxy
ENV['HTTP_PROXY'] || ENV['http_proxy']
end
def proxy_settings
http_proxy ? URI.parse(http_proxy) : nil
end
def proxy
@proxy ||= Net::HTTP.Proxy(proxy_settings.host, proxy_settings.port, proxy_settings.user, proxy_settings.password)
end
def http_request
if proxy_settings
proxy.new(url.host, url.port)
else
Net::HTTP.new(url.host, url.port)
end
end
# Performs the actual request and returns a REST::Response object with the response
def perform
case verb
when :get
self.request = Net::HTTP::Get.new(path, headers)
when :head
self.request = Net::HTTP::Head.new(path, headers)
when :delete
self.request = Net::HTTP::Delete.new(path, headers)
when :put
self.request = Net::HTTP::Put.new(path, headers)
self.request.body = body
when :post
self.request = Net::HTTP::Post.new(path, headers)
self.request.body = body
else
raise ArgumentError, "Unknown HTTP verb `#{verb}'"
end
if options[:username] and options[:password]
request.basic_auth(options[:username], options[:password])
end
http_request = http_request()
# enable SSL/TLS
if url.scheme == 'https'
require 'net/https'
require 'openssl'
http_request.use_ssl = true
if options[:tls_verify] or options[:verify_ssl]
if http_request.respond_to?(:enable_post_connection_check=)
http_request.enable_post_connection_check = true
end
# from http://curl.haxx.se/ca/cacert.pem
http_request.ca_file = options[:tls_ca_file] || File.join(File.expand_path('../../../support/cacert.pem', __FILE__))
http_request.verify_mode = OpenSSL::SSL::VERIFY_PEER
else
http_request.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
if options[:tls_key_and_certificate_file]
key_and_certificate = File.read(options[:tls_key_and_certificate_file])
options[:tls_key] = OpenSSL::PKey::RSA.new(key_and_certificate)
options[:tls_certificate] = OpenSSL::X509::Certificate.new(key_and_certificate)
end
if options[:tls_key] and options[:tls_certificate]
http_request.key = options[:tls_key]
http_request.cert = options[:tls_certificate]
elsif options[:tls_key] || options[:tls_certificate]
raise ArgumentError, "Please specify both the certificate and private key (:tls_key and :tls_certificate)"
end
end
begin
response = http_request.start { |http| http.request(request) }
rescue EOFError => error
raise REST::DisconnectedError, error.message
end
REST::Response.new(response.code, response.instance_variable_get('@header'), response.body)
end
# Shortcut for REST::Request.new(*args).perform.
#
# See new for options.
def self.perform(*args)
request = new(*args)
request.perform
end
end
end