root/trunk/lib/protocols/httpcli2.rb

Revision 788, 21.5 kB (checked in by raggi, 8 months ago)

Merge of branches/raggi
Most notable work and patches by Aman Gupta, Roger Pack, and James Tucker.
Patches / Tickets also submitted by: Jeremy Evans, aanand, darix, mmmurf,
danielaquino, macournoyer.

  • Moved docs into docs/ dir
  • Major refactor of rakefile, added generic rakefile helpers in tasks
  • Added example CPP build rakefile in tasks/cpp.rake
  • Moved rake tests out to tasks/tests.rake
  • Added svn ignores where appropriate
  • Fixed jruby build on older java platforms
  • Gem now builds from Rakefile rather than directly via extconf
  • Gem unified for jruby, C++ and pure ruby.
  • Correction for pure C++ build, removing ruby dependency
  • Fix for CYGWIN builds on ipv6
  • Major refactor for extconf.rb
  • Working mingw builds
  • extconf optionally uses pkg_config over manual configuration
  • extconf builds for 1.9 on any system that has 1.9
  • extconf no longer links pthread explicitly
  • looks for kqueue on all *nix systems
  • better error output on std::runtime_error, now says where it came from
  • Fixed some tests on jruby
  • Added test for general send_data flaw, required for a bugfix in jruby build
  • Added timeout to epoll tests
  • Added fixes for java reactor ruby api
  • Small addition of some docs in httpclient.rb and httpcli2.rb
  • Some refactor and fixes in smtpserver.rb
  • Added parenthesis where possible to avoid excess ruby warnings
  • Refactor of $eventmachine_library logic for accuracy and maintenance, jruby
  • EM::start_server now supports unix sockets
  • EM::connect now supports unix sockets
  • EM::defer @threadqueue now handled more gracefully
  • Added better messages on exceptions raised
  • Fix edge case in timer fires
  • Explicitly require buftok.rb
  • Add protocols to autoload, rather than require them all immediately
  • Fix a bug in pr_eventmachine for outbound_q
  • Refactors to take some of the use of defer out of tests.
  • Fixes in EM.defer under start/stop conditions. Reduced scope of threads.
  • Property svn:keywords set to Id
Line 
1 # $Id$
2 #
3 # Author:: Francis Cianfrocca (gmail: blackhedd)
4 # Homepage::  http://rubyeventmachine.com
5 # Date:: 16 July 2006
6 #
7 # See EventMachine and EventMachine::Connection for documentation and
8 # usage examples.
9 #
10 #----------------------------------------------------------------------------
11 #
12 # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13 # Gmail: blackhedd
14 #
15 # This program is free software; you can redistribute it and/or modify
16 # it under the terms of either: 1) the GNU General Public License
17 # as published by the Free Software Foundation; either version 2 of the
18 # License, or (at your option) any later version; or 2) Ruby's License.
19 #
20 # See the file COPYING for complete licensing information.
21 #
22 #---------------------------------------------------------------------------
23 #
24 #
25
26
27
28 module EventMachine
29 module Protocols
30
31   # = Example
32   #
33  
34   #  EM.run{
35   #    include EM::Protocols
36   #    conn = HttpClient2.connect 'google.com', 80
37  
38   #    req = conn.get('/')
39   #    req.callback{
40   #      p(req.content)
41   #    }
42         class HttpClient2 < Connection
43                 include LineText2
44
45
46                 class Request
47                         include Deferrable
48
49                         attr_reader :version
50                         attr_reader :status
51                         attr_reader :header_lines
52                         attr_reader :headers
53                         attr_reader :content
54                         attr_reader :internal_error
55
56                         def initialize conn, args
57                                 @conn = conn
58                                 @args = args
59                                 @header_lines = []
60                                 @headers = {}
61                                 @blanks = 0
62                         end
63
64                         def send_request
65                                 az = @args[:authorization] and az = "Authorization: #{az}\r\n"
66
67                                 r = [
68                                         "#{@args[:verb]} #{@args[:uri]} HTTP/#{@args[:version] || "1.1"}\r\n",
69                                         "Host: #{@args[:host_header] || "_"}\r\n",
70                                         az || "",
71                                         "\r\n"
72                                 ]
73                                 @conn.send_data r.join
74                         end
75
76
77                         #--
78                         #
79                         def receive_line ln
80                                 if @chunk_trailer
81                                         receive_chunk_trailer(ln)
82                                 elsif @chunking
83                                         receive_chunk_header(ln)
84                                 else
85                                         receive_header_line(ln)
86                                 end
87                         end
88
89                         #--
90                         #
91                         def receive_chunk_trailer ln
92                                 if ln.length == 0
93                                         @conn.pop_request
94                                         succeed
95                                 else
96                                         p "Received chunk trailer line"
97                                 end
98                         end
99
100                         #--
101                         # Allow up to ten blank lines before we get a real response line.
102                         # Allow no more than 100 lines in the header.
103                         #
104                         def receive_header_line ln
105                                 if ln.length == 0
106                                         if @header_lines.length > 0
107                                                 process_header
108                                         else
109                                                 @blanks += 1
110                                                 if @blanks > 10
111                                                         @conn.close_connection
112                                                 end
113                                         end
114                                 else
115                                         @header_lines << ln
116                                         if @header_lines.length > 100
117                                                 @internal_error = :bad_header
118                                                 @conn.close_connection
119                                         end
120                                 end
121                         end
122
123                         #--
124                         # Cf RFC 2616 pgh 3.6.1 for the format of HTTP chunks.
125                         #
126                         def receive_chunk_header ln
127                                 if ln.length > 0
128                                         chunksize = ln.to_i(16)
129                                         if chunksize > 0
130                                                 @conn.set_text_mode(ln.to_i(16))
131                                         else
132                                                 @content = @content.join
133                                                 @chunk_trailer = true
134                                         end
135                                 else
136                                         # We correctly come here after each chunk gets read.
137                                         p "Got A BLANK chunk line"
138                                 end
139
140                         end
141
142
143                         #--
144                         # We get a single chunk. Append it to the incoming content and switch back to line mode.
145                         #
146                         def receive_chunked_text text
147                                 p "RECEIVED #{text.length} CHUNK"
148                                 (@content ||= []) << text
149                         end
150
151
152                         #--
153                         # TODO, inefficient how we're handling this. Part of it is done so as to
154                         # make sure we don't have problems in detecting chunked-encoding, content-length,
155                         # etc.
156                         #
157                         #
158                         HttpResponseRE = /\AHTTP\/(1.[01]) ([\d]{3})/i
159                         ClenRE = /\AContent-length:\s*(\d+)/i
160                         ChunkedRE = /\ATransfer-encoding:\s*chunked/i
161                         ColonRE = /\:\s*/
162
163                         def process_header
164                                 unless @header_lines.first =~ HttpResponseRE
165                                         @conn.close_connection
166                                         @internal_error = :bad_request
167                                 end
168                                 @version = $1.dup
169                                 @status = $2.dup.to_i
170
171                                 clen = nil
172                                 chunks = nil
173                                 @header_lines.each_with_index do |e,ix|
174                                         if ix > 0
175                                                 hdr,val = e.split(ColonRE,2)
176                                                 (@headers[hdr.downcase] ||= []) << val
177                                         end
178
179                                         if clen == nil and e =~ ClenRE
180                                                 clen = $1.dup.to_i
181                                         end
182                                         if e =~ ChunkedRE
183                                                 chunks = true
184                                         end
185                                 end
186
187                                 if clen
188                                         @conn.set_text_mode clen
189                                 elsif chunks
190                                         @chunking = true
191                                 else
192                                         # Chunked transfer, multipart, or end-of-connection.
193                                         # For end-of-connection, we need to go the unbind
194                                         # method and suppress its desire to fail us.
195                                         p "NO CLEN"
196                                         p @args[:uri]
197                                         p @header_lines
198                                         @internal_error = :unsupported_clen
199                                         @conn.close_connection
200                                 end
201                         end
202                         private :process_header
203
204
205                         def receive_text text
206                                 @chunking ? receive_chunked_text(text) : receive_sized_text(text)
207                         end
208
209                         #--
210                         # At the present time, we only handle contents that have a length
211                         # specified by the content-length header.
212                         #
213                         def receive_sized_text text
214                                 @content = text
215                                 @conn.pop_request
216                                 succeed
217                         end
218                 end
219
220                 # Make a connection to a remote HTTP server.
221                 # Can take either a pair of arguments (which will be interpreted as
222                 # a hostname/ip-address and a port), or a hash.
223                 # If the arguments are a hash, then supported values include:
224                 #  :host => a hostname or ip-address;
225                 #  :port => a port number
226                 #--
227                 # TODO, support optional encryption arguments like :ssl
228                 def self.connect *args
229                         if args.length == 2
230                                 args = {:host=>args[0], :port=>args[1]}
231                         else
232                                 args = args.first
233                         end
234
235                         h,prt,ssl = args[:host], Integer(args[:port]), (args[:tls] || args[:ssl])
236                         conn = EM.connect( h, prt, self )
237                         # TODO, start_tls if necessary
238                         conn.set_default_host_header( h, prt, ssl )
239                         conn
240                 end
241
242
243                 #--
244                 # Compute and remember a string to be used as the host header in HTTP requests
245                 # unless the user overrides it with an argument to #request.
246                 #
247                 def set_default_host_header host, port, ssl
248                         if (ssl and port != 443) or (!ssl and port != 80)
249                                 @host_header = "#{host}:#{port}"
250                         else
251                                 @host_header = host
252                         end
253                 end
254
255
256                 def post_init
257                         super
258                         @connected = EM::DefaultDeferrable.new
259                 end
260
261                 def connection_completed
262                         super
263                         @connected.succeed
264                 end
265
266                 #--
267                 # All pending requests, if any, must fail.
268                 # We might come here without ever passing through connection_completed
269                 # in case we can't connect to the server. We'll also get here when the
270                 # connection closes (either because the server closes it, or we close it
271                 # due to detecting an internal error or security violation).
272                 # In either case, run down all pending requests, if any, and signal failure
273                 # on them.
274                 #
275                 # Set and remember a flag (@closed) so we can immediately fail any
276                 # subsequent requests.
277                 #
278                 def unbind
279                         super
280                         @closed = true
281                         (@requests || []).each {|r| r.fail}
282                 end
283
284
285                 def get args
286                         if args.is_a?(String)
287                                 args = {:uri=>args}
288                         end
289                         args[:verb] = "GET"
290                         request args
291                 end
292
293                 def post args
294                         if args.is_a?(String)
295                                 args = {:uri=>args}
296                         end
297                         args[:verb] = "POST"
298                         request args
299                 end
300
301                 def request args
302                         args[:host_header] = @host_header unless args.has_key?(:host_header)
303                         args[:authorization] = @authorization unless args.has_key?(:authorization)
304                         r = Request.new self, args
305                         if @closed
306                                 r.fail
307                         else
308                                 (@requests ||= []).unshift r
309                                 @connected.callback {r.send_request}
310                         end
311                         r
312                 end
313
314                 def receive_line ln
315                         if req = @requests.last
316                                 req.receive_line ln
317                         else
318                                 p "??????????"
319                                 p ln
320                         end
321
322                 end
323                 def receive_binary_data text
324                         @requests.last.receive_text text
325                 end
326
327                 #--
328                 # Called by a Request object when it completes.
329                 #
330                 def pop_request
331                         @requests.pop
332                 end
333         end
334
335
336 =begin
337         class HttpClient2x < Connection
338                 include LineText2
339
340                 # TODO: Make this behave appropriate in case a #connect fails.
341                 # Currently, this produces no errors.
342
343                 # Make a connection to a remote HTTP server.
344                 # Can take either a pair of arguments (which will be interpreted as
345                 # a hostname/ip-address and a port), or a hash.
346                 # If the arguments are a hash, then supported values include:
347                 #  :host => a hostname or ip-address;
348                 #  :port => a port number
349                 #--
350                 # TODO, support optional encryption arguments like :ssl
351                 def self.connect *args
352                         if args.length == 2
353                                 args = {:host=>args[0], :port=>args[1]}
354                         else
355                                 args = args.first
356                         end
357
358                         h,prt = args[:host],Integer(args[:port])
359                         EM.connect( h, prt, self, h, prt )
360                 end
361
362
363                 #--
364                 # Sugars a connection that makes a single request and then
365                 # closes the connection. Matches the behavior and the arguments
366                 # of the original implementation of class HttpClient.
367                 #
368                 # Intended primarily for back compatibility, but the idiom
369                 # is probably useful so it's not deprecated.
370                 # We return a Deferrable, as did the original implementation.
371                 #
372                 # Because we're improving the way we deal with errors and exceptions
373                 # (specifically, HTTP response codes other than 2xx will trigger the
374                 # errback rather than the callback), this may break some existing code.
375                 #
376                 def self.request args
377                         c = connect args
378                 end
379
380                 #--
381                 # Requests can be pipelined. When we get a request, add it to the
382                 # front of a queue as an array. The last element of the @requests
383                 # array is always the oldest request received. Each element of the
384                 # @requests array is a two-element array consisting of a hash with
385                 # the original caller's arguments, and an initially-empty Ostruct
386                 # containing the data we retrieve from the server's response.
387                 # Maintain the instance variable @current_response, which is the response
388                 # of the oldest pending request. That's just to make other code a little
389                 # easier. If the variable doesn't exist when we come here, we're
390                 # obviously the first request being made on the connection.
391                 #
392                 # The reason for keeping this method private (and requiring use of the
393                 # convenience methods #get, #post, #head, etc) is to avoid the small
394                 # performance penalty of canonicalizing the verb.
395                 #
396                 def request args
397                         d = EventMachine::DefaultDeferrable.new
398
399                         if @closed
400                                 d.fail
401                                 return d
402                         end
403
404                         o = OpenStruct.new
405                         o.deferrable = d
406                         (@requests ||= []).unshift [args, o]
407                         @current_response ||= @requests.last.last
408                         @connected.callback {
409                                 az = args[:authorization] and az = "Authorization: #{az}\r\n"
410
411                                 r = [
412                                         "#{args[:verb]} #{args[:uri]} HTTP/#{args[:version] || "1.1"}\r\n",
413                                         "Host: #{args[:host_header] || @host_header}\r\n",
414                                         az || "",
415                                         "\r\n"
416                                 ]
417                                 p r
418                                 send_data r.join
419                         }
420                         o.deferrable
421                 end
422                 private :request
423
424                 def get args
425                         if args.is_a?(String)
426                                 args = {:uri=>args}
427                         end
428                         args[:verb] = "GET"
429                         request args
430                 end
431
432                 def initialize host, port
433                         super
434                         @host_header = "#{host}:#{port}"
435                 end
436                 def post_init
437                         super
438                         @connected = EM::DefaultDeferrable.new
439                 end
440
441
442                 def connection_completed
443                         super
444                         @connected.succeed
445                 end
446
447                 #--
448                 # Make sure to throw away any leftover incoming data if we've
449                 # been closed due to recognizing an error.
450                 #
451                 # Generate an internal error if we get an unreasonable number of
452                 # header lines. It could be malicious.
453                 #
454                 def receive_line ln
455                         p ln
456                         return if @closed
457
458                         if ln.length > 0
459                                 (@current_response.headers ||= []).push ln
460                                 abort_connection if @current_response.headers.length > 100
461                         else
462                                 process_received_headers
463                         end
464                 end
465
466                 #--
467                 # We come here when we've seen all the headers for a particular request.
468                 # What we do next depends on the response line (which should be the
469                 # first line in the header set), and whether there is content to read.
470                 # We may transition into a text-reading state to read content, or
471                 # we may abort the connection, or we may go right back into parsing
472                 # responses for the next response in the chain.
473                 #
474                 # We make an ASSUMPTION that the first line is an HTTP response.
475                 # Anything else produces an error that aborts the connection.
476                 # This may not be enough, because it may be that responses to pipelined
477                 # requests will come with a blank-line delimiter.
478                 #
479                 # Any non-2xx response will be treated as a fatal error, and abort the
480                 # connection. We will set up the status and other response parameters.
481                 # TODO: we will want to properly support 1xx responses, which some versions
482                 # of IIS copiously generate.
483                 # TODO: We need to give the option of not aborting the connection with certain
484                 # non-200 responses, in order to work with NTLM and other authentication
485                 # schemes that work at the level of individual connections.
486                 #
487                 # Some error responses will get sugarings. For example, we'll return the
488                 # Location header in the response in case of a 301/302 response.
489                 #
490                 # Possible dispositions here:
491                 # 1) No content to read (either content-length is zero or it's a HEAD request);
492                 # 2) Switch to text mode to read a specific number of bytes;
493                 # 3) Read a chunked or multipart response;
494                 # 4) Read till the server closes the connection.
495                 #
496                 # Our reponse to the client can be either to wait till all the content
497                 # has been read and then to signal caller's deferrable, or else to signal
498                 # it when we finish the processing the headers and then expect the caller
499                 # to have given us a block to call as the content comes in. And of course
500                 # the latter gets stickier with chunks and multiparts.
501                 #
502                 HttpResponseRE = /\AHTTP\/(1.[01]) ([\d]{3})/i
503                 ClenRE = /\AContent-length:\s*(\d+)/i
504                 def process_received_headers
505                         abort_connection unless @current_response.headers.first =~ HttpResponseRE
506                         @current_response.version = $1.dup
507                         st = $2.dup
508                         @current_response.status = st.to_i
509                         abort_connection unless st[0,1] == "2"
510
511                         clen = nil
512                         @current_response.headers.each do |e|
513                                 if clen == nil and e =~ ClenRE
514                                         clen = $1.dup.to_i
515                                 end
516                         end
517
518                         if clen
519                                 set_text_mode clen
520                         end
521                 end
522                 private :process_received_headers
523
524
525                 def receive_binary_data text
526                         @current_response.content = text
527                         @current_response.deferrable.succeed @current_response
528                         @requests.pop
529                         @current_response = (@requests.last || []).last
530                         set_line_mode
531                 end
532
533
534
535                 # We've received either a server error or an internal error.
536                 # Close the connection and abort any pending requests.
537                 #--
538                 # When should we call close_connection? It will cause #unbind
539                 # to be fired. Should the user expect to see #unbind before
540                 # we call #receive_http_error, or the other way around?
541                 #
542                 # Set instance variable @closed. That's used to inhibit further
543                 # processing of any inbound data after an error has been recognized.
544                 #
545                 # We shouldn't have to worry about any leftover outbound data,
546                 # because we call close_connection (not close_connection_after_writing).
547                 # That ensures that any pipelined requests received after an error
548                 # DO NOT get streamed out to the server on this connection.
549                 # Very important. TODO, write a unit-test to establish that behavior.
550                 #
551                 def abort_connection
552                         close_connection
553                         @closed = true
554                         @current_response.deferrable.fail( @current_response )
555                 end
556
557
558                 #------------------------
559                 # Below here are user-overridable methods.
560
561         end
562 =end
563 end
564 end
565
566
567 =begin
568 module EventMachine
569 module Protocols
570
571 class HttpClient < Connection
572   include EventMachine::Deferrable
573
574
575     MaxPostContentLength = 20 * 1024 * 1024
576
577   # USAGE SAMPLE:
578   #
579   # EventMachine.run {
580   #   http = EventMachine::Protocols::HttpClient.request(
581   #     :host => server,
582   #     :port => 80,
583   #     :request => "/index.html",
584   #     :query_string => "parm1=value1&parm2=value2"
585   #   )
586   #   http.callback {|response|
587   #     puts response[:status]
588   #     puts response[:headers]
589   #     puts response[:content]
590   #   }
591   # }
592   #
593
594   # TODO:
595   # Add streaming so we can support enormous POSTs. Current max is 20meg.
596   # Timeout for connections that run too long or hang somewhere in the middle.
597   # Persistent connections (HTTP/1.1), may need a associated delegate object.
598   # DNS: Some way to cache DNS lookups for hostnames we connect to. Ruby's
599   # DNS lookups are unbelievably slow.
600   # HEAD requests.
601   # Chunked transfer encoding.
602   # Convenience methods for requests. get, post, url, etc.
603   # SSL.
604   # Handle status codes like 304, 100, etc.
605   # Refactor this code so that protocol errors all get handled one way (an exception?),
606   # instead of sprinkling set_deferred_status :failed calls everywhere.
607
608   def self.request( args = {} )
609     args[:port] ||= 80
610     EventMachine.connect( args[:host], args[:port], self ) {|c|
611       # According to the docs, we will get here AFTER post_init is called.
612       c.instance_eval {@args = args}
613     }
614   end
615
616   def post_init
617     @start_time = Time.now
618     @data = ""
619     @read_state = :base
620   end
621
622   # We send the request when we get a connection.
623   # AND, we set an instance variable to indicate we passed through here.
624   # That allows #unbind to know whether there was a successful connection.
625   # NB: This naive technique won't work when we have to support multiple
626   # requests on a single connection.
627   def connection_completed
628     @connected = true
629     send_request @args
630   end
631
632   def send_request args
633     args[:verb] ||= args[:method] # Support :method as an alternative to :verb.
634     args[:verb] ||= :get # IS THIS A GOOD IDEA, to default to GET if nothing was specified?
635
636     verb = args[:verb].to_s.upcase
637     unless ["GET", "POST", "PUT", "DELETE", "HEAD"].include?(verb)
638       set_deferred_status :failed, {:status => 0} # TODO, not signalling the error type
639       return # NOTE THE EARLY RETURN, we're not sending any data.
640     end
641
642     request = args[:request] || "/"
643     unless request[0,1] == "/"
644       request = "/" + request
645     end
646
647     qs = args[:query_string] || ""
648     if qs.length > 0 and qs[0,1] != '?'
649       qs = "?" + qs
650     end
651
652     # Allow an override for the host header if it's not the connect-string.
653     host = args[:host_header] || args[:host] || "_"
654     # For now, ALWAYS tuck in the port string, although we may want to omit it if it's the default.
655     port = args[:port]
656
657     # POST items.
658     postcontenttype = args[:contenttype] || "application/octet-stream"
659     postcontent = args[:content] || ""
660     raise "oversized content in HTTP POST" if postcontent.length > MaxPostContentLength
661
662     # ESSENTIAL for the request's line-endings to be CRLF, not LF. Some servers misbehave otherwise.
663     # TODO: We ASSUME the caller wants to send a 1.1 request. May not be a good assumption.
664     req = [
665       "#{verb} #{request}#{qs} HTTP/1.1",
666       "Host: #{host}:#{port}",
667       "User-agent: Ruby EventMachine",
668     ]
669
670     if verb == "POST" || verb == "PUT"
671       req << "Content-type: #{postcontenttype}"
672       req << "Content-length: #{postcontent.length}"
673     end
674
675     # TODO, this cookie handler assumes it's getting a single, semicolon-delimited string.
676     # Eventually we will want to deal intelligently with arrays and hashes.
677     if args[:cookie]
678       req << "Cookie: #{args[:cookie]}"
679     end
680
681     req << ""
682     reqstring = req.map {|l| "#{l}\r\n"}.join
683     send_data reqstring
684
685     if verb == "POST" || verb == "PUT"
686       send_data postcontent
687     end
688   end
689
690
691   def receive_data data
692     while data and data.length > 0
693       case @read_state
694       when :base
695         # Perform any per-request initialization here and don't consume any data.
696         @data = ""
697         @headers = []
698         @content_length = nil # not zero
699         @content = ""
700         @status = nil
701         @read_state = :header
702       when :header
703         ary = data.split( /\r?\n/m, 2 )
704         if ary.length == 2
705           data = ary.last
706           if ary.first == ""
707               if @content_length and @content_length > 0
708                   @read_state = :content
709               else
710                   dispatch_response
711                   @read_state = :base
712               end
713           else
714             @headers << ary.first
715             if @headers.length == 1
716               parse_response_line
717             elsif ary.first =~ /\Acontent-length:\s*/i
718               # Only take the FIRST content-length header that appears,
719               # which we can distinguish because @content_length is nil.
720               # TODO, it's actually a fatal error if there is more than one
721               # content-length header, because the caller is presumptively
722               # a bad guy. (There is an exploit that depends on multiple
723               # content-length headers.)
724               @content_length ||= $'.to_i
725             end
726           end
727         else
728           @data << data
729           data = ""
730         end
731       when :content
732         # If there was no content-length header, we have to wait until the connection
733         # closes. Everything we get until that point is content.
734         # TODO: Must impose a content-size limit, and also must implement chunking.
735         # Also, must support either temporary files for large content, or calling
736         # a content-consumer block supplied by the user.
737         if @content_length
738           bytes_needed = @content_length - @content.length
739           @content += data[0, bytes_needed]
740           data = data[bytes_needed..-1] || ""
741           if @content_length == @content.length
742             dispatch_response
743             @read_state = :base
744           end
745         else
746           @content << data
747           data = ""
748         end
749       end
750     end
751   end
752
753
754   # We get called here when we have received an HTTP response line.
755   # It's an opportunity to throw an exception or trigger other exceptional
756   # handling.
757   def parse_response_line
758     if @headers.first =~ /\AHTTP\/1\.[01] ([\d]{3})/
759       @status = $1.to_i
760     else
761       set_deferred_status :failed, {
762         :status => 0 # crappy way of signifying an unrecognized response. TODO, find a better way to do this.
763       }
764       close_connection
765     end
766   end
767   private :parse_response_line
768
769   def dispatch_response
770     @read_state = :base
771     set_deferred_status :succeeded, {
772       :content => @content,
773       :headers => @headers,
774       :status => @status
775     }
776     # TODO, we close the connection for now, but this is wrong for persistent clients.
777     close_connection
778   end
779
780   def unbind
781     if !@connected
782       set_deferred_status :failed, {:status => 0} # YECCCCH. Find a better way to signal no-connect/network error.
783     elsif (@read_state == :content and @content_length == nil)
784       dispatch_response
785     end
786   end
787 end
788
789
790 end
791 end
792
793 =end
794
Note: See TracBrowser for help on using the browser.