Class: TP2::HTTP1Adapter

Inherits:
Object
  • Object
show all
Defined in:
lib/tp2/http1_adapter.rb

Defined Under Namespace

Classes: ProtocolError

Constant Summary collapse

CRLF =

response API

"\r\n"
ZERO_CRLF_CRLF =
"0\r\n\r\n"
CRLF_ZERO_CRLF_CRLF =
"\r\n0\r\n\r\n"
SEND_FLAGS =
UM::MSG_NOSIGNAL | UM::MSG_WAITALL

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(machine, fd, &app) ⇒ HTTP1Adapter

Returns a new instance of HTTP1Adapter.



10
11
12
13
14
15
16
# File 'lib/tp2/http1_adapter.rb', line 10

def initialize(machine, fd, &app)
  @machine = machine
  @fd = fd
  @buffer = String.new('', capacity: 4096)
  @sio = StringIO.new(@buffer)
  @app = app
end

Instance Attribute Details

#fdObject (readonly)

Returns the value of attribute fd.



8
9
10
# File 'lib/tp2/http1_adapter.rb', line 8

def fd
  @fd
end

Instance Method Details

#complete?(req) ⇒ Boolean

Returns:

  • (Boolean)


75
76
77
# File 'lib/tp2/http1_adapter.rb', line 75

def complete?(req)
  req.headers[':body-done-reading']
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.



138
139
140
141
# File 'lib/tp2/http1_adapter.rb', line 138

def finish(_request)
  # request.tx_incr(5)
  @machine.send(@fd, ZERO_CRLF_CRLF, ZERO_CRLF_CRLF.bytesize, SEND_FLAGS)
end

#get_body(req) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
# File 'lib/tp2/http1_adapter.rb', line 41

def get_body(req)
  headers = req.headers
  content_length = headers['content-length']
  return read(content_length.to_i) if content_length

  chunked_encoding = headers['transfer-encoding']&.downcase == 'chunked'
  return get_body_chunked_encoding(headers) if chunked_encoding

  # if content-length is not specified, we read to EOF, up to max 1MB size
  read(1 << 20, nil, false)
end

#get_body_chunk(req, buffered_only = false) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/tp2/http1_adapter.rb', line 53

def get_body_chunk(req, buffered_only = false)
  headers = req.headers
  content_length = headers['content-length']
  if content_length
    return nil if headers[':body-done-reading']

    chunk = read(content_length.to_i)
    headers[':body-done-reading'] = true
    return chunk
  end

  chunked_encoding = headers['transfer-encoding']&.downcase == 'chunked'
  return read_chunk(headers, nil) if chunked_encoding

  return nil if headers[':body-done-reading']

  # if content-length is not specified, we read to EOF, up to max 1MB size      
  chunk = read(1 << 20, nil, false)
  headers[':body-done-reading'] = true
  chunk
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.

Parameters:

  • request (Qeweney::Request)

    HTTP request

  • body (String)

    response body

  • headers


92
93
94
95
96
97
98
99
100
101
102
# File 'lib/tp2/http1_adapter.rb', line 92

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

#runObject



18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/tp2/http1_adapter.rb', line 18

def run
  while true
    persist = serve_request
    break if !persist
  end
rescue => e
  puts '*' * 40
  p e
  puts e.backtrace.join("\n")
  exit!
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.

Parameters:

  • request (Qeweney::Request)

    HTTP request

  • chunk (String)

    response body chunk

  • done (boolean) (defaults to: false)

    whether the response is completed



125
126
127
128
129
130
131
132
133
# File 'lib/tp2/http1_adapter.rb', line 125

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.

Parameters:

  • request (Qeweney::Request)

    HTTP request

  • headers (Hash)

    response headers

  • empty_response (boolean) (defaults to: false)

    whether a response body will be sent

  • chunked (boolean) (defaults to: true)

    whether to use chunked transfer encoding



111
112
113
114
115
116
# File 'lib/tp2/http1_adapter.rb', line 111

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

#serve_requestObject



32
33
34
35
36
37
38
39
# File 'lib/tp2/http1_adapter.rb', line 32

def serve_request
  headers = parse_headers
  return false if !headers

  request = Qeweney::Request.new(headers, self)
  @app.call(request)
  persist_connection?(headers)
end