Class: Cougar::Request

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

Constant Summary collapse

WRITE_TIMEOUT =
10
READ_TIMEOUT =
10
SOCKET_WRITE_ERR_MSG =
"Socket timeout writing data"
SOCKET_READ_ERR_MSG =
"Socket timeout reading data"
RACK_ENV_CONST =
{
  "rack.version" => [1, 3].freeze,
  "rack.url_scheme" => "http",
  "rack.errors" => Cougar::Rack::ERROR_STREAM,
  "rack.multithread" => false,
  "rack.multiprocess" => true,
  "rack.run_once" => false,
  "SERVER_SOFTWARE" => SERVER_SOFTWARE,
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(client) ⇒ Request

Returns a new instance of Request.



14
15
16
17
18
19
20
# File 'lib/cougar/request.rb', line 14

def initialize(client)
  @client = client
  @buffer = String.new
  @read_buf = String.new(capacity: 16384)
  @remote_addr = client.respond_to?(:remote_address) ? client.remote_address.ip_address : "127.0.0.1"
  reset_for_next_request
end

Instance Method Details

#envObject



74
75
76
# File 'lib/cougar/request.rb', line 74

def env
  @env
end

#fast_read(size, buf) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/cougar/request.rb', line 83

def fast_read(size, buf)
  while true
    ret = @client.read_nonblock(size, buf, exception: false)
    if :wait_readable == ret
      unless @client.wait_readable(READ_TIMEOUT)
        raise SOCKET_READ_ERR_MSG
      end
    else
      return ret
    end
  end
end

#fast_write(str) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/cougar/request.rb', line 96

def fast_write(str)
  n = 0
  byte_size = str.bytesize
  while n < byte_size
    ret = @client.write_nonblock(n.zero? ? str : str.byteslice(n..-1), exception: false)
    if :wait_writable == ret
      unless @client.wait_writable(WRITE_TIMEOUT)
        raise SOCKET_WRITE_ERR_MSG
      end
    else
      n += ret
    end
  end
end

#has_buffered_data?Boolean

Returns:

  • (Boolean)


36
37
38
# File 'lib/cougar/request.rb', line 36

def has_buffered_data?
  !@buffer.empty?
end

#parseObject



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
# File 'lib/cougar/request.rb', line 40

def parse
  # Read data until we have a complete HTTP request
  while @env.nil?
    # Try to parse the accumulated buffer
    unless @buffer.empty?
      @env = Picohttp.parse_request_env(@buffer)

      if @env
        # Find where headers end and body begins
        @body_offset = @buffer.index("\r\n\r\n")
        @body_offset += 4 if @body_offset
        break
      end
    end

    # Need more data
    break unless fast_read(16384, @read_buf)

    @buffer << @read_buf
  end

  return false unless @env

  # Add additional Rack environment variables
  setup_rack_env

  # Handle request body if present
  setup_request_body

  true
rescue EOFError
  false
end

#reset_for_next_requestObject



22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/cougar/request.rb', line 22

def reset_for_next_request
  if @body_offset
    content_length = @env&.fetch("CONTENT_LENGTH", "0").to_i
    consumed = @body_offset + content_length
    if consumed >= @buffer.bytesize
      @buffer.clear
    else
      @buffer = @buffer.byteslice(consumed, @buffer.bytesize - consumed)
    end
  end
  @env = nil
  @body_offset = nil
end

#respond(status, headers, body) ⇒ Object



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
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/cougar/request.rb', line 111

def respond(status, headers, body)
  if @env["SERVER_PROTOCOL"] == "HTTP/1.0"
    status_line = HttpStatuses.status_line_10(status)
    should_keepalive = false
  else
    status_line = HttpStatuses.status_line_11(status)
    should_keepalive = !@env["HTTP_CONNECTION"]&.casecmp?("close")
  end

  headers["connection"] = should_keepalive ? "keep-alive" : "close"

  if !headers["content-length"] && status >= 200 && status != 204 && status != 304
    old_body = body
    body = []
    length = 0
    old_body.each do |chunk|
      body << chunk
      length += chunk.bytesize
    end
    headers["content-length"] = length.to_s
  end

  buf = +""
  buf << status_line
  headers.each do |name, value|
    case value
    when Array
      value.each do |v|
        buf << name << ": " << v << "\r\n"
      end
    else
      buf << name << ": " << value << "\r\n"
    end
  end
  buf << "\r\n"

  body.each do |chunk|
    buf << chunk
  end
  fast_write(buf)

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

  should_keepalive
end