class Polyphony::HTTP::Server::HTTP1Adapter

HTTP1 protocol implementation

Constants

DEFAULT_HEADERS_OPTS

Public Class Methods

new(conn, opts) click to toggle source

Initializes a protocol adapter instance

# File lib/polyphony/http/server/http1.rb, line 13
def initialize(conn, opts)
  @conn = conn
  @opts = opts
  @parser = ::HTTP::Parser.new(self)
end

Public Instance Methods

close() click to toggle source
# File lib/polyphony/http/server/http1.rb, line 219
def close
  @conn.close
end
consume_request() click to toggle source

Waits for the current request to complete. Transfers control to the parse loop, and resumes once the parse_loop has fired the on_message_complete callback

# File lib/polyphony/http/server/http1.rb, line 72
def consume_request
  request = @requests_head
  loop do
    data = @conn.readpartial(8192)
    @parser << data
    return if request.complete?

    snooze
  rescue EOFError
    break
  end
end
each(&block) click to toggle source
# File lib/polyphony/http/server/http1.rb, line 19
def each(&block)
  loop do
    data = @conn.readpartial(8192)
    return if handle_incoming_data(data, &block)
  rescue EOFError
    break
  end
rescue SystemCallError, IOError
  # ignore
ensure
  finalize_client_loop
end
finalize_client_loop() click to toggle source
# File lib/polyphony/http/server/http1.rb, line 46
def finalize_client_loop
  # release references to various objects
  @requests_head = @requests_tail = nil
  @parser = nil
  @conn.close
end
finish() click to toggle source

Finishes the response to the current request. If no headers were sent, default headers are sent using send_headers. @return [void]

# File lib/polyphony/http/server/http1.rb, line 215
def finish
  @conn << "0\r\n\r\n"
end
get_body_chunk() click to toggle source

Reads a body chunk for the current request. Transfers control to the parse loop, and resumes once the parse_loop has fired the on_body callback

# File lib/polyphony/http/server/http1.rb, line 55
def get_body_chunk
  @waiting_for_body_chunk = true
  @next_chunk = nil
  while !@requests_tail.complete? && (data = @conn.readpartial(8192))
    @parser << data
    return @next_chunk if @next_chunk

    snooze
  end
  nil
ensure
  @waiting_for_body_chunk = nil
end
handle_incoming_data(data, &block) click to toggle source

return [Boolean] true if client loop should stop

# File lib/polyphony/http/server/http1.rb, line 33
def handle_incoming_data(data, &block)
  @parser << data
  snooze
  while (request = @requests_head)
    return true if upgrade_connection(request.headers, &block)

    @requests_head = request.__next__
    block.call(request)
    return true unless request.keep_alive?
  end
  nil
end
http2_upgraded_headers(headers) click to toggle source

Returns headers for HTTP2 upgrade @param headers [Hash] request headers @return [Hash] headers for HTTP2 upgrade

# File lib/polyphony/http/server/http1.rb, line 160
def http2_upgraded_headers(headers)
  headers.merge(
    ':scheme'    => 'http',
    ':authority' => headers['Host']
  )
end
on_body(chunk) click to toggle source
# File lib/polyphony/http/server/http1.rb, line 105
def on_body(chunk)
  if @waiting_for_body_chunk
    @next_chunk = chunk
    @waiting_for_body_chunk = nil
  else
    @requests_tail.buffer_body_chunk(chunk)
  end
end
on_headers_complete(headers) click to toggle source
# File lib/polyphony/http/server/http1.rb, line 90
def on_headers_complete(headers)
  headers[':path'] = @parser.request_url
  headers[':method'] = @parser.http_method
  queue_request(Request.new(headers, self))
end
on_message_complete() click to toggle source
# File lib/polyphony/http/server/http1.rb, line 114
def on_message_complete
  @waiting_for_body_chunk = nil
  @requests_tail.complete!(@parser.keep_alive?)
end
protocol() click to toggle source
# File lib/polyphony/http/server/http1.rb, line 85
def protocol
  version = @parser.http_version
  "HTTP #{version.join('.')}"
end
queue_request(request) click to toggle source
# File lib/polyphony/http/server/http1.rb, line 96
def queue_request(request)
  if @requests_head
    @requests_tail.__next__ = request
    @requests_tail = request
  else
    @requests_head = @requests_tail = request
  end
end
respond(body, headers) click to toggle source

Sends response including headers and body. Waits for the request to complete if not yet completed. The body is sent using chunked transfer encoding. @param body [String] response body @param headers

# File lib/polyphony/http/server/http1.rb, line 173
def respond(body, headers)
  consume_request if @parsing
  data = format_headers(headers, body)
  if body
    data << if @parser.http_minor == 0
              body
            else
              "#{body.bytesize.to_s(16)}\r\n#{body}\r\n0\r\n\r\n"
            end
  end
  @conn << data
end
send_chunk(chunk, done: false) click to toggle source

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. @param chunk [String] response body chunk @param done [boolean] whether the response is completed @return [void]

# File lib/polyphony/http/server/http1.rb, line 206
def send_chunk(chunk, done: false)
  data = +"#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
  data << "0\r\n\r\n" if done
  @conn << data
end
send_headers(headers, opts = DEFAULT_HEADERS_OPTS) click to toggle source

Sends response headers. If empty_response is truthy, the response status code will default to 204, otherwise to 200. @param headers [Hash] response headers @param empty_response [boolean] whether a response body will be sent @return [void]

# File lib/polyphony/http/server/http1.rb, line 196
def send_headers(headers, opts = DEFAULT_HEADERS_OPTS)
  @conn << format_headers(headers, !opts[:empty_response])
end
upgrade_connection(headers, &block) click to toggle source

Upgrades the connection to a different protocol, if the 'Upgrade' header is given. By default the only supported upgrade protocol is HTTP2. Additional protocols, notably WebSocket, can be specified by passing a hash to the :upgrade option when starting a server:

opts = {
  upgrade: {
    websocket: Polyphony::Websocket.handler(&method(:ws_handler))
  }
}
Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) { |req| ... }

@param headers [Hash] request headers @return [boolean] truthy if the connection has been upgraded

# File lib/polyphony/http/server/http1.rb, line 133
def upgrade_connection(headers, &block)
  upgrade_protocol = headers['Upgrade']
  return nil unless upgrade_protocol

  upgrade_protocol = upgrade_protocol.downcase.to_sym
  upgrade_handler = @opts[:upgrade] && @opts[:upgrade][upgrade_protocol]
  return upgrade_with_handler(upgrade_handler, headers) if upgrade_handler
  return upgrade_to_http2(headers, &block) if upgrade_protocol == :h2c

  nil
end
upgrade_to_http2(headers, &block) click to toggle source
# File lib/polyphony/http/server/http1.rb, line 151
def upgrade_to_http2(headers, &block)
  @parser = @requests_head = @requests_tail = nil
  HTTP2Adapter.upgrade_each(@conn, @opts, http2_upgraded_headers(headers), &block)
  true
end
upgrade_with_handler(handler, headers) click to toggle source
# File lib/polyphony/http/server/http1.rb, line 145
def upgrade_with_handler(handler, headers)
  @parser = @requests_head = @requests_tail = nil
  handler.(@conn, headers)
  true
end

Private Instance Methods

empty_status_line(status) click to toggle source
# File lib/polyphony/http/server/http1.rb, line 258
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
format_header_lines(key, value) click to toggle source
# File lib/polyphony/http/server/http1.rb, line 242
def format_header_lines(key, value)
  if value.is_a?(Array)
    value.inject(+'') { |data, item| data << "#{key}: #{item}\r\n" }
  else
    "#{key}: #{value}\r\n"
  end
end
format_headers(headers, body) click to toggle source

Formats response headers. If empty_response is true(thy), the response status code will default to 204, otherwise to 200. @param headers [Hash] response headers @param empty_response [boolean] whether a response body will be sent @return [String] formatted response headers

# File lib/polyphony/http/server/http1.rb, line 230
def format_headers(headers, body)
  status = headers[':status'] || (body ? 200 : 204)
  data = format_status_line(body, status)

  headers.each do |k, v|
    next if k =~ /^:/

    format_header_lines(k, v)
  end
  data << "\r\n"
end
format_status_line(body, status) click to toggle source
# File lib/polyphony/http/server/http1.rb, line 250
def format_status_line(body, status)
  if !body
    empty_status_line(status)
  else
    with_body_status_line(status, body)
  end
end
with_body_status_line(status, body) click to toggle source
# File lib/polyphony/http/server/http1.rb, line 266
def with_body_status_line(status, body)
  if @parser.http_minor == 0
    +"HTTP/1.0 #{status}\r\nContent-Length: #{body.bytesize}\r\n"
  else
    +"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
  end
end