Class: HTTPX::Connection::HTTP1
- Inherits:
-
Object
- Object
- HTTPX::Connection::HTTP1
- Includes:
- HTTPX::Callbacks, Loggable
- Defined in:
- lib/httpx/connection/http1.rb
Constant Summary collapse
- MAX_REQUESTS =
100- CRLF =
"\r\n"
Constants included from Loggable
Instance Attribute Summary collapse
-
#pending ⇒ Object
readonly
Returns the value of attribute pending.
-
#requests ⇒ Object
readonly
Returns the value of attribute requests.
Instance Method Summary collapse
- #<<(data) ⇒ Object
- #close ⇒ Object
- #consume ⇒ Object
- #dispatch ⇒ Object
- #empty? ⇒ Boolean
- #exhausted? ⇒ Boolean
- #handle_error(ex) ⇒ Object
-
#initialize(buffer, options) ⇒ HTTP1
constructor
A new instance of HTTP1.
- #interests ⇒ Object
- #on_complete ⇒ Object
- #on_data(chunk) ⇒ Object
- #on_headers(h) ⇒ Object
-
#on_start ⇒ Object
HTTP Parser callbacks.
- #on_trailers(h) ⇒ Object
- #ping ⇒ Object
- #reset ⇒ Object
- #send(request) ⇒ Object
- #timeout ⇒ Object
Methods included from Loggable
Methods included from HTTPX::Callbacks
Constructor Details
#initialize(buffer, options) ⇒ HTTP1
Returns a new instance of HTTP1.
15 16 17 18 19 20 21 22 23 24 25 |
# File 'lib/httpx/connection/http1.rb', line 15 def initialize(buffer, ) = Options.new() @max_concurrent_requests = .max_concurrent_requests || MAX_REQUESTS @max_requests = .max_requests || MAX_REQUESTS @parser = Parser::HTTP1.new(self) @buffer = buffer @version = [1, 1] @pending = [] @requests = [] @handshake_completed = false end |
Instance Attribute Details
#pending ⇒ Object (readonly)
Returns the value of attribute pending.
13 14 15 |
# File 'lib/httpx/connection/http1.rb', line 13 def pending @pending end |
#requests ⇒ Object (readonly)
Returns the value of attribute requests.
13 14 15 |
# File 'lib/httpx/connection/http1.rb', line 13 def requests @requests end |
Instance Method Details
#<<(data) ⇒ Object
72 73 74 |
# File 'lib/httpx/connection/http1.rb', line 72 def <<(data) @parser << data end |
#close ⇒ Object
52 53 54 55 |
# File 'lib/httpx/connection/http1.rb', line 52 def close reset emit(:close, true) end |
#consume ⇒ Object
88 89 90 91 92 93 94 95 96 97 |
# File 'lib/httpx/connection/http1.rb', line 88 def consume requests_limit = [@max_requests, @requests.size].min concurrent_requests_limit = [@max_concurrent_requests, requests_limit].min @requests.each_with_index do |request, idx| break if idx >= concurrent_requests_limit next if request.state == :done handle(request) end end |
#dispatch ⇒ Object
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/httpx/connection/http1.rb', line 151 def dispatch if @request.expects? @parser.reset! return handle(@request) end request = @request @request = nil @requests.shift response = request.response emit(:response, request, response) if @parser.upgrade? response << @parser.upgrade_data throw(:called) end @parser.reset! @max_requests -= 1 manage_connection(response) send(@pending.shift) unless @pending.empty? end |
#empty? ⇒ Boolean
61 62 63 64 65 66 67 68 69 70 |
# File 'lib/httpx/connection/http1.rb', line 61 def empty? # this means that for every request there's an available # partial response, so there are no in-flight requests waiting. @requests.empty? || ( # checking all responses can be time-consuming. Alas, as in HTTP/1, responses # do not come out of order, we can get away with checking first and last. !@requests.first.response.nil? && (@requests.size == 1 || !@requests.last.response.nil?) ) end |
#exhausted? ⇒ Boolean
57 58 59 |
# File 'lib/httpx/connection/http1.rb', line 57 def exhausted? !@max_requests.positive? end |
#handle_error(ex) ⇒ Object
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/httpx/connection/http1.rb', line 175 def handle_error(ex) if (ex.is_a?(EOFError) || ex.is_a?(TimeoutError)) && @request && @request.response && !@request.response.headers.key?("content-length") && !@request.response.headers.key?("transfer-encoding") # if the response does not contain a content-length header, the server closing the # connnection is the indicator of response consumed. # https://greenbytes.de/tech/webdav/rfc2616.html#rfc.section.4.4 catch(:called) { on_complete } return end if @pipelining catch(:called) { disable } else @requests.each do |request| emit(:error, request, ex) end @pending.each do |request| emit(:error, request, ex) end end end |
#interests ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/httpx/connection/http1.rb', line 31 def interests # this means we're processing incoming response already return :r if @request return if @requests.empty? request = @requests.first return unless request return :w if request.interests == :w || !@buffer.empty? :r end |
#on_complete ⇒ Object
144 145 146 147 148 149 |
# File 'lib/httpx/connection/http1.rb', line 144 def on_complete return unless @request log(level: 2) { "parsing complete" } dispatch end |
#on_data(chunk) ⇒ Object
134 135 136 137 138 139 140 141 142 |
# File 'lib/httpx/connection/http1.rb', line 134 def on_data(chunk) return unless @request log(color: :green) { "-> DATA: #{chunk.bytesize} bytes..." } log(level: 2, color: :green) { "-> #{chunk.inspect}" } response = @request.response response << chunk end |
#on_headers(h) ⇒ Object
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/httpx/connection/http1.rb', line 107 def on_headers(h) @request = @requests.first return if @request.response log(level: 2) { "headers received" } headers = @request..headers_class.new(h) response = @request..response_class.new(@request, @parser.status_code, @parser.http_version.join("."), headers) log(color: :yellow) { "-> HEADLINE: #{response.status} HTTP/#{@parser.http_version.join(".")}" } log(color: :yellow) { response.headers.each.map { |f, v| "-> HEADER: #{f}: #{v}" }.join("\n") } @request.response = response on_complete if response.complete? end |
#on_start ⇒ Object
HTTP Parser callbacks
must be public methods, or else they won’t be reachable
103 104 105 |
# File 'lib/httpx/connection/http1.rb', line 103 def on_start log(level: 2) { "parsing begins" } end |
#on_trailers(h) ⇒ Object
124 125 126 127 128 129 130 131 132 |
# File 'lib/httpx/connection/http1.rb', line 124 def on_trailers(h) return unless @request response = @request.response log(level: 2) { "trailer headers received" } log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{v.join(", ")}" }.join("\n") } response.merge_headers(h) end |
#ping ⇒ Object
198 199 200 201 |
# File 'lib/httpx/connection/http1.rb', line 198 def ping emit(:reset) emit(:exhausted) end |
#reset ⇒ Object
46 47 48 49 50 |
# File 'lib/httpx/connection/http1.rb', line 46 def reset @max_requests = .max_requests || MAX_REQUESTS @parser.reset! @handshake_completed = false end |
#send(request) ⇒ Object
76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/httpx/connection/http1.rb', line 76 def send(request) unless @max_requests.positive? @pending << request return end return if @requests.include?(request) @requests << request @pipelining = true if @requests.size > 1 end |
#timeout ⇒ Object
27 28 29 |
# File 'lib/httpx/connection/http1.rb', line 27 def timeout .timeout[:operation_timeout] end |