Class: HTTPX::Connection::HTTP1

Inherits:
Object
  • Object
show all
Includes:
HTTPX::Callbacks, Loggable
Defined in:
lib/httpx/connection/http1.rb

Direct Known Subclasses

Plugins::Proxy::HTTP::ProxyParser

Constant Summary collapse

MAX_REQUESTS =
100
CRLF =
"\r\n"

Constants included from Loggable

Loggable::COLORS

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Loggable

#log, #log_exception

Methods included from HTTPX::Callbacks

#emit, #on, #once, #only

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)
  @options = Options.new(options)
  @max_concurrent_requests = @options.max_concurrent_requests || MAX_REQUESTS
  @max_requests = @options.max_requests || MAX_REQUESTS
  @parser = Parser::HTTP1.new(self)
  @buffer = buffer
  @version = [1, 1]
  @pending = []
  @requests = []
  @handshake_completed = false
end

Instance Attribute Details

#pendingObject (readonly)

Returns the value of attribute pending.



13
14
15
# File 'lib/httpx/connection/http1.rb', line 13

def pending
  @pending
end

#requestsObject (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

#closeObject



52
53
54
55
# File 'lib/httpx/connection/http1.rb', line 52

def close
  reset
  emit(:close, true)
end

#consumeObject



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

#dispatchObject



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

Returns:

  • (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

Returns:

  • (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
    disable
  else
    @requests.each do |request|
      emit(:error, request, ex)
    end
    @pending.each do |request|
      emit(:error, request, ex)
    end
  end
end

#interestsObject



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_completeObject



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.options.headers_class.new(h)
  response = @request.options.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_startObject

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

#pingObject



198
199
200
201
# File 'lib/httpx/connection/http1.rb', line 198

def ping
  emit(:reset)
  emit(:exhausted)
end

#resetObject



46
47
48
49
50
# File 'lib/httpx/connection/http1.rb', line 46

def reset
  @max_requests = @options.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

#timeoutObject



27
28
29
# File 'lib/httpx/connection/http1.rb', line 27

def timeout
  @options.timeout[:operation_timeout]
end