Changeset 587

Show
Ignore:
Timestamp:
11/26/07 16:40:43 (1 year ago)
Author:
blackhedd
Message:

development work on a replacement for the HTTP client.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • version_0/lib/protocols/httpcli2.rb

    r586 r587  
    2525 
    2626 
     27 
    2728module EventMachine 
    2829module Protocols 
    2930 
     31 
    3032        class HttpClient2 < Connection 
     33                include LineText2 
     34 
     35 
     36                class Request 
     37                        include Deferrable 
     38 
     39                        attr_reader :version 
     40                        attr_reader :status 
     41                        attr_reader :header_lines 
     42                        attr_reader :headers 
     43                        attr_reader :content 
     44                        attr_reader :internal_error 
     45 
     46                        def initialize conn, args 
     47                                @conn = conn 
     48                                @args = args 
     49                                @header_lines = [] 
     50                                @headers = {} 
     51                                @blanks = 0 
     52                        end 
     53 
     54                        def send_request 
     55                                az = @args[:authorization] and az = "Authorization: #{az}\r\n" 
     56 
     57                                r = [ 
     58                                        "#{@args[:verb]} #{@args[:uri]} HTTP/#{@args[:version] || "1.1"}\r\n", 
     59                                        "Host: #{@args[:host_header] || "_"}\r\n", 
     60                                        az || "", 
     61                                        "\r\n" 
     62                                ] 
     63                                @conn.send_data r.join 
     64                        end 
     65 
     66                        #-- 
     67                        # Allow up to ten blank lines before we get a real response line. 
     68                        # Allow no more than 100 lines in the header. 
     69                        # 
     70                        def receive_line ln 
     71                                if ln.length == 0 
     72                                        if @header_lines.length > 0 
     73                                                process_header 
     74                                        else 
     75                                                @blanks += 1 
     76                                                if @blanks > 10 
     77                                                        @conn.close_connection 
     78                                                end 
     79                                        end 
     80                                else 
     81                                        @header_lines << ln 
     82                                        if @header_lines.length > 100 
     83                                                @internal_error = :bad_header 
     84                                                @conn.close_connection 
     85                                        end 
     86                                end 
     87                        end 
     88 
     89 
     90                        #-- 
     91                        # 
     92                        # 
     93                        HttpResponseRE = /\AHTTP\/(1.[01]) ([\d]{3})/i 
     94                        ClenRE = /\AContent-length:\s*(\d+)/i 
     95                        ColonRE = /\:\s*/ 
     96 
     97                        def process_header 
     98                                unless @header_lines.first =~ HttpResponseRE 
     99                                        @conn.close_connection 
     100                                        @internal_error = :bad_request 
     101                                end 
     102                                @version = $1.dup 
     103                                @status = $2.dup.to_i 
     104 
     105                                clen = nil 
     106                                @header_lines.each_with_index do |e,ix| 
     107                                        if ix > 0 
     108                                                hdr,val = e.split(ColonRE) 
     109                                                (@headers[hdr.downcase] ||= []) << val 
     110                                        end 
     111                                        if clen == nil and e =~ ClenRE 
     112                                                clen = $1.dup.to_i 
     113                                        end 
     114                                end 
     115 
     116                                if clen 
     117                                        @conn.set_text_mode clen 
     118                                else 
     119                                        # Chunked transfer, multipart, or end-of-connection. 
     120                                        # For end-of-connection, we need to go the unbind 
     121                                        # method and suppress its desire to fail us. 
     122                                        p "NO CLEN" 
     123                                        @internal_error = :unsupported_clen 
     124                                        @conn.close_connection 
     125                                end 
     126                        end 
     127                        private :process_header 
     128 
     129                        #-- 
     130                        # At the present time, we only handle contents that have a length 
     131                        # specified by the content-length header. 
     132                        # 
     133                        def receive_text text 
     134                                @content = text 
     135                                @conn.pop_request 
     136                                succeed self 
     137                        end 
     138                end 
     139 
     140                # Make a connection to a remote HTTP server. 
     141                # Can take either a pair of arguments (which will be interpreted as 
     142                # a hostname/ip-address and a port), or a hash. 
     143                # If the arguments are a hash, then supported values include: 
     144                #  :host => a hostname or ip-address; 
     145                #  :port => a port number 
     146                #-- 
     147                # TODO, support optional encryption arguments like :ssl 
     148                def self.connect *args 
     149                        if args.length == 2 
     150                                args = {:host=>args[0], :port=>args[1]} 
     151                        else 
     152                                args = args.first 
     153                        end 
     154 
     155                        h,prt,ssl = args[:host], Integer(args[:port]), (args[:tls] || args[:ssl]) 
     156                        conn = EM.connect( h, prt, self ) 
     157                        # TODO, start_tls if necessary 
     158                        conn.set_default_host_header( h, prt, ssl ) 
     159                        conn 
     160                end 
     161 
     162 
     163                #-- 
     164                # Compute and remember a string to be used as the host header in HTTP requests 
     165                # unless the user overrides it with an argument to #request. 
     166                # 
     167                def set_default_host_header host, port, ssl 
     168                        if (ssl and port != 443) or (!ssl and port != 80) 
     169                                @host_header = "#{host}:#{port}" 
     170                        else 
     171                                @host_header = host 
     172                        end 
     173                end 
     174 
     175 
     176                def post_init 
     177                        super 
     178                        @connected = EM::DefaultDeferrable.new 
     179                end 
     180 
     181                def connection_completed 
     182                        super 
     183                        @connected.succeed 
     184                end 
     185 
     186                #-- 
     187                # All pending requests, if any, must fail. 
     188                # We might come here without ever passing through connection_completed 
     189                # in case we can't connect to the server. We'll also get here when the 
     190                # connection closes (either because the server closes it, or we close it 
     191                # due to detecting an internal error or security violation). 
     192                # In either case, run down all pending requests, if any, and signal failure 
     193                # on them. 
     194                # 
     195                # Set and remember a flag (@closed) so we can immediately fail any 
     196                # subsequent requests. 
     197                # 
     198                def unbind 
     199                        super 
     200                        @closed = true 
     201                        (@requests || []).each {|r| r.fail r} 
     202                end 
     203 
     204 
     205                def get args 
     206                        if args.is_a?(String) 
     207                                args = {:uri=>args} 
     208                        end 
     209                        args[:verb] = "GET" 
     210                        request args 
     211                end 
     212 
     213                def request args 
     214                        args[:host_header] = @host_header unless args.has_key?(:host_header) 
     215                        r = Request.new self, args 
     216                        if @closed 
     217                                r.fail 
     218                        else 
     219                                (@requests ||= []).unshift r 
     220                                @connected.callback {r.send_request} 
     221                        end 
     222                        r 
     223                end 
     224 
     225                def receive_line ln 
     226                        @requests.last.receive_line ln 
     227                end 
     228                def receive_binary_data text 
     229                        @requests.last.receive_text text 
     230                end 
     231 
     232                #-- 
     233                # Called by a Request object when it completes. 
     234                # 
     235                def pop_request 
     236                        @requests.pop 
     237                end 
     238        end 
     239 
     240 
     241=begin 
     242        class HttpClient2x < Connection 
    31243                include LineText2 
    32244 
     
    253465 
    254466        end 
     467=end 
    255468end 
    256469end 
  • version_0/tests/test_httpclient2.rb

    r585 r587  
    6767        end 
    6868 
     69        def test_bad_server 
     70                EM.run { 
     71                        http = EM::P::HttpClient2.connect Localhost, 9999 
     72                        d = http.get "/" 
     73                        d.errback {|r| p r.internal_error; EM.stop } 
     74                } 
     75        end 
     76 
    6977        def test_get 
    7078                EM.run { 
    71                         EM.start_server Localhost, Localport, TestServer 
    7279                        http = EM::P::HttpClient2.connect "www.bayshorenetworks.com", 80 
    7380                        d = http.get "/" 
    7481                        d.callback {|resp| 
     82                                p resp.content 
     83                                EM.stop 
     84                        } 
     85                } 
     86        end 
     87 
     88        # Not a pipelined request because we wait for one response before we request the next. 
     89        def test_get_multiple 
     90                EM.run { 
     91                        http = EM::P::HttpClient2.connect "www.bayshorenetworks.com", 80 
     92                        d = http.get "/" 
     93                        d.callback {|r| 
    7594                                e = http.get "/" 
    76                                 e.callback {|resp| 
    77                                         p resp.status 
     95                                e.callback {|r| 
     96                                        p r.content 
     97                                        EM.stop 
    7898                                } 
    7999                        } 
     
    81101        end 
    82102 
     103        def test_get_pipeline 
     104                EM.run { 
     105                        http = EM::P::HttpClient2.connect "www.microsoft.com", 80 
     106                        http.get("/").callback {|r| 
     107                                p r.headers 
     108                        } 
     109                        http.get("/").callback {|r| 
     110                                p r.headers 
     111                        } 
     112                        EM::Timer.new(1) {EM.stop} 
     113                } 
     114        end 
     115 
     116 
     117        def test_authheader 
     118                EM.run { 
     119                        EM.start_server Localhost, Localport, TestServer 
     120                        http = EM::P::HttpClient2.connect Localhost, 18842 
     121                        d = http.get :url=>"/", :authorization=>"Basic xxx" 
     122                        d.callback {|r| EM.stop} 
     123                        d.errback {|r| EM.stop} 
     124                } 
     125        end 
     126 
     127 
    83128end 
    84129