cs-httpi 0.9.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/.autotest +5 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG.md +66 -0
  6. data/Gemfile +9 -0
  7. data/LICENSE +20 -0
  8. data/README.md +229 -0
  9. data/Rakefile +18 -0
  10. data/autotest/discover.rb +1 -0
  11. data/cs-httpi.gemspec +26 -0
  12. data/lib/cs-httpi.rb +198 -0
  13. data/lib/cs-httpi/adapter.rb +67 -0
  14. data/lib/cs-httpi/adapter/curb.rb +119 -0
  15. data/lib/cs-httpi/adapter/httpclient.rb +98 -0
  16. data/lib/cs-httpi/adapter/net_http.rb +115 -0
  17. data/lib/cs-httpi/auth/config.rb +78 -0
  18. data/lib/cs-httpi/auth/ssl.rb +91 -0
  19. data/lib/cs-httpi/dime.rb +56 -0
  20. data/lib/cs-httpi/request.rb +99 -0
  21. data/lib/cs-httpi/response.rb +85 -0
  22. data/lib/cs-httpi/version.rb +5 -0
  23. data/nbproject/private/private.properties +2 -0
  24. data/nbproject/private/rake-d.txt +0 -0
  25. data/nbproject/project.properties +7 -0
  26. data/nbproject/project.xml +15 -0
  27. data/spec/cs-httpi/adapter/curb_spec.rb +232 -0
  28. data/spec/cs-httpi/adapter/httpclient_spec.rb +164 -0
  29. data/spec/cs-httpi/adapter/net_http_spec.rb +142 -0
  30. data/spec/cs-httpi/adapter_spec.rb +55 -0
  31. data/spec/cs-httpi/auth/config_spec.rb +117 -0
  32. data/spec/cs-httpi/auth/ssl_spec.rb +128 -0
  33. data/spec/cs-httpi/httpi_spec.rb +284 -0
  34. data/spec/cs-httpi/request_spec.rb +140 -0
  35. data/spec/cs-httpi/response_spec.rb +125 -0
  36. data/spec/fixtures/attachment.gif +0 -0
  37. data/spec/fixtures/client_cert.pem +16 -0
  38. data/spec/fixtures/client_key.pem +15 -0
  39. data/spec/fixtures/xml.gz +0 -0
  40. data/spec/fixtures/xml.xml +10 -0
  41. data/spec/fixtures/xml_dime.dime +0 -0
  42. data/spec/fixtures/xml_dime.xml +1 -0
  43. data/spec/integration/request_spec.rb +95 -0
  44. data/spec/integration/server.rb +39 -0
  45. data/spec/spec_helper.rb +12 -0
  46. data/spec/support/fixture.rb +27 -0
  47. data/spec/support/matchers.rb +19 -0
  48. metadata +158 -0
@@ -0,0 +1,91 @@
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
+ CERT_TYPES = [:pem, :der]
13
+
14
+ # Returns whether SSL configuration is present.
15
+ def present?
16
+ (verify_mode == :none) || (cert && cert_key)
17
+ rescue TypeError, Errno::ENOENT
18
+ false
19
+ end
20
+
21
+ # Accessor for the cert key file to validate SSL certificates.
22
+ attr_accessor :cert_key_file
23
+
24
+ # Accessor for the cert key password to validate SSL certificates.
25
+ attr_accessor :cert_key_password
26
+
27
+ # Accessor for the cert file to validate SSL connections.
28
+ attr_accessor :cert_file
29
+
30
+ # Accessor for the cacert file to validate SSL certificates.
31
+ attr_accessor :ca_cert_file
32
+
33
+ # Returns the cert type to validate SSL certificates PEM|DER.
34
+ def cert_type
35
+ @cert_type ||= :pem
36
+ end
37
+
38
+ # Sets the cert type to validate SSL certificates PEM|DER.
39
+ def cert_type=(type)
40
+ raise ArgumentError, "Invalid SSL cert type: #{type}" unless CERT_TYPES.include? type
41
+ @cert_type = type
42
+ end
43
+
44
+ # Returns the SSL verify mode. Defaults to <tt>:peer</tt>.
45
+ def verify_mode
46
+ @verify_mode ||= :peer
47
+ end
48
+
49
+ # Sets the SSL verify mode. Expects one of <tt>HTTPI::Auth::SSL::VERIFY_MODES</tt>.
50
+ def verify_mode=(mode)
51
+ raise ArgumentError, "Invalid SSL verify mode: #{mode}" unless VERIFY_MODES.include? mode
52
+ @verify_mode = mode
53
+ end
54
+
55
+ # Returns an <tt>OpenSSL::X509::Certificate</tt> for the +cert_file+.
56
+ def cert
57
+ @cert ||= OpenSSL::X509::Certificate.new File.read(cert_file) if cert_file
58
+ end
59
+
60
+ # Sets the +OpenSSL+ certificate.
61
+ attr_writer :cert
62
+
63
+ # Returns an <tt>OpenSSL::X509::Certificate</tt> for the +ca_cert_file+.
64
+ def ca_cert
65
+ @ca_cert ||= OpenSSL::X509::Certificate.new File.read(ca_cert_file)
66
+ end
67
+
68
+ # Sets the +OpenSSL+ ca certificate.
69
+ attr_writer :ca_cert
70
+
71
+ # Returns an <tt>OpenSSL::PKey::RSA</tt> for the +cert_key_file+.
72
+ def cert_key
73
+ @cert_key ||= OpenSSL::PKey::RSA.new(File.read(cert_key_file), cert_key_password) if cert_key_file
74
+ end
75
+
76
+ # Sets the +OpenSSL+ certificate key.
77
+ attr_writer :cert_key
78
+
79
+ # Returns the SSL verify mode as a <tt>OpenSSL::SSL::VERIFY_*</tt> constant.
80
+ def openssl_verify_mode
81
+ case verify_mode
82
+ when :none then OpenSSL::SSL::VERIFY_NONE
83
+ when :peer then OpenSSL::SSL::VERIFY_PEER
84
+ when :fail_if_no_peer_cert then OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
85
+ when :client_once then OpenSSL::SSL::VERIFY_CLIENT_ONCE
86
+ end
87
+ end
88
+
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,56 @@
1
+ module HTTPI
2
+ class DimeRecord < Struct.new('DimeRecord', :version, :first, :last, :chunked, :type_format, :options, :id, :type, :data)
3
+ end
4
+
5
+ class Dime < Array
6
+ BINARY = 1
7
+ XML = 2
8
+
9
+ def initialize(body)
10
+ bytes = body.unpack('C*')
11
+
12
+ while bytes.length > 0
13
+ record = DimeRecord.new
14
+
15
+ # Shift out bitfields for the first fields
16
+ byte = bytes.shift
17
+ record.version = (byte >> 3) & 31 # 5 bits DIME format version (always 1)
18
+ record.first = (byte >> 2) & 1 # 1 bit Set if this is the first part in the message
19
+ record.last = (byte >> 1) & 1 # 1 bit Set if this is the last part in the message
20
+ record.chunked = byte & 1 # 1 bit This file is broken into chunked parts
21
+ record.type_format = (bytes.shift >> 4) & 15 # 4 bits Type of file in the part (1 for binary data, 2 for XML)
22
+ # 4 bits Reserved (skipped in the above command)
23
+
24
+ # Fetch big-endian lengths
25
+ lengths = [] # we can't use a hash since the order will be screwed in Ruby 1.8
26
+ lengths << [:options, (bytes.shift << 8) | bytes.shift] # 2 bytes Length of the "options" field
27
+ lengths << [:id, (bytes.shift << 8) | bytes.shift] # 2 bytes Length of the "ID" or "name" field
28
+ lengths << [:type, (bytes.shift << 8) | bytes.shift] # 2 bytes Length of the "type" field
29
+ lengths << [:data, (bytes.shift << 24) | (bytes.shift << 16) | (bytes.shift << 8) | bytes.shift] # 4 bytes Size of the included file
30
+
31
+ # Read in padded data
32
+ lengths.each do |attribute_set|
33
+ attribute, length = attribute_set
34
+ content = bytes.slice!(0, length).pack('C*')
35
+ if attribute == :data && record.type_format == BINARY
36
+ content = StringIO.new(content)
37
+ end
38
+
39
+ record.send "#{attribute.to_s}=", content
40
+
41
+ bytes.slice!(0, 4 - (length & 3)) if (length & 3) != 0
42
+ end
43
+
44
+ self << record
45
+ end
46
+ end
47
+
48
+ def xml_records
49
+ select { |r| r.type_format == XML }
50
+ end
51
+
52
+ def binary_records
53
+ select { |r| r.type_format == BINARY }
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,99 @@
1
+ require "uri"
2
+ require "cs-httpi/auth/config"
3
+ require "rack/utils"
4
+
5
+ module HTTPI
6
+
7
+ # = HTTPI::Request
8
+ #
9
+ # Represents an HTTP request and contains various methods for customizing that request.
10
+ class Request
11
+
12
+ # Available attribute writers.
13
+ ATTRIBUTES = [:url, :proxy, :headers, :body, :open_timeout, :read_timeout]
14
+
15
+ # Accepts a Hash of +args+ to mass assign attributes and authentication credentials.
16
+ def initialize(args = {})
17
+ if args.kind_of? String
18
+ self.url = args
19
+ elsif args.kind_of?(Hash) && !args.empty?
20
+ mass_assign args
21
+ end
22
+ end
23
+
24
+ # Sets the +url+ to access. Raises an +ArgumentError+ unless the +url+ is valid.
25
+ def url=(url)
26
+ @url = normalize_url! url
27
+ end
28
+
29
+ # Returns the +url+ to access.
30
+ attr_reader :url
31
+
32
+ # Sets the +proxy+ to use. Raises an +ArgumentError+ unless the +proxy+ is valid.
33
+ def proxy=(proxy)
34
+ @proxy = normalize_url! proxy
35
+ end
36
+
37
+ # Returns the +proxy+ to use.
38
+ attr_reader :proxy
39
+
40
+ # Returns whether to use SSL.
41
+ def ssl?
42
+ return @ssl unless @ssl.nil?
43
+ !!(url.to_s =~ /^https/)
44
+ end
45
+
46
+ # Sets whether to use SSL.
47
+ attr_writer :ssl
48
+
49
+ # Returns a Hash of HTTP headers. Defaults to return an empty Hash.
50
+ def headers
51
+ @headers ||= Rack::Utils::HeaderHash.new
52
+ end
53
+
54
+ # Sets the Hash of HTTP headers.
55
+ def headers=(headers)
56
+ @headers = Rack::Utils::HeaderHash.new(headers)
57
+ end
58
+
59
+ # Adds a header information to accept gzipped content.
60
+ def gzip
61
+ headers["Accept-Encoding"] = "gzip,deflate"
62
+ end
63
+
64
+ attr_accessor :open_timeout, :read_timeout
65
+ attr_reader :body
66
+
67
+ def body=(new_value)
68
+ if new_value.is_a?(Hash)
69
+ @body = new_value.map {|k,v| "#{k}=#{v}" }.join "&"
70
+ else
71
+ @body = new_value
72
+ end
73
+ end
74
+
75
+ # Returns the <tt>HTTPI::Authentication</tt> object.
76
+ def auth
77
+ @auth ||= Auth::Config.new
78
+ end
79
+
80
+ # Returns whether any authentication credentials were specified.
81
+ def auth?
82
+ !!auth.type
83
+ end
84
+
85
+ # Expects a Hash of +args+ to assign.
86
+ def mass_assign(args)
87
+ ATTRIBUTES.each { |key| send("#{key}=", args[key]) if args[key] }
88
+ end
89
+
90
+ private
91
+
92
+ # Expects a +url+, validates its validity and returns a +URI+ object.
93
+ def normalize_url!(url)
94
+ raise ArgumentError, "Invalid URL: #{url}" unless url.to_s =~ /^http/
95
+ url.kind_of?(URI) ? url : URI(url)
96
+ end
97
+
98
+ end
99
+ end
@@ -0,0 +1,85 @@
1
+ require "zlib"
2
+ require "stringio"
3
+ require "cs-httpi/dime"
4
+ require "rack/utils"
5
+
6
+ module HTTPI
7
+
8
+ # = HTTPI::Response
9
+ #
10
+ # Represents an HTTP response and contains various response details.
11
+ class Response
12
+
13
+ # Range of HTTP response codes considered to be successful.
14
+ SuccessfulResponseCodes = 200..299
15
+
16
+ # Initializer expects an HTTP response +code+, +headers+ and +body+.
17
+ def initialize(code, headers, body)
18
+ self.code = code.to_i
19
+ self.headers = Rack::Utils::HeaderHash.new(headers)
20
+ self.raw_body = body
21
+ end
22
+
23
+ attr_accessor :code, :headers, :raw_body, :attachments
24
+
25
+ # Returns whether the HTTP response is considered successful.
26
+ def error?
27
+ !SuccessfulResponseCodes.include? code.to_i
28
+ end
29
+
30
+ # Returns whether the HTTP response is a multipart response.
31
+ def multipart?
32
+ !!(headers["Content-Type"] =~ /^multipart/i)
33
+ end
34
+
35
+ # Returns any DIME attachments.
36
+ def attachments
37
+ decode_body unless @body
38
+ @attachments ||= []
39
+ end
40
+
41
+ # Returns the HTTP response body.
42
+ def body
43
+ decode_body unless @body
44
+ @body
45
+ end
46
+
47
+ attr_writer :body
48
+
49
+ private
50
+
51
+ def decode_body
52
+ return @body = "" if !raw_body || raw_body.empty?
53
+
54
+ body = gzipped_response? ? decoded_gzip_body : raw_body
55
+ @body = dime_response? ? decoded_dime_body(body) : body
56
+ end
57
+
58
+ # Returns whether the response is gzipped.
59
+ def gzipped_response?
60
+ headers["Content-Encoding"] == "gzip" || raw_body[0..1] == "\x1f\x8b"
61
+ end
62
+
63
+ # Returns whether this is a DIME response.
64
+ def dime_response?
65
+ headers["Content-Type"] == "application/dime"
66
+ end
67
+
68
+ # Returns the gzip decoded response body.
69
+ def decoded_gzip_body
70
+ gzip = Zlib::GzipReader.new StringIO.new(raw_body)
71
+ raise ArgumentError.new "couldn't create gzip reader" unless gzip
72
+ gzip.read
73
+ ensure
74
+ gzip.close if gzip
75
+ end
76
+
77
+ # Returns the DIME decoded response body.
78
+ def decoded_dime_body(body = nil)
79
+ dime = Dime.new(body || raw_body)
80
+ self.attachments = dime.binary_records
81
+ dime.xml_records.first.data
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,5 @@
1
+ module HTTPI
2
+
3
+ VERSION = "0.9.5.1"
4
+
5
+ end
@@ -0,0 +1,2 @@
1
+ file.reference.cs-httpi-lib=/home/tino/work/github/cs-httpi/lib
2
+ file.reference.cs-httpi-spec=/home/tino/work/github/cs-httpi/spec
File without changes
@@ -0,0 +1,7 @@
1
+ file.reference.cs-httpi-lib=lib
2
+ file.reference.cs-httpi-spec=spec
3
+ main.file=
4
+ platform.active=Ruby
5
+ source.encoding=UTF-8
6
+ src.dir=${file.reference.cs-httpi-lib}
7
+ test.src.dir=${file.reference.cs-httpi-spec}
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project xmlns="http://www.netbeans.org/ns/project/1">
3
+ <type>org.netbeans.modules.ruby.rubyproject</type>
4
+ <configuration>
5
+ <data xmlns="http://www.netbeans.org/ns/ruby-project/1">
6
+ <name>cs-httpi</name>
7
+ <source-roots>
8
+ <root id="src.dir"/>
9
+ </source-roots>
10
+ <test-roots>
11
+ <root id="test.src.dir"/>
12
+ </test-roots>
13
+ </data>
14
+ </configuration>
15
+ </project>
@@ -0,0 +1,232 @@
1
+ require "spec_helper"
2
+ require "cs-httpi/adapter/curb"
3
+ require "cs-httpi/request"
4
+
5
+ # curb does not run on jruby
6
+ unless RUBY_PLATFORM =~ /java/
7
+ require "curb"
8
+
9
+ describe HTTPI::Adapter::Curb do
10
+ let(:adapter) { HTTPI::Adapter::Curb.new }
11
+ let(:curb) { Curl::Easy.any_instance }
12
+
13
+ describe "#get" do
14
+ before do
15
+ curb.expects(:http_get)
16
+ curb.expects(:response_code).returns(200)
17
+ curb.expects(:header_str).returns("Accept-encoding: utf-8")
18
+ curb.expects(:body_str).returns(Fixture.xml)
19
+ end
20
+
21
+ it "returns a valid HTTPI::Response" do
22
+ adapter.get(basic_request).should match_response(:body => Fixture.xml)
23
+ end
24
+ end
25
+
26
+ describe "#post" do
27
+ before do
28
+ curb.expects(:http_post)
29
+ curb.expects(:response_code).returns(200)
30
+ curb.expects(:header_str).returns("Accept-encoding: utf-8")
31
+ curb.expects(:body_str).returns(Fixture.xml)
32
+ end
33
+
34
+ it "returns a valid HTTPI::Response" do
35
+ adapter.post(basic_request).should match_response(:body => Fixture.xml)
36
+ end
37
+ end
38
+
39
+ describe "#post" do
40
+ it "sends the body in the request" do
41
+ curb.expects(:http_post).with('xml=hi&name=123')
42
+ adapter.post(basic_request { |request| request.body = 'xml=hi&name=123' } )
43
+ end
44
+ end
45
+
46
+ describe "#head" do
47
+ before do
48
+ curb.expects(:http_head)
49
+ curb.expects(:response_code).returns(200)
50
+ curb.expects(:header_str).returns("Accept-encoding: utf-8")
51
+ curb.expects(:body_str).returns(Fixture.xml)
52
+ end
53
+
54
+ it "returns a valid HTTPI::Response" do
55
+ adapter.head(basic_request).should match_response(:body => Fixture.xml)
56
+ end
57
+ end
58
+
59
+ describe "#put" do
60
+ before do
61
+ curb.expects(:http_put)
62
+ curb.expects(:response_code).returns(200)
63
+ curb.expects(:header_str).returns("Accept-encoding: utf-8")
64
+ curb.expects(:body_str).returns(Fixture.xml)
65
+ end
66
+
67
+ it "returns a valid HTTPI::Response" do
68
+ adapter.put(basic_request).should match_response(:body => Fixture.xml)
69
+ end
70
+ end
71
+
72
+ describe "#put" do
73
+ it "sends the body in the request" do
74
+ curb.expects(:http_put).with('xml=hi&name=123')
75
+ adapter.put(basic_request { |request| request.body = 'xml=hi&name=123' } )
76
+ end
77
+ end
78
+
79
+ describe "#delete" do
80
+ before do
81
+ curb.expects(:http_delete)
82
+ curb.expects(:response_code).returns(200)
83
+ curb.expects(:header_str).returns("Accept-encoding: utf-8")
84
+ curb.expects(:body_str).returns("")
85
+ end
86
+
87
+ it "returns a valid HTTPI::Response" do
88
+ adapter.delete(basic_request).should match_response(:body => "")
89
+ end
90
+ end
91
+
92
+ describe "settings:" do
93
+ before { curb.stubs(:http_get) }
94
+
95
+ describe "url" do
96
+ it "always sets the request url" do
97
+ curb.expects(:url=).with(basic_request.url.to_s)
98
+ adapter.get(basic_request)
99
+ end
100
+ end
101
+
102
+ describe "proxy_url" do
103
+ it "is not set unless it's specified" do
104
+ curb.expects(:proxy_url=).never
105
+ adapter.get(basic_request)
106
+ end
107
+
108
+ it "is set if specified" do
109
+ request = basic_request { |request| request.proxy = "http://proxy.example.com" }
110
+
111
+ curb.expects(:proxy_url=).with(request.proxy.to_s)
112
+ adapter.get(request)
113
+ end
114
+ end
115
+
116
+ describe "timeout" do
117
+ it "is not set unless it's specified" do
118
+ curb.expects(:timeout=).never
119
+ adapter.get(basic_request)
120
+ end
121
+
122
+ it "is set if specified" do
123
+ request = basic_request { |request| request.read_timeout = 30 }
124
+
125
+ curb.expects(:timeout=).with(30)
126
+ adapter.get(request)
127
+ end
128
+ end
129
+
130
+ describe "connect_timeout" do
131
+ it "is not set unless it's specified" do
132
+ curb.expects(:connect_timeout=).never
133
+ adapter.get(basic_request)
134
+ end
135
+
136
+ it "is set if specified" do
137
+ request = basic_request { |request| request.open_timeout = 30 }
138
+
139
+ curb.expects(:connect_timeout=).with(30)
140
+ adapter.get(request)
141
+ end
142
+ end
143
+
144
+ describe "headers" do
145
+ it "is always set" do
146
+ curb.expects(:headers=).with({})
147
+ adapter.get(basic_request)
148
+ end
149
+ end
150
+
151
+ describe "verbose" do
152
+ it "is always set to false" do
153
+ curb.expects(:verbose=).with(false)
154
+ adapter.get(basic_request)
155
+ end
156
+ end
157
+
158
+ describe "http_auth_types" do
159
+ it "is set to :basic for HTTP basic auth" do
160
+ request = basic_request { |request| request.auth.basic "username", "password" }
161
+
162
+ curb.expects(:http_auth_types=).with(:basic)
163
+ adapter.get(request)
164
+ end
165
+
166
+ it "is set to :digest for HTTP digest auth" do
167
+ request = basic_request { |request| request.auth.digest "username", "password" }
168
+
169
+ curb.expects(:http_auth_types=).with(:digest)
170
+ adapter.get(request)
171
+ end
172
+ end
173
+
174
+ describe "username and password" do
175
+ it "is set for HTTP basic auth" do
176
+ request = basic_request { |request| request.auth.basic "username", "password" }
177
+
178
+ curb.expects(:username=).with("username")
179
+ curb.expects(:password=).with("password")
180
+ adapter.get(request)
181
+ end
182
+
183
+ it "is set for HTTP digest auth" do
184
+ request = basic_request { |request| request.auth.digest "username", "password" }
185
+
186
+ curb.expects(:username=).with("username")
187
+ curb.expects(:password=).with("password")
188
+ adapter.get(request)
189
+ end
190
+ end
191
+
192
+ context "(for SSL client auth)" do
193
+ let(:ssl_auth_request) do
194
+ basic_request do |request|
195
+ request.auth.ssl.cert_key_file = "spec/fixtures/client_key.pem"
196
+ request.auth.ssl.cert_file = "spec/fixtures/client_cert.pem"
197
+ end
198
+ end
199
+
200
+ it "cert_key, cert and ssl_verify_peer should be set" do
201
+ curb.expects(:cert_key=).with(ssl_auth_request.auth.ssl.cert_key_file)
202
+ curb.expects(:cert=).with(ssl_auth_request.auth.ssl.cert_file)
203
+ curb.expects(:ssl_verify_peer=).with(true)
204
+ curb.expects(:certtype=).with(ssl_auth_request.auth.ssl.cert_type.to_s.upcase)
205
+
206
+ adapter.get(ssl_auth_request)
207
+ end
208
+
209
+ it "sets the cert_type to DER if specified" do
210
+ ssl_auth_request.auth.ssl.cert_type = :der
211
+ curb.expects(:certtype=).with(:der.to_s.upcase)
212
+
213
+ adapter.get(ssl_auth_request)
214
+ end
215
+
216
+ it "sets the cacert if specified" do
217
+ ssl_auth_request.auth.ssl.ca_cert_file = "spec/fixtures/client_cert.pem"
218
+ curb.expects(:cacert=).with(ssl_auth_request.auth.ssl.ca_cert_file)
219
+
220
+ adapter.get(ssl_auth_request)
221
+ end
222
+ end
223
+ end
224
+
225
+ def basic_request
226
+ request = HTTPI::Request.new :url => "http://example.com"
227
+ yield request if block_given?
228
+ request
229
+ end
230
+
231
+ end
232
+ end