Class: TP2::HTTP1Connection
- Inherits:
-
Object
- Object
- TP2::HTTP1Connection
- Defined in:
- lib/tp2/http.rb
Overview
Encapsulates a HTTP/1 connection
Constant Summary collapse
- SEND_FLAGS =
rubocop:disable Metrics/ClassLength
UM::MSG_NOSIGNAL | UM::MSG_WAITALL
- CRLF =
response API
"\r\n"
- ZERO_CRLF_CRLF =
"0\r\n\r\n"
- CRLF_ZERO_CRLF_CRLF =
"\r\n0\r\n\r\n"
- INTERNAL_HEADER_REGEXP =
/^:/.freeze
Instance Method Summary collapse
- #collect_header_lines(lines, key, value) ⇒ Object
- #empty_status_line(status) ⇒ Object
-
#finish(_request) ⇒ void
Finishes the response to the current request.
-
#format_headers(headers, body, chunked) ⇒ String
Formats response headers into an array.
- #format_status_line(body, status, chunked) ⇒ Object
- #get_scheme_from_headers(headers) ⇒ Object
- #http1_1?(request) ⇒ Boolean
-
#initialize(machine, fd, bgid, &app) ⇒ HTTP1Connection
constructor
rubocop:disable Naming/MethodParameterName.
- #normalize_headers(headers) ⇒ Object
- #on_body(chunk) ⇒ Object
-
#on_headers_complete(headers) ⇒ Object
rubocop:disable Metrics/MethodLength.
- #on_message_complete ⇒ Object
-
#respond(_request, body, headers) ⇒ Object
Sends response including headers and body.
- #run ⇒ Object
-
#send_chunk(_request, chunk, done: false) ⇒ void
Sends a response body chunk.
-
#send_headers(request, headers, empty_response: false, chunked: true) ⇒ void
Sends response headers.
- #with_body_status_line(status, body, chunked) ⇒ Object
Constructor Details
#initialize(machine, fd, bgid, &app) ⇒ HTTP1Connection
rubocop:disable Naming/MethodParameterName
11 12 13 14 15 16 17 |
# File 'lib/tp2/http.rb', line 11 def initialize(machine, fd, bgid, &app) # rubocop:disable Naming/MethodParameterName @machine = machine @fd = fd @bgid = bgid @parser = Http::Parser.new(self) @app = app end |
Instance Method Details
#collect_header_lines(lines, key, value) ⇒ Object
180 181 182 183 184 185 186 |
# File 'lib/tp2/http.rb', line 180 def collect_header_lines(lines, key, value) if value.is_a?(Array) value.inject(lines) { |_, item| lines << "#{key}: #{item}\r\n" } else lines << "#{key}: #{value}\r\n" end end |
#empty_status_line(status) ⇒ Object
164 165 166 167 168 169 170 |
# File 'lib/tp2/http.rb', line 164 def empty_status_line(status) if status == 204 +"HTTP/1.1 #{status}\r\n" else +"HTTP/1.1 #{status}\r\nContent-Length: 0\r\n" end end |
#finish(_request) ⇒ void
This method returns an undefined value.
Finishes the response to the current request. If no headers were sent, default headers are sent using #send_headers.
126 127 128 129 |
# File 'lib/tp2/http.rb', line 126 def finish(_request) # request.tx_incr(5) @machine.send(@fd, ZERO_CRLF_CRLF, ZERO_CRLF_CRLF.bytesize, SEND_FLAGS) end |
#format_headers(headers, body, chunked) ⇒ String
Formats response headers into an array. If empty_response is true(thy), the response status code will default to 204, otherwise to 200.
143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/tp2/http.rb', line 143 def format_headers(headers, body, chunked) status = headers[':status'] status ||= (body ? Qeweney::Status::OK : Qeweney::Status::NO_CONTENT) lines = format_status_line(body, status, chunked) headers.each do |k, v| next if k =~ INTERNAL_HEADER_REGEXP collect_header_lines(lines, k, v) end lines << CRLF lines end |
#format_status_line(body, status, chunked) ⇒ Object
156 157 158 159 160 161 162 |
# File 'lib/tp2/http.rb', line 156 def format_status_line(body, status, chunked) if !body empty_status_line(status) else with_body_status_line(status, body, chunked) end end |
#get_scheme_from_headers(headers) ⇒ Object
28 29 30 31 32 33 34 |
# File 'lib/tp2/http.rb', line 28 def get_scheme_from_headers(headers) if (proto = headers['x-forwarded-proto']) proto.downcase else 'http' end end |
#http1_1?(request) ⇒ Boolean
131 132 133 |
# File 'lib/tp2/http.rb', line 131 def http1_1?(request) request.headers[':protocol'] == 'http/1.1' end |
#normalize_headers(headers) ⇒ Object
36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/tp2/http.rb', line 36 def normalize_headers(headers) headers.each_with_object({}) do |(k, v), h| k = k.downcase hk = h[k] if hk hk = h[k] = [hk] unless hk.is_a?(Array) v.is_a?(Array) ? hk.concat(v) : hk << v else h[k] = v end end end |
#on_body(chunk) ⇒ Object
49 50 51 |
# File 'lib/tp2/http.rb', line 49 def on_body(chunk) @request.buffer_body_chunk(chunk) end |
#on_headers_complete(headers) ⇒ Object
rubocop:disable Metrics/MethodLength
19 20 21 22 23 24 25 26 |
# File 'lib/tp2/http.rb', line 19 def on_headers_complete(headers) # rubocop:disable Metrics/MethodLength headers = normalize_headers(headers) headers[':path'] = @parser.request_url headers[':method'] = @parser.http_method.downcase headers[':scheme'] = get_scheme_from_headers(headers) headers[':protocol'] = "http/#{@parser.http_major}.#{@parser.http_minor}" @request = Qeweney::Request.new(headers, self) end |
#on_message_complete ⇒ Object
53 54 55 56 |
# File 'lib/tp2/http.rb', line 53 def @done = @request.headers[':protocol'] != 'http/1.1' @app.call(@request) end |
#respond(_request, body, headers) ⇒ Object
Sends response including headers and body. Waits for the request to complete if not yet completed. The body is sent using chunked transfer encoding.
80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/tp2/http.rb', line 80 def respond(_request, body, headers) formatted_headers = format_headers(headers, body, false) # request.tx_incr(formatted_headers.bytesize + (body ? body.bytesize : 0)) if body buf = formatted_headers + body @machine.send(@fd, buf, buf.bytesize, SEND_FLAGS) # handle_write(formatted_headers + body) else @machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS) end end |
#run ⇒ Object
58 59 60 61 62 63 64 65 66 67 |
# File 'lib/tp2/http.rb', line 58 def run @machine.recv_each(@fd, @bgid, 0) do |buf| @parser << buf break if @done end rescue SystemCallError # do nothing ensure @machine.close(@fd) end |
#send_chunk(_request, chunk, done: false) ⇒ void
This method returns an undefined value.
Sends a response body chunk. If no headers were sent, default headers are sent using #send_headers. if the done option is true(thy), an empty chunk will be sent to signal response completion to the client.
113 114 115 116 117 118 119 120 121 |
# File 'lib/tp2/http.rb', line 113 def send_chunk(_request, chunk, done: false) data = +'' data << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n" if chunk data << "0\r\n\r\n" if done return if data.empty? # request.tx_incr(data.bytesize) @machine.send(@fd, data, data.bytesize, SEND_FLAGS) end |
#send_headers(request, headers, empty_response: false, chunked: true) ⇒ void
This method returns an undefined value.
Sends response headers. If empty_response is truthy, the response status code will default to 204, otherwise to 200.
99 100 101 102 103 104 |
# File 'lib/tp2/http.rb', line 99 def send_headers(request, headers, empty_response: false, chunked: true) formatted_headers = format_headers(headers, !empty_response, http1_1?(request) && chunked) # request.tx_incr(formatted_headers.bytesize) # @machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS) end |
#with_body_status_line(status, body, chunked) ⇒ Object
172 173 174 175 176 177 178 |
# File 'lib/tp2/http.rb', line 172 def with_body_status_line(status, body, chunked) if chunked +"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n" else +"HTTP/1.1 #{status}\r\nContent-Length: #{body.is_a?(String) ? body.bytesize : body.to_i}\r\n" end end |