fast_http 0.1.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.
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Copyright (c) 2005 Zed A. Shaw
3
+ * You can redistribute it and/or modify it under the same terms as Ruby.
4
+ */
5
+
6
+ #ifndef http11_parser_h
7
+ #define http11_parser_h
8
+
9
+ #include <sys/types.h>
10
+
11
+ #if defined(_WIN32)
12
+ #include <stddef.h>
13
+ #endif
14
+
15
+ typedef void (*element_cb)(void *data, const char *at, size_t length);
16
+ typedef void (*field_cb)(void *data, const char *field, size_t flen, const char *value, size_t vlen);
17
+
18
+ typedef struct httpclient_parser {
19
+ int cs;
20
+ size_t body_start;
21
+ int content_len;
22
+ size_t nread;
23
+ size_t mark;
24
+ size_t field_start;
25
+ size_t field_len;
26
+
27
+ void *data;
28
+
29
+ field_cb http_field;
30
+ element_cb reason_phrase;
31
+ element_cb status_code;
32
+ element_cb chunk_size;
33
+ element_cb http_version;
34
+ element_cb header_done;
35
+ element_cb last_chunk;
36
+
37
+
38
+ } httpclient_parser;
39
+
40
+ int httpclient_parser_init(httpclient_parser *parser);
41
+ int httpclient_parser_finish(httpclient_parser *parser);
42
+ size_t httpclient_parser_execute(httpclient_parser *parser, const char *data, size_t len, size_t off);
43
+ int httpclient_parser_has_error(httpclient_parser *parser);
44
+ int httpclient_parser_is_finished(httpclient_parser *parser);
45
+
46
+ #define httpclient_parser_nread(parser) (parser)->nread
47
+
48
+ #endif
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Copyright (c) 2005 Zed A. Shaw
3
+ * You can redistribute it and/or modify it under the same terms as Ruby.
4
+ */
5
+
6
+ #include "http11_parser.h"
7
+ #include <stdio.h>
8
+ #include <assert.h>
9
+ #include <stdlib.h>
10
+ #include <ctype.h>
11
+ #include <string.h>
12
+
13
+ #define LEN(AT, FPC) (FPC - buffer - parser->AT)
14
+ #define MARK(M,FPC) (parser->M = (FPC) - buffer)
15
+ #define PTR_TO(F) (buffer + parser->F)
16
+ #define L(M) fprintf(stderr, "" # M "\n");
17
+
18
+
19
+ /** machine **/
20
+ %%{
21
+ machine httpclient_parser;
22
+
23
+ action mark {MARK(mark, fpc); }
24
+
25
+ action start_field { MARK(field_start, fpc); }
26
+
27
+ action write_field {
28
+ parser->field_len = LEN(field_start, fpc);
29
+ }
30
+
31
+ action start_value { MARK(mark, fpc); }
32
+
33
+ action write_value {
34
+ parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, fpc));
35
+ }
36
+
37
+ action reason_phrase {
38
+ parser->reason_phrase(parser->data, PTR_TO(mark), LEN(mark, fpc));
39
+ }
40
+
41
+ action status_code {
42
+ parser->status_code(parser->data, PTR_TO(mark), LEN(mark, fpc));
43
+ }
44
+
45
+ action http_version {
46
+ parser->http_version(parser->data, PTR_TO(mark), LEN(mark, fpc));
47
+ }
48
+
49
+ action chunk_size {
50
+ parser->chunk_size(parser->data, PTR_TO(mark), LEN(mark, fpc));
51
+ }
52
+
53
+ action last_chunk {
54
+ parser->last_chunk(parser->data, NULL, 0);
55
+ }
56
+
57
+ action done {
58
+ parser->body_start = fpc - buffer + 1;
59
+ if(parser->header_done != NULL)
60
+ parser->header_done(parser->data, fpc + 1, pe - fpc - 1);
61
+ fbreak;
62
+ }
63
+
64
+ # line endings
65
+ CRLF = "\r\n";
66
+
67
+ # character types
68
+ CTL = (cntrl | 127);
69
+ tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
70
+
71
+ # elements
72
+ token = (ascii -- (CTL | tspecials));
73
+
74
+ Reason_Phrase = (any -- CRLF)+ >mark %reason_phrase;
75
+ Status_Code = digit+ >mark %status_code;
76
+ http_number = (digit+ "." digit+) ;
77
+ HTTP_Version = ("HTTP/" http_number) >mark %http_version ;
78
+ Status_Line = HTTP_Version " " Status_Code " " Reason_Phrase :> CRLF;
79
+
80
+ field_name = token+ >start_field %write_field;
81
+ field_value = any* >start_value %write_value;
82
+ message_header = field_name ":" " "* field_value :> CRLF;
83
+
84
+ Response = Status_Line (message_header)* (CRLF @done);
85
+
86
+ chunk_ext_val = token+;
87
+ chunk_ext_name = token+;
88
+ chunk_extension = (";" chunk_ext_name >start_field %write_field %start_value ("=" chunk_ext_val >start_value)? %write_value )*;
89
+ last_chunk = "0"? chunk_extension :> (CRLF @last_chunk @done);
90
+ chunk_size = xdigit+;
91
+ chunk = chunk_size >mark %chunk_size chunk_extension :> (CRLF @done);
92
+ Chunked_Header = (chunk | last_chunk);
93
+
94
+ main := Response | Chunked_Header;
95
+ }%%
96
+
97
+ /** Data **/
98
+ %% write data;
99
+
100
+ int httpclient_parser_init(httpclient_parser *parser) {
101
+ int cs = 0;
102
+ %% write init;
103
+ parser->cs = cs;
104
+ parser->body_start = 0;
105
+ parser->content_len = 0;
106
+ parser->mark = 0;
107
+ parser->nread = 0;
108
+ parser->field_len = 0;
109
+ parser->field_start = 0;
110
+
111
+ return(1);
112
+ }
113
+
114
+
115
+ /** exec **/
116
+ size_t httpclient_parser_execute(httpclient_parser *parser, const char *buffer, size_t len, size_t off) {
117
+ const char *p, *pe;
118
+ int cs = parser->cs;
119
+
120
+ assert(off <= len && "offset past end of buffer");
121
+
122
+ p = buffer+off;
123
+ pe = buffer+len;
124
+
125
+ assert(*pe == '\0' && "pointer does not end on NUL");
126
+ assert(pe - p == len - off && "pointers aren't same distance");
127
+
128
+
129
+ %% write exec;
130
+
131
+ parser->cs = cs;
132
+ parser->nread += p - (buffer + off);
133
+
134
+ assert(p <= pe && "buffer overflow after parsing execute");
135
+ assert(parser->nread <= len && "nread longer than length");
136
+ assert(parser->body_start <= len && "body starts after buffer end");
137
+ assert(parser->mark < len && "mark is after buffer end");
138
+ assert(parser->field_len <= len && "field has length longer than whole buffer");
139
+ assert(parser->field_start < len && "field starts after buffer end");
140
+
141
+ if(parser->body_start) {
142
+ /* final \r\n combo encountered so stop right here */
143
+ %%write eof;
144
+ parser->nread++;
145
+ }
146
+
147
+ return(parser->nread);
148
+ }
149
+
150
+ int httpclient_parser_finish(httpclient_parser *parser)
151
+ {
152
+ int cs = parser->cs;
153
+
154
+ %%write eof;
155
+
156
+ parser->cs = cs;
157
+
158
+ if (httpclient_parser_has_error(parser) ) {
159
+ return -1;
160
+ } else if (httpclient_parser_is_finished(parser) ) {
161
+ return 1;
162
+ } else {
163
+ return 0;
164
+ }
165
+ }
166
+
167
+ int httpclient_parser_has_error(httpclient_parser *parser) {
168
+ return parser->cs == httpclient_parser_error;
169
+ }
170
+
171
+ int httpclient_parser_is_finished(httpclient_parser *parser) {
172
+ return parser->cs == httpclient_parser_first_final;
173
+ }
@@ -0,0 +1,12 @@
1
+ have_library: checking for main() in -lc... -------------------- yes
2
+
3
+ "gcc -o conftest -I. -I/usr/local/lib/ruby/1.8/i686-darwin9.8.0 -I. -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -g -O2 -pipe -fno-common conftest.c -L. -L/usr/local/lib -L. -lruby-static -lc -ldl -lobjc "
4
+ checked program was:
5
+ /* begin */
6
+ 1: /*top*/
7
+ 2: int main() { return 0; }
8
+ 3: int t() { void ((*volatile p)()); p = (void ((*)()))main; return 0; }
9
+ /* end */
10
+
11
+ --------------------
12
+
@@ -0,0 +1,3 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. ext http11_client http11_client])
2
+ require File.join(File.dirname(__FILE__), *%w[fast_http client])
3
+ require File.join(File.dirname(__FILE__), *%w[fast_http pushbackio])
@@ -0,0 +1,441 @@
1
+ require 'socket'
2
+ require 'timeout'
3
+
4
+ module FastHttp
5
+
6
+ # Thrown for errors not related to the protocol format (HttpClientParserError are
7
+ # thrown for that).
8
+ class HttpClientError < StandardError; end
9
+
10
+ # A simple hash is returned for each request made by HttpClient with
11
+ # the headers that were given by the server for that request.
12
+ class HttpResponse < Hash
13
+ # The reason returned in the http response ("OK","File not found",etc.)
14
+ attr_accessor :http_reason
15
+
16
+ # The HTTP version returned.
17
+ attr_accessor :http_version
18
+
19
+ # The status code (as a string!)
20
+ attr_accessor :http_status
21
+
22
+ # The http body of the response, in the raw
23
+ attr_accessor :http_body
24
+
25
+ # When parsing chunked encodings this is set
26
+ attr_accessor :http_chunk_size
27
+
28
+ # The actual chunks taken from the chunked encoding
29
+ attr_accessor :raw_chunks
30
+
31
+ # Converts the http_chunk_size string properly
32
+ def chunk_size
33
+ if @chunk_size == nil
34
+ @chunk_size = @http_chunk_size ? @http_chunk_size.to_i(base=16) : 0
35
+ end
36
+
37
+ @chunk_size
38
+ end
39
+
40
+ # true if this is the last chunk, nil otherwise (false)
41
+ def last_chunk?
42
+ @last_chunk || chunk_size == 0
43
+ end
44
+
45
+ # Easier way to find out if this is a chunked encoding
46
+ def chunked_encoding?
47
+ /chunked/i === self[HttpClient::TRANSFER_ENCODING]
48
+ end
49
+ end
50
+
51
+ # A mixin that has most of the HTTP encoding methods you need to work
52
+ # with the protocol. It's used by HttpClient, but you can use it
53
+ # as well.
54
+ module HttpEncoding
55
+ COOKIE="Cookie"
56
+ FIELD_ENCODING="%s: %s\r\n"
57
+
58
+ # Converts a Hash of cookies to the appropriate simple cookie
59
+ # headers.
60
+ def encode_cookies(cookies)
61
+ result = ""
62
+ cookies.each do |k,v|
63
+ if v.kind_of? Array
64
+ v.each {|x| result << encode_field(COOKIE, encode_param(k,x)) }
65
+ else
66
+ result << encode_field(COOKIE, encode_param(k,v))
67
+ end
68
+ end
69
+ return result
70
+ end
71
+
72
+ # Encode HTTP header fields of "k: v\r\n"
73
+ def encode_field(k,v)
74
+ FIELD_ENCODING % [k,v]
75
+ end
76
+
77
+ # Encodes the headers given in the hash returning a string
78
+ # you can use.
79
+ def encode_headers(head)
80
+ @headers_cache = {}
81
+ @headers_cache[head] ||
82
+ result = ""
83
+ head.each do |k,v|
84
+ if v.kind_of? Array
85
+ v.each {|x| result << encode_field(k,x) }
86
+ else
87
+ result << encode_field(k,v)
88
+ end
89
+ end
90
+ return result
91
+ end
92
+
93
+ # URL encodes a single k=v parameter.
94
+ def encode_param(k,v)
95
+ escape(k) + "=" + escape(v)
96
+ end
97
+
98
+ # Takes a query string and encodes it as a URL encoded
99
+ # set of key=value pairs with & separating them.
100
+ def encode_query(uri, query)
101
+ params = []
102
+
103
+ if query
104
+ query.each do |k,v|
105
+ if v.kind_of? Array
106
+ v.each {|x| params << encode_param(k,x) }
107
+ else
108
+ params << encode_param(k,v)
109
+ end
110
+ end
111
+
112
+ uri += "?" + params.join('&')
113
+ end
114
+
115
+ return uri
116
+ end
117
+
118
+ # HTTP is kind of retarded that you have to specify
119
+ # a Host header, but if you include port 80 then further
120
+ # redirects will tack on the :80 which is annoying.
121
+ def encode_host(host, port)
122
+ host + (port.to_i != 80 ? ":#{port}" : "")
123
+ end
124
+
125
+ # Escapes a URI.
126
+ def escape(s)
127
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
128
+ '%'+$1.unpack('H2'*$1.size).join('%').upcase
129
+ }.tr(' ', '+')
130
+ end
131
+
132
+
133
+ # Unescapes a URI escaped string.
134
+ def unescape(s)
135
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
136
+ [$1.delete('%')].pack('H*')
137
+ }
138
+ end
139
+
140
+ # Parses a query string by breaking it up at the '&'
141
+ # and ';' characters. You can also use this to parse
142
+ # cookies by changing the characters used in the second
143
+ # parameter (which defaults to '&;'.
144
+ def query_parse(qs, d = '&;')
145
+ params = {}
146
+ (qs||'').split(/[#{d}] */n).inject(params) { |h,p|
147
+ k, v=unescape(p).split('=',2)
148
+ if cur = params[k]
149
+ if cur.class == Array
150
+ params[k] << v
151
+ else
152
+ params[k] = [cur, v]
153
+ end
154
+ else
155
+ params[k] = v
156
+ end
157
+ }
158
+
159
+ return params
160
+ end
161
+ end
162
+
163
+
164
+ # The actual HttpClient that does the work with the thinnest
165
+ # layer between you and the protocol. All exceptions and leaks
166
+ # are allowed to pass through since those are important when
167
+ # testing. It doesn't pretend to be a full client, but instead
168
+ # is just enough client to track cookies, form proper HTTP requests,
169
+ # and return HttpResponse hashes with the results.
170
+ #
171
+ # It's designed so that you create one client, and then you work it
172
+ # with a minimum of parameters as you need. The initialize method
173
+ # lets you pass in defaults for most of the parameters you'll need,
174
+ # and you can simple call the method you want and it'll be translated
175
+ # to an HTTP method (client.get => GET, client.foobar = FOOBAR).
176
+ #
177
+ # Here's a few examples:
178
+ #
179
+ # client = HttpClient.new(:head => {"X-DefaultHeader" => "ONE"})
180
+ # resp = client.post("/test")
181
+ # resp = client.post("/test", :head => {"X-TestSend" => "Status"}, :body => "TEST BODY")
182
+ # resp = client.put("/testput", :query => {"q" => "test"}, :body => "SOME JUNK")
183
+ # client.reset
184
+ #
185
+ # The HttpClient.reset call clears cookies that are maintained.
186
+ #
187
+ # It uses method_missing to do the translation of .put to "PUT /testput HTTP/1.1"
188
+ # so you can get into trouble if you're calling unknown methods on it. By
189
+ # default the methods are PUT, GET, POST, DELETE, HEAD. You can change
190
+ # the allowed methods by passing :allowed_methods => [:put, :get, ..] to
191
+ # the initialize for the object.
192
+ #
193
+ # == Notifications
194
+ #
195
+ # You can register a "notifier" with the client that will get called when
196
+ # different events happen. Right now the Notifier class just has a few
197
+ # functions for the common parts of an HTTP request that each take a
198
+ # symbol and some extra parameters. See FastHttp::Notifier for more
199
+ # information.
200
+ #
201
+ # == Parameters
202
+ #
203
+ # :head => {K => V} or {K => [V1,V2]}
204
+ # :query => {K => V} or {K => [V1,V2]}
205
+ # :body => "some body" (you must encode for now)
206
+ # :cookies => {K => V} or {K => [V1, V2]}
207
+ # :allowed_methods => [:put, :get, :post, :delete, :head]
208
+ # :notifier => Notifier.new
209
+ # :redirect => false (give it a number and it'll follow redirects for that count)
210
+ #
211
+ class HttpClient
212
+ include HttpEncoding
213
+
214
+ TRANSFER_ENCODING="TRANSFER_ENCODING"
215
+ CONTENT_LENGTH="CONTENT_LENGTH"
216
+ SET_COOKIE="SET_COOKIE"
217
+ LOCATION="LOCATION"
218
+ HOST="HOST"
219
+ HTTP_REQUEST_HEADER="%s %s HTTP/1.1\r\n"
220
+ REQ_CONTENT_LENGTH="Content-Length"
221
+ REQ_HOST="Host"
222
+ CHUNK_SIZE=1024 * 16
223
+ CRLF="\r\n"
224
+
225
+ # Access to the host, port, default options, and cookies currently in play
226
+ attr_accessor :host, :port, :options, :cookies, :allowed_methods, :sock
227
+
228
+ # Doesn't make the connect until you actually call a .put,.get, etc.
229
+ def initialize(host, port, options = {})
230
+ @options = options
231
+ @host = host
232
+ @port = port
233
+ @cookies = options[:cookies]
234
+ @allowed_methods = options[:allowed_methods] || [:put, :get, :post, :delete, :head]
235
+ @redirect = options[:redirect] || false
236
+ @parser = HttpClientParser.new
237
+ @ignore_data = options[:ignore_data]
238
+ end
239
+
240
+
241
+ # Builds a full request from the method, uri, req, and @cookies
242
+ # using the default @options and writes it to out (should be an IO).
243
+ def build_request(out, method, uri, req)
244
+ ops = @options.merge(req)
245
+ query = ops[:query]
246
+
247
+ # merge head differently since that's typically what they mean
248
+ head = req[:head] || {}
249
+ head = ops[:head].merge(head) if ops[:head]
250
+
251
+ # setup basic headers we always need
252
+ head[REQ_HOST] = encode_host(@host,@port)
253
+ head[REQ_CONTENT_LENGTH] = ops[:body] ? ops[:body].length : 0
254
+
255
+ # blast it out
256
+ out.write(HTTP_REQUEST_HEADER % [method, encode_query(uri,query)])
257
+ out.write(encode_headers(head))
258
+ if @cookies
259
+ out.write(encode_cookies(@cookies.merge(req[:cookies] || {})))
260
+ elsif req[:cookies]
261
+ out.write(encode_cookies(req[:cookies]))
262
+ end
263
+ out.write(CRLF)
264
+ end
265
+
266
+ # Does the read operations needed to parse a header with the @parser.
267
+ # A "header" in this case is either an HTTP header or a Chunked encoding
268
+ # header (since the @parser handles both).
269
+ def read_parsed_header
270
+ @parser.reset
271
+ resp = HttpResponse.new
272
+ data = @sock.read(CHUNK_SIZE, partial=true)
273
+ nread = @parser.execute(resp, data, 0)
274
+
275
+ while !@parser.finished?
276
+ data << @sock.read(CHUNK_SIZE, partial=true)
277
+ nread = @parser.execute(resp, data, nread)
278
+ end
279
+
280
+ return resp
281
+ end
282
+
283
+
284
+ # Used to process chunked headers and then read up their bodies.
285
+ def read_chunked_header
286
+ resp = read_parsed_header
287
+ @sock.push(resp.http_body)
288
+
289
+ if !resp.last_chunk?
290
+ resp.http_body = @sock.read(resp.chunk_size)
291
+
292
+ trail = @sock.read(2)
293
+ if trail != CRLF
294
+ raise HttpClientParserError.new("Chunk ended in #{trail.inspect} not #{CRLF.inspect}")
295
+ end
296
+ end
297
+
298
+ return resp
299
+ end
300
+
301
+
302
+ # Collects up a chunked body both collecting the body together *and*
303
+ # collecting the chunks into HttpResponse.raw_chunks[] for alternative
304
+ # analysis.
305
+ def read_chunked_body(header)
306
+ @sock.push(header.http_body)
307
+ header.http_body = ""
308
+ header.raw_chunks = []
309
+
310
+ while true
311
+ chunk = read_chunked_header
312
+ header.raw_chunks << chunk unless @ignore_data
313
+ if !chunk.last_chunk?
314
+ header.http_body << chunk.http_body unless @ignore_data
315
+ else
316
+ break # last chunk, done
317
+ end
318
+ end
319
+
320
+ header
321
+ end
322
+
323
+ # Reads the SET_COOKIE string out of resp and translates it into
324
+ # the @cookies store for this HttpClient.
325
+ def store_cookies(resp)
326
+ if @cookies and resp[SET_COOKIE]
327
+ cookies = query_parse(resp[SET_COOKIE], ';')
328
+ @cookies.merge! cookies
329
+ @cookies.delete "path"
330
+ end
331
+ end
332
+
333
+ # Reads an HTTP response from the given socket. It uses
334
+ # readpartial which only appeared in Ruby 1.8.4. The result
335
+ # is a fully formed HttpResponse object for you to play with.
336
+ #
337
+ # As with other methods in this class it doesn't stop any exceptions
338
+ # from reaching your code. It's for experts who want these exceptions
339
+ # so either write a wrapper, use net/http, or deal with it on your end.
340
+ def read_response
341
+ resp = HttpResponse.new
342
+
343
+ resp = read_parsed_header
344
+
345
+ if resp.chunked_encoding?
346
+ read_chunked_body(resp)
347
+ elsif resp[CONTENT_LENGTH]
348
+ needs = resp[CONTENT_LENGTH].to_i - resp.http_body.length
349
+ # Some requests can actually give a content length, and then not have content
350
+ # so we ignore HttpClientError exceptions and pray that's good enough
351
+ if @ignore_data
352
+ @sock.read(needs) if needs > 0 rescue HttpClientError
353
+ else
354
+ resp.http_body += @sock.read(needs) if needs > 0 rescue HttpClientError
355
+ end
356
+ else
357
+ while true
358
+ begin
359
+ if @ignore_data
360
+ @sock.read(CHUNK_SIZE, partial=true)
361
+ else
362
+ resp.http_body += @sock.read(CHUNK_SIZE, partial=true)
363
+ end
364
+ rescue HttpClientError
365
+ break # this is fine, they closed the socket then
366
+ end
367
+ end
368
+ end
369
+
370
+ store_cookies(resp) if @cookies
371
+ return resp
372
+ end
373
+
374
+ # Does the socket connect and then build_request, read_response
375
+ # calls finally returning the result.
376
+ def send_request(method, uri, req)
377
+ begin
378
+ @sock = PushBackIO.new(TCPSocket.new(@host, @port))
379
+
380
+ out = StringIO.new
381
+ build_request(out, method, uri, req)
382
+ body = req[:body] || "" unless method == :head or method == :get
383
+
384
+ @sock.write(out.string + body)
385
+ @sock.flush
386
+
387
+ return read_response
388
+ rescue Object
389
+ raise $!
390
+ ensure
391
+ if @sock
392
+ @sock.close
393
+ end
394
+ end
395
+ end
396
+
397
+
398
+ # Translates unknown function calls into PUT, GET, POST, DELETE, HEAD
399
+ # methods. The allowed HTTP methods allowed are restricted by the
400
+ # @allowed_methods attribute which you can set after construction or
401
+ # during construction with :allowed_methods => [:put, :get, ...]
402
+ def method_missing(symbol, *args)
403
+ if @allowed_methods.include? symbol
404
+ method = symbol.to_s.upcase
405
+ resp = send_request(method, args[0], args[1] || {})
406
+ resp = redirect(symbol, resp) if @redirect
407
+
408
+ return resp
409
+ else
410
+ raise HttpClientError.new("Invalid method: #{symbol}")
411
+ end
412
+ end
413
+
414
+ # Keeps doing requests until it doesn't receive a 3XX request.
415
+ def redirect(method, resp, *args)
416
+ @redirect.times do
417
+ break if resp.http_status.index("3") != 0
418
+
419
+ host = encode_host(@host,@port)
420
+ location = resp[LOCATION]
421
+
422
+ if location.index(host) == 0
423
+ # begins with the host so strip that off
424
+ location = location[host.length .. -1]
425
+ end
426
+
427
+ resp = self.send(method, location, *args)
428
+ end
429
+
430
+ return resp
431
+ end
432
+
433
+ # Clears out the cookies in use so far in order to get
434
+ # a clean slate.
435
+ def reset
436
+ @cookies.clear
437
+ end
438
+ end
439
+
440
+ end
441
+