Module: Uma::HTTP

Extended by:
HTTP
Included in:
HTTP
Defined in:
lib/uma/http.rb

Defined Under Namespace

Classes: ChunkedIO, Error, ParseError, RackIO, ResponseError

Constant Summary collapse

RE_REQUEST_LINE =
/^([a-z]+)\s+([^\s\?]+)(?:\?([^\s]+))?\s+(http\/[019\.]{1,3})/i
RE_HEADER_LINE =
/^([a-z0-9-]+):\s+(.+)/i
CHUNK_END =
"\r\n0\r\n\r\n"

Instance Method Summary collapse

Instance Method Details

#format_headers(headers) ⇒ Object



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/uma/http.rb', line 285

def format_headers(headers)
  buf = +''
  headers.each do |k, v|
    next if k =~ /^rack\./

    case v
    when String
      buf << "#{k}: #{v}\r\n"
    when Array
      v.each { buf << "#{k}: #{it}\r\n" }
    else
      raise ResponseError, "Invalid header value #{v.inspect}"
    end
  end
  buf << "\r\n"
  buf
end

#get_request_env(config, stream) ⇒ Object



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/uma/http.rb', line 137

def get_request_env(config, stream)
  env = {
    'rack.url_scheme' => 'http',
    'SCRIPT_NAME'   => '',
    'SERVER_NAME'   => 'localhost',
    'rack.hijack?'  => true,
    'rack.hijack'   => -> {
      env['uma.hijacked?'] = true
      env['rack.hijack'] = env['rack.input'] || RackIO.new(env, stream)
    },
    'rack.errors'   => config[:error_stream]
  }
  buf = +''
  ret = stream.get_line(buf, 4096)
  return if !ret

  parse_request_line(env, buf)
  while true
    ret = stream.get_line(buf, 4096)
    raise ParseError, "Unexpected EOF" if !ret
    break if buf.empty?

    parse_header(env, buf)
  end

  if env['CONTENT_LENGTH'] || env['HTTP_TRANSFER_ENCODING'] == 'chunked'
    env['rack.input'] = RackIO.new(env, stream)
  end
  env
end

#http_connection(machine, config, fd) ⇒ Object



111
112
113
114
115
116
# File 'lib/uma/http.rb', line 111

def http_connection(machine, config, fd)
  stream = UM::Stream.new(machine, fd)
  while true
    break if !process_request(machine, config, fd, stream)
  end
end

#parse_header(env, line) ⇒ Object

Raises:



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/uma/http.rb', line 182

def parse_header(env, line)
  m = line.match(RE_HEADER_LINE)
  raise ParseError, 'Invalid header' if !m

  key = "HTTP_#{m[1].upcase.tr('-', '_')}"
  value = m[2]
  case key
  when 'HTTP_CONTENT_TYPE'
    env['CONTENT_TYPE'] = value
  when 'HTTP_CONTENT_LENGTH'
    env['CONTENT_LENGTH'] = value
  when 'HTTP_HOST'
    env['SERVER_NAME'] = value
    env[key] = value
  else
    env[key] = value
  end
end

#parse_request_line(env, line) ⇒ Object

Raises:



170
171
172
173
174
175
176
177
178
# File 'lib/uma/http.rb', line 170

def parse_request_line(env, line)
  m = line.match(RE_REQUEST_LINE)
  raise ParseError, 'Invalid request line' if !m

  env['REQUEST_METHOD']   = m[1].upcase
  env['PATH_INFO']        = m[2]
  env['QUERY_STRING']     = m[3] || ''
  env['SERVER_PROTOCOL']  = m[4]
end

#process_request(machine, config, fd, stream) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/uma/http.rb', line 118

def process_request(machine, config, fd, stream)
  env = get_request_env(config, stream)
  return false if !env

  rack_response = config[:app].(env)
  if !env['uma.hijacked?']
    send_rack_response(machine, env, fd, rack_response)
    should_process_next_request?(env)
  else
    false
  end
rescue => e
  if (h = config[:error_handler])
    h.(e)
  else
    send_error_response(machine, fd, e)
  end
end

#send_body_chunked(machine, env, fd, buf_status, buf_headers, body) ⇒ Object



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/uma/http.rb', line 237

def send_body_chunked(machine, env, fd, buf_status, buf_headers, body)
  case body
  when String
    buf_chunk_header = "#{body.bytesize.to_s(16)}\r\n"
    machine.sendv(
      fd,
      buf_status,
      buf_headers,
      buf_chunk_header,
      body,
      CHUNK_END
    )
  when Array
    bufs = [buf_status, buf_headers]
    first = true
    body.each {
      bufs << (first ? "#{it.bytesize.to_s(16)}\r\n" : "\r\n#{it.bytesize.to_s(16)}\r\n")
      first = false
      bufs << it
    }
    bufs << CHUNK_END
    machine.sendv(fd, *bufs)
  else
    if body.respond_to?(:each)
      first = true
      body.each do
        chunk_header = first ? "#{it.bytesize.to_s(16)}\r\n" : "\r\n#{it.bytesize.to_s(16)}\r\n"
        if first
          machine.sendv(fd, buf_status, buf_headers, chunk_header, it)
          first = false
        else
          machine.sendv(fd, chunk_header, it)
        end
      end
      machine.sendv(fd, first ? "0\r\n\r\n" : "\r\n0\r\n\r\n")
    elsif body.respond_to?(:call)
      machine.sendv(fd, buf_status, buf_headers)
      chunked_stream = ChunkedIO.new(machine, fd, env['uma.stream'])
      body.call(chunked_stream)
    else
      raise ResponseError, "Invalid response body: #{body.inspect}"
    end

    chunked_stream.close if chunked_stream
    body.close if body.respond_to?(:close)
  end
end

#send_rack_response(machine, env, fd, response) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/uma/http.rb', line 203

def send_rack_response(machine, env, fd, response)
  status, headers, body = response
  hijack = headers['rack.hijack']

  empty_body = body.nil? || body == '' || (body.is_a?(Array) && body.empty?)

  if !hijack
    chunked = nil
    headers['content-length'] = '0' if empty_body
    chunked = !headers['content-length']
    headers['transfer-encoding'] = 'chunked' if chunked
  end

  buf_status = "HTTP/1.1 #{status}\r\n"
  buf_headers = format_headers(headers)

  if hijack
    machine.sendv(fd, buf_status, buf_headers)
    hijack.(env['rack.hijack'].())
  elsif body && !empty_body
    if chunked
      send_body_chunked(machine, env, fd, buf_status, buf_headers, body)
    else
      if body.is_a?(Array)
        machine.sendv(fd, buf_status, buf_headers, *body)
      else
        machine.sendv(fd, buf_status, buf_headers, body)
      end
    end
  else
    machine.sendv(fd, buf_status, buf_headers)
  end
end

#should_process_next_request?(env) ⇒ Boolean

Returns:

  • (Boolean)


303
304
305
306
# File 'lib/uma/http.rb', line 303

def should_process_next_request?(env)
  # TODO: look at env
  true
end