Module: Raptor::Http

Defined in:
lib/raptor/http.rb,
sig/generated/raptor/http.rbs

Overview

Shared HTTP utilities used by both the HTTP/1.x and HTTP/2 handlers: Rack env keys that aren't provided by Rack itself, low-level socket writing, and Common Log Format access-log formatting.

Defined Under Namespace

Classes: WriteError

Constant Summary collapse

WRITE_TIMEOUT =

Returns:

  • (::Integer)
5
CONTENT_LENGTH =

Returns:

  • (::String)
"CONTENT_LENGTH"
CONTENT_TYPE =

Returns:

  • (::String)
"CONTENT_TYPE"
HTTP_VERSION =

Returns:

  • (::String)
"HTTP_VERSION"
REMOTE_ADDR =

Returns:

  • (::String)
"REMOTE_ADDR"
REQUEST_URI =

Returns:

  • (::String)
"REQUEST_URI"
SERVER_SOFTWARE =

Returns:

  • (::String)
"SERVER_SOFTWARE"
SERVER_SOFTWARE_VALUE =

Returns:

  • (Object)
"Raptor/#{Raptor::VERSION}".freeze

Class Method Summary collapse

Class Method Details

.call_response_finished(env, status, headers, error) ⇒ void

This method returns an undefined value.

Calls every rack.response_finished callback in reverse registration order, rescuing any that raise.

Parameters:

  • env (Hash, nil) —

    the Rack environment

  • status (Integer, nil) —

    the response status code

  • headers (Hash, nil) —

    the response headers

  • error (Exception, nil) —

    any error raised during processing, or nil on success



130
131
132
133
134
135
136
# File 'lib/raptor/http.rb', line 130

def self.call_response_finished(env, status, headers, error)
  return unless env && env[Rack::RACK_RESPONSE_FINISHED].is_a?(Array)

  env[Rack::RACK_RESPONSE_FINISHED].reverse_each do |callable|
    callable.call(env, status, headers, error) rescue nil
  end
end

.no_entity_body_status?(status) ⇒ Boolean

Returns whether an HTTP status forbids an entity body.

Parameters:

  • status (Integer) —

    the response status code

Returns:

  • (Boolean)


36
37
38
# File 'lib/raptor/http.rb', line 36

def self.no_entity_body_status?(status)
  (status >= 100 && status < 200) || status == 204 || status == 304
end

.parser_worker(env_template, max_body_size) ⇒ Proc

Returns a Ractor-safe proc that parses an HTTP/1.x request from the state hash's buffered bytes.

Parameters:

  • env_template (Hash) —

    the Rack env template to seed each HTTP/1.x request with

  • max_body_size (Integer, nil) —

    byte limit for HTTP/1.x request bodies, or nil for no limit

Returns:

  • (Proc)


114
115
116
117
118
# File 'lib/raptor/http.rb', line 114

def self.parser_worker(env_template, max_body_size)
  proc do |data|
    Raptor::Http1.parse(data, env_template, max_body_size)
  end
end

.socket_write(socket, string, timeout: WRITE_TIMEOUT) ⇒ void

This method returns an undefined value.

Writes string in full, retrying on partial writes. Bounded by timeout so a slow client can't pin the writing thread.

Parameters:

  • socket (TCPSocket) —

    the socket to write to

  • string (String) —

    the data to write

  • timeout (Integer) (defaults to: WRITE_TIMEOUT) —

    seconds to wait for the socket to become writable on each partial write

  • timeout: (Integer) (defaults to: WRITE_TIMEOUT)

Raises:

  • (WriteError) —

    if the socket is not writable within the timeout or raises IOError



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/raptor/http.rb', line 50

def self.socket_write(socket, string, timeout: WRITE_TIMEOUT)
  bytes = 0
  byte_size = string.bytesize

  while bytes < byte_size
    begin
      bytes += socket.write_nonblock(bytes.zero? ? string : string.byteslice(bytes..-1))
    rescue IO::WaitWritable
      raise WriteError unless socket.wait_writable(timeout)
      retry
    rescue IOError
      raise WriteError
    end
  end
end

.socket_writev(socket, strings, timeout: WRITE_TIMEOUT) ⇒ void

This method returns an undefined value.

Writes strings in full via a single writev(2) syscall when possible, falling back to per-string writes on partial results. Bounded by timeout so a slow client can't pin the writing thread.

Parameters:

  • socket (TCPSocket) —

    the socket to write to

  • strings (Array<String>) —

    the buffers to write in order

  • timeout (Integer) (defaults to: WRITE_TIMEOUT) —

    seconds to wait for the socket to become writable on each retry

  • timeout: (Integer) (defaults to: WRITE_TIMEOUT)

Raises:

  • (WriteError) —

    if the socket is not writable within the timeout or raises IOError



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/raptor/http.rb', line 77

def self.socket_writev(socket, strings, timeout: WRITE_TIMEOUT)
  total = strings.sum(&:bytesize)
  return if total.zero?

  begin
    written = Raptor::VectorIO.writev_nonblock(socket, strings)
  rescue IO::WaitWritable
    raise WriteError unless socket.wait_writable(timeout)
    retry
  rescue IOError
    raise WriteError
  end

  return if written == total

  offset = 0
  strings.each do |string|
    size = string.bytesize
    if written >= offset + size
      offset += size
    else
      start = written - offset
      socket_write(socket, start.zero? ? string : string.byteslice(start..-1), timeout: timeout)
      offset += size
      written = offset
    end
  end
end

.write_access_log(io, env, status, size, remote_addr) ⇒ void

This method returns an undefined value.

Writes a Common Log Format entry to io. Write failures are silently ignored.

Parameters:

  • io (IO) —

    the destination IO

  • env (Hash) —

    the Rack environment

  • status (Integer) —

    the response status code

  • size (String) —

    the response body size in bytes, or - if unknown

  • remote_addr (String) —

    the client IP address



149
150
151
152
153
154
155
156
157
# File 'lib/raptor/http.rb', line 149

def self.write_access_log(io, env, status, size, remote_addr)
  timestamp = Time.now.strftime("%d/%b/%Y:%H:%M:%S %z")
  method = env[Rack::REQUEST_METHOD]
  query = env[Rack::QUERY_STRING]
  path = query.empty? ? env[Rack::PATH_INFO] : "#{env[Rack::PATH_INFO]}?#{query}"
  protocol = env[Rack::SERVER_PROTOCOL]

  io.puts(%(#{remote_addr} - - [#{timestamp}] "#{method} #{path} #{protocol}" #{status} #{size})) rescue nil
end