cs-httpi 0.9.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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