Class: Raptor::Request

Inherits:
Object
  • Object
show all
Defined in:
lib/raptor/request.rb

Overview

Handles HTTP request processing and Rack application integration.

Request manages the HTTP parsing pipeline using Ractors and coordinates with the reactor for connection state management. It bridges between the low-level HTTP parsing and high-level Rack application interface, handling both incomplete requests (that need more data) and complete requests (ready for application processing).

Defined Under Namespace

Classes: Error, WriteError

Constant Summary collapse

BODY_BUFFER_THRESHOLD =
256 * 1024
FILE_CHUNK_SIZE =
64 * 1024
KEEPALIVE_BUFFER_SIZE =
64 * 1024
WRITE_TIMEOUT =
5
KEEPALIVE_READ_TIMEOUT =
0.001
MAX_KEEPALIVE_REQUESTS =
100
HTTP_SCHEME =
"http"
HTTP_10 =
"HTTP/1.0"
HTTP_11 =
"HTTP/1.1"
STATUS_LINE_CACHE_10 =
Hash.new do |h, status|
  reason = Rack::Utils::HTTP_STATUS_CODES[status]
  h[status] = "HTTP/1.0 #{status}#{reason ? " #{reason}" : ""}\r\n".freeze
end
STATUS_LINE_CACHE_11 =
Hash.new do |h, status|
  reason = Rack::Utils::HTTP_STATUS_CODES[status]
  h[status] = "HTTP/1.1 #{status}#{reason ? " #{reason}" : ""}\r\n".freeze
end
STATUS_WITH_NO_ENTITY_BODY =
Set.new([204, 304, *100..199]).freeze
ERROR_RESPONSE_500 =
"HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
CONNECTION_CLOSE =
"close"
CONNECTION_KEEPALIVE =
"keep-alive"
TRANSFER_ENCODING_CHUNKED =
"chunked"
HTTP_CONNECTION =
"HTTP_CONNECTION"
HTTP_TRANSFER_ENCODING =
"HTTP_TRANSFER_ENCODING"
RACK_HEADER_PREFIX =
"rack."
RACK_HIJACKED =
"rack.hijacked"
RACK_HIJACK_IO =
"rack.hijack_io"
ILLEGAL_HEADER_KEY_REGEX =
/[\x00-\x20\(\)<>@,;:\\"\/\[\]\?=\{\}\x7F]/
ILLEGAL_HEADER_VALUE_REGEX =
/[\x00-\x08\x0A-\x1F]/

Instance Method Summary collapse

Constructor Details

#initialize(app, server_port) ⇒ void

Creates a new Request handler.

Parameters:

  • app (#call)

    the Rack application to dispatch complete requests to

  • server_port (Integer)

    port number used to populate SERVER_PORT in the Rack env



72
73
74
75
# File 'lib/raptor/request.rb', line 72

def initialize(app, server_port)
  @app = app
  @server_port = server_port
end

Instance Method Details

#handle_parsed_request(parsed_request, reactor, thread_pool) ⇒ void

This method returns an undefined value.

Handles a parsed HTTP request by either continuing parsing or dispatching to the Rack app.

For incomplete requests, updates reactor state and re-registers for more I/O. For complete requests, removes from reactor, builds Rack env, and dispatches to thread pool.

Parameters:

  • parsed_request (Hash)

    the parsed request state from the ractor pool

  • reactor (Reactor)

    the reactor managing the client connection

  • thread_pool (AtomicThreadPool)

    thread pool for application processing



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/raptor/request.rb', line 157

def handle_parsed_request(parsed_request, reactor, thread_pool)
  unless parsed_request[:complete]
    reactor.update_state(parsed_request)
  else
    socket = reactor.remove(parsed_request[:id])
    request_count = (parsed_request[:request_count] || 0) + 1
    remote_addr = parsed_request[:remote_addr] || "127.0.0.1"
    url_scheme = parsed_request[:url_scheme] || HTTP_SCHEME

    thread_pool << proc do
      process_client(
        socket,
        parsed_request[:id],
        parsed_request[:env].dup,
        parsed_request[:parse_data],
        parsed_request[:body],
        reactor,
        thread_pool,
        request_count,
        remote_addr,
        url_scheme
      )
    end
  end
end

#http_parser_workerProc

Returns a Proc for HTTP parsing work in Ractor context.

The returned Proc processes raw socket data through the appropriate HTTP parser and returns either a complete request state (ready for app processing) or incomplete request state (needs more data).

Returns:

  • (Proc)

    a Ractor-safe proc that accepts a state hash and returns an updated state hash



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/raptor/request.rb', line 86

def http_parser_worker
  proc do |data|
    next Raptor::Http2.process_frames(data) if data[:protocol] == :http2

    parser = Raptor::HttpParser.new
    env = {}
    nread = parser.execute(env, data[:buffer], 0)
    parse_data = if data[:parse_data]
      data[:parse_data].dup
    else
      { parse_count: 0, content_length: parser.content_length }
    end
    parse_data[:parse_count] += 1

    message = if parser.finished?
      if parser.has_body?
        body_buffer = data[:buffer].byteslice(nread..-1) || ""

        if env[HTTP_TRANSFER_ENCODING]&.include?(TRANSFER_ENCODING_CHUNKED)
          decoded_body = String.new
          offset = 0
          chunked_complete = false

          while offset < body_buffer.bytesize
            crlf = body_buffer.index("\r\n", offset)
            break unless crlf

            chunk_size = body_buffer.byteslice(offset, crlf - offset).to_i(16)

            if chunk_size == 0
              chunked_complete = true
              break
            end

            offset = crlf + 2
            decoded_body << body_buffer.byteslice(offset, chunk_size)
            offset += chunk_size + 2
          end

          if chunked_complete
            env.delete(HTTP_TRANSFER_ENCODING)
            data.merge(env: env, body: decoded_body, parse_data: parse_data, complete: true)
          else
            data.merge(env: env, parse_data: parse_data)
          end
        elsif parser.content_length > body_buffer.bytesize
          data.merge(env: env, parse_data: parse_data)
        else
          data.merge(env: env, body: body_buffer, parse_data: parse_data, complete: true)
        end
      else
        data.merge(env: env, body: nil, parse_data: parse_data, complete: true)
      end
    else
      data.merge(env: env, parse_data: parse_data)
    end
    Ractor.make_shareable(message)
  end
end