Class: Tipi::HTTP1Connection

Inherits:
Connection show all
Defined in:
lib/tipi/controller/web_stock.rb

Constant Summary collapse

CRLF =

response API

"\r\n"
CRLF_ZERO_CRLF_CRLF =
"\r\n0\r\n\r\n"
INTERNAL_HEADER_REGEXP =
/^:/.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io, evloop, &app) ⇒ HTTP1Connection

Returns a new instance of HTTP1Connection.



32
33
34
35
36
37
38
# File 'lib/tipi/controller/web_stock.rb', line 32

def initialize(io, evloop, &app)
  @io = io
  @evloop = evloop
  @parser = Http::Parser.new(self)
  @app = app
  setup_read_request
end

Instance Attribute Details

#ioObject (readonly)

Returns the value of attribute io.



30
31
32
# File 'lib/tipi/controller/web_stock.rb', line 30

def io
  @io
end

Instance Method Details

#close_ioObject



117
118
119
# File 'lib/tipi/controller/web_stock.rb', line 117

def close_io
  @evloop.emit([:close_io, self, @io])
end

#collect_header_lines(lines, key, value) ⇒ Object



235
236
237
238
239
240
241
# File 'lib/tipi/controller/web_stock.rb', line 235

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



219
220
221
222
223
224
225
# File 'lib/tipi/controller/web_stock.rb', line 219

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.



185
186
187
188
# File 'lib/tipi/controller/web_stock.rb', line 185

def finish(request)
  request.tx_incr(5)
  handle_write("0\r\n\r\n")
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.

Parameters:

  • headers (Hash)

    response headers

  • body (boolean)

    whether a response body will be sent

  • chunked (boolean)

    whether to use chunked transfer encoding

Returns:

  • (String)

    formatted response headers



198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/tipi/controller/web_stock.rb', line 198

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



211
212
213
214
215
216
217
# File 'lib/tipi/controller/web_stock.rb', line 211

def format_status_line(body, status, chunked)
  if !body
    empty_status_line(status)
  else
    with_body_status_line(status, body, chunked)
  end
end

#handle_read_requestObject



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/tipi/controller/web_stock.rb', line 89

def handle_read_request
  result = @io.read_nonblock(16384, exception: false)
  case result
  when :wait_readable
    watch_io(false)
  when :wait_writable
    watch_io(true)
  when nil
    close_io
  else
    @parser << result
    if @request_complete
      handle_request
      # @response = handle_request(@request_headers, @request_body)
      # handle_write_response
    else
      watch_io(false)
    end
  end
rescue HTTP::Parser::Error, SystemCallError, IOError
  close_io
end

#handle_requestObject



121
122
123
124
125
126
# File 'lib/tipi/controller/web_stock.rb', line 121

def handle_request
  @app.call(@request)
  # req = Qeweney::Request.new(headers, self)
  # response_body = "Hello, world!"
  # "HTTP/1.1 200 OK\nContent-Length: #{response_body.bytesize}\n\n#{response_body}"
end

#handle_write(data = nil) ⇒ Object



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/tipi/controller/web_stock.rb', line 243

def handle_write(data = nil)
  if data
    if @response_buffer
      @response_buffer << data
    else
      @response_buffer = +data
    end
  end

  result = @io.write_nonblock(@response_buffer, exception: false)
  case result
  when :wait_readable
    watch_io(false)
  when :wait_writable
    watch_io(true)
  when nil
    close_io
  else
    setup_read_request
    watch_io(false)
  end
end

#http1_1?(request) ⇒ Boolean

Returns:

  • (Boolean)


161
162
163
# File 'lib/tipi/controller/web_stock.rb', line 161

def http1_1?(request)
  request.headers[':protocol'] == 'http/1.1'
end

#io_readyObject



81
82
83
84
85
86
87
# File 'lib/tipi/controller/web_stock.rb', line 81

def io_ready
  if !@request_complete
    handle_read_request
  else
    handle_write_response
  end
end

#normalize_headers(headers) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/tipi/controller/web_stock.rb', line 56

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



73
74
75
# File 'lib/tipi/controller/web_stock.rb', line 73

def on_body(chunk)
  @request.buffer_body_chunk(chunk)
end

#on_headers_complete(headers) ⇒ Object



46
47
48
49
50
51
52
53
54
# File 'lib/tipi/controller/web_stock.rb', line 46

def on_headers_complete(headers)
  headers = normalize_headers(headers)
  headers[':path'] = @parser.request_url
  headers[':method'] = @parser.http_method.downcase
  scheme = (proto = headers['x-forwarded-proto']) ?
            proto.downcase : scheme_from_connection
  headers[':scheme'] = scheme
  @request = Qeweney::Request.new(headers, self)
end

#on_message_completeObject



77
78
79
# File 'lib/tipi/controller/web_stock.rb', line 77

def on_message_complete
  @request_complete = true
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


138
139
140
141
142
143
144
145
146
# File 'lib/tipi/controller/web_stock.rb', line 138

def respond(request, body, headers)
  formatted_headers = format_headers(headers, body, false)
  request.tx_incr(formatted_headers.bytesize + (body ? body.bytesize : 0))
  if body
    handle_write(formatted_headers + body)
  else
    handle_write(formatted_headers)
  end
end

#scheme_from_connectionObject



69
70
71
# File 'lib/tipi/controller/web_stock.rb', line 69

def scheme_from_connection
  @io.is_a?(OpenSSL::SSL::SSLSocket) ? 'https' : 'http'
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



172
173
174
175
176
177
178
179
180
# File 'lib/tipi/controller/web_stock.rb', line 172

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)
  handle_write(data)
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



155
156
157
158
159
# File 'lib/tipi/controller/web_stock.rb', line 155

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)
  handle_write(formatted_headers)
end

#setup_read_requestObject



40
41
42
43
44
# File 'lib/tipi/controller/web_stock.rb', line 40

def setup_read_request
  @request_complete = nil
  @request = nil
  @response_buffer = nil
end

#watch_io(rw) ⇒ Object



112
113
114
115
# File 'lib/tipi/controller/web_stock.rb', line 112

def watch_io(rw)
  @evloop.watch_io(self, @io, rw, true)
  # @evloop.emit([:watch_io, self, @io, rw, true])
end

#with_body_status_line(status, body, chunked) ⇒ Object



227
228
229
230
231
232
233
# File 'lib/tipi/controller/web_stock.rb', line 227

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