Class: Rhino::HTTP

Inherits:
Object
  • Object
show all
Defined in:
lib/rhino/http.rb

Overview

An interface for HTTP. Responsible for reading and writting to the socket via the HTTP protocol.

Usage:

http = Rhino::HTTP.new(socket, application)
http.handle

Defined Under Namespace

Classes: Exception

Constant Summary collapse

RESERVED =
/\A(Date|Connection)\Z/i.freeze
VERSION =
"HTTP/1.1".freeze
CRLF =
"\r\n".freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(socket, application) ⇒ HTTP

Returns a new instance of HTTP.


21
22
23
24
# File 'lib/rhino/http.rb', line 21

def initialize(socket, application)
  self.socket = socket
  self.application = application
end

Instance Attribute Details

#applicationObject

Returns the value of attribute application


19
20
21
# File 'lib/rhino/http.rb', line 19

def application
  @application
end

#socketObject

Returns the value of attribute socket


18
19
20
# File 'lib/rhino/http.rb', line 18

def socket
  @socket
end

Instance Method Details

#handleObject


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
# File 'lib/rhino/http.rb', line 88

def handle
  env = parse

  begin
    status, headers, body = application.call(env)
  rescue ::Exception => exception
    Rhino.logger.log(exception.inspect)
    status, headers, body = 500, {}, []
  end

  time = Time.now.httpdate

  socket.write "#{VERSION} #{status} #{Rack::Utils::HTTP_STATUS_CODES.fetch(status) { 'UNKNOWN' }}#{CRLF}"
  socket.write "Date: #{time}#{CRLF}"
  socket.write "Connection: close#{CRLF}"

  headers.each do |key, value|
    if !RESERVED.match(key)
      socket.write "#{key}: #{value}#{CRLF}"
    end
  end

  socket.write(CRLF)

  body.each do |chunk|
    socket.write(chunk)
  end

  Rhino.logger.log("[#{time}] '#{env["REQUEST_METHOD"]} #{env["REQUEST_URI"]} #{env["HTTP_VERSION"]}' #{status}")
end

#parseObject

Raises:


26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/rhino/http.rb', line 26

def parse
  rl = socket.gets
  matches = /\A(?<method>\S+)\s+(?<uri>\S+)\s+(?<version>\S+)#{CRLF}\Z/.match(rl)
  raise Exception.new("invalid request line: #{rl.inspect}") if !matches
  begin
    uri = URI.parse(matches[:uri])
  rescue URI::InvalidURIError
    raise Exception.new("invalid URI in request line: #{rl.inspect}")
  end

  env = {
    "rack.errors" => $stderr,
    "rack.input" => socket,
    "rack.version" => Rack::VERSION,
    "rack.multithread" => !!Rhino.config.multithread,
    "rack.multiprocess" => !!Rhino.config.multiprocess,
    "rack.run_once" => !!Rhino.config.run_once,
    "rack.url_scheme" => uri.scheme || "http",
    "REQUEST_METHOD" => matches[:method],
    "REQUEST_URI" => matches[:uri],
    "HTTP_VERSION" => matches[:version],
    "QUERY_STRING" => uri.query || "",
    "SERVER_PORT" => uri.port || 80,
    "SERVER_NAME" => uri.host || "localhost",
    "PATH_INFO" => uri.path || "",
    "SCRIPT_NAME" => "",
  }

  key = nil
  value = nil
  loop do
    hl = socket.gets

    if key && value
      matches = /\A\s+(?<fold>.+)#{CRLF}\Z/.match(hl)
      if matches
        value = "#{value} #{matches[:fold].strip}"
        next
      end

      case key
      when Rack::CONTENT_TYPE then env["CONTENT_TYPE"] = value
      when Rack::CONTENT_LENGTH then env["CONTENT_LENGTH"] = Integer(value)
      else env["HTTP_" + key.tr("-", "_").upcase] ||= value
      end
    end

    break if hl.eql?(CRLF)

    matches = /\A(?<key>[^:]+):\s*(?<value>.+)#{CRLF}\Z/.match(hl)
    raise Exception.new("invalid header line: #{hl.inspect}") unless matches
    key = matches[:key].strip
    value = matches[:value].strip
  end

  input = socket.read(env["CONTENT_LENGTH"] || 0)

  env["rack.input"] = StringIO.new(input)

  return env
end