Module: Yahns::HttpResponse

Includes:
Unicorn::HttpResponse
Included in:
HttpClient
Defined in:
lib/yahns/http_response.rb

Overview

Writes a Rack response to your client using the HTTP/1.1 specification. You use it by simply doing:

status, headers, body = rack_app.call(env)
http_response_write(status, headers, body)

Most header correctness (including Content-Length and Content-Type) is the job of Rack, with the exception of the “Date” header.

Constant Summary collapse

MTX =
Mutex.new
CONN_KA =

avoid GC overhead for frequently used-strings:

"Connection: keep-alive\r\n\r\n"
CONN_CLOSE =
"Connection: close\r\n\r\n"
Z =
""
CCC_RESPONSE_START =
[ 'HTTP', '/1.1 ' ]
RESPONSE_START =
CCC_RESPONSE_START.join
REQUEST_METHOD =
"REQUEST_METHOD"
HEAD =
"HEAD"
MSG_MORE =
0
MSG_DONTWAIT =
0

Instance Method Summary collapse

Instance Method Details

#do_cccObject

returns nil on success :wait_readable/:wait_writable/:close for epoll



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/yahns/http_response.rb', line 226

def do_ccc
  @response_start_sent = true
  wbuf = nil
  rv = nil
  CCC_RESPONSE_START.each do |buf|
    if wbuf
      wbuf << buf
    else
      case rv = kgio_trywrite(buf)
      when nil
        break
      when String
        buf = rv
      when :wait_writable, :wait_readable
        if self.class.output_buffering
          wbuf = buf.dup
          @state = Yahns::WbufStr.new(wbuf, :ccc_done)
          break
        else
          response_wait_write(rv) or return :close
        end
      end while true
    end
  end
  rv
end

#err_response(code) ⇒ Object



61
62
63
# File 'lib/yahns/http_response.rb', line 61

def err_response(code)
  "#{response_start}#{CODES[code]}\r\n\r\n"
end

#have_more?(value) ⇒ Boolean



125
126
127
# File 'lib/yahns/http_response.rb', line 125

def have_more?(value)
  value.to_i > 0 && @hs.env[REQUEST_METHOD] != HEAD
end

#http_100_response(env) ⇒ Object

only used if input_buffering is true (not :lazy or false) input_buffering==:lazy/false gives control to the app returns nil on success returns :close, :wait_writable, or :wait_readable



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/yahns/http_response.rb', line 257

def http_100_response(env)
  env.delete("HTTP_EXPECT") =~ /\A100-continue\z/i or return
  buf = @response_start_sent ? "100 Continue\r\n\r\nHTTP/1.1 ".freeze
                             : "HTTP/1.1 100 Continue\r\n\r\n".freeze

  case rv = kgio_trywrite(buf)
  when String
    buf = rv
  when :wait_writable, :wait_readable
    if self.class.output_buffering
      @state = Yahns::WbufStr.new(buf, :r100_done)
      return rv
    else
      response_wait_write(rv) or return :close
    end
  else
    return rv
  end while true
end

#http_response_done(alive) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/yahns/http_response.rb', line 94

def http_response_done(alive)
  @input = @input.close if @input
  if alive
    @response_start_sent = false
    # @hs.buf will have data if the client pipelined
    if @hs.buf.empty?
      @state = :headers
      :wait_readable
    else
      @state = :pipelined
      # we shouldn't start processing the application again until we know
      # the socket is writable for the response
      :wait_writable
    end
  else
    # shutdown is needed in case the app forked, we rescue here since
    # StreamInput may issue shutdown as well
    shutdown rescue nil
    :close
  end
end

#http_response_write(status, headers, body) ⇒ Object

writes the rack_response to socket as an HTTP response returns :wait_readable, :wait_writable, :forget, or nil



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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/yahns/http_response.rb', line 131

def http_response_write(status, headers, body)
  status = CODES[status.to_i] || status
  offset = 0
  count = hijack = nil
  k = self.class
  alive = @hs.next? && k.persistent_connections
  flags = MSG_DONTWAIT

  if @hs.headers?
    buf = "#{response_start}#{status}\r\nDate: #{httpdate}\r\n"
    headers.each do |key, value|
      case key
      when %r{\ADate\z}i
        next
      when %r{\AContent-Range\z}i
        if %r{\Abytes (\d+)-(\d+)/\d+\z} =~ value
          offset = $1.to_i
          count = $2.to_i - offset + 1
        end
        buf << kv_str(key, value)
      when %r{\AConnection\z}i
        # allow Rack apps to tell us they want to drop the client
        alive = false if value =~ /\bclose\b/i
      when %r{\AContent-Length\z}i
        flags |= MSG_MORE if have_more?(value)
        buf << kv_str(key, value)
      when "rack.hijack"
        hijack = value
        body = nil # ensure we do not close body
      else
        buf << kv_str(key, value)
      end
    end
    buf << (alive ? CONN_KA : CONN_CLOSE)
    case rv = kgio_syssend(buf, flags)
    when nil # all done, likely
      break
    when String
      flags = MSG_DONTWAIT
      buf = rv # hope the skb grows
    when :wait_writable, :wait_readable
      if k.output_buffering
        alive = hijack ? hijack : alive
        rv = response_header_blocked(rv, buf, body, alive, offset, count)
        body = nil # ensure we do not close body in ensure
        return rv
      else
        response_wait_write(rv) or return :close
      end
    end while true
  end

  return response_hijacked(hijack) if hijack

  if body.respond_to?(:to_path)
    @state = body = Yahns::StreamFile.new(body, alive, offset, count)
    return step_write
  end

  wbuf = rv = nil
  body.each do |chunk|
    if wbuf
      rv = wbuf.wbuf_write(self, chunk)
    else
      case rv = kgio_trywrite(chunk)
      when nil # all done, likely and good!
        break
      when String
        chunk = rv # hope the skb grows when we loop into the trywrite
      when :wait_writable, :wait_readable
        if k.output_buffering
          wbuf = Yahns::Wbuf.new(body, alive, k.output_buffer_tmpdir)
          rv = wbuf.wbuf_write(self, chunk)
          break
        else
          response_wait_write(rv) or return :close
        end
      end while true
    end
  end

  # if we buffered the write body, we must return :wait_writable
  # (or :wait_readable for SSL) and hit Yahns::HttpClient#step_write
  if wbuf
    body = nil # ensure we do not close the body in ensure
    wbuf_maybe(wbuf, rv)
  else
    http_response_done(alive)
  end
ensure
  body.respond_to?(:close) and body.close
end

#httpdateObject



20
21
22
# File 'lib/yahns/http_response.rb', line 20

def httpdate
  MTX.synchronize { super }
end

#kgio_syssend(buf, flags) ⇒ Object



43
44
45
# File 'lib/yahns/http_response.rb', line 43

def kgio_syssend(buf, flags)
  kgio_trywrite(buf)
end

#kv_str(key, value) ⇒ Object



116
117
118
119
120
121
122
123
# File 'lib/yahns/http_response.rb', line 116

def kv_str(key, value)
  if value =~ /\n/
    # avoiding blank, key-only cookies with /\n+/
    value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
  else
    "#{key}: #{value}\r\n"
  end
end

#response_header_blocked(ret, header, body, alive, offset, count) ⇒ Object



65
66
67
68
69
70
71
72
73
74
# File 'lib/yahns/http_response.rb', line 65

def response_header_blocked(ret, header, body, alive, offset, count)
  if body.respond_to?(:to_path)
    alive = Yahns::StreamFile.new(body, alive, offset, count)
    body = nil
  end
  wbuf = Yahns::Wbuf.new(body, alive, self.class.output_buffer_tmpdir)
  rv = wbuf.wbuf_write(self, header)
  body.each { |chunk| rv = wbuf.wbuf_write(self, chunk) } if body
  wbuf_maybe(wbuf, rv)
end

#response_startObject



48
49
50
# File 'lib/yahns/http_response.rb', line 48

def response_start
  @response_start_sent ? Z : RESPONSE_START
end

#response_wait_write(rv) ⇒ Object



52
53
54
55
56
57
58
59
# File 'lib/yahns/http_response.rb', line 52

def response_wait_write(rv)
  # call the kgio_wait_readable or kgio_wait_writable method
  ok = __send__("kgio_#{rv}") and return ok
  k = self.class
  k.logger.info("fd=#{fileno} ip=#@kgio_addr timeout on :#{rv} after "\
                "#{k.client_timeout}s")
  false
end

#wbuf_maybe(wbuf, rv) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/yahns/http_response.rb', line 76

def wbuf_maybe(wbuf, rv)
  case rv # wbuf_write return value
  when nil # all done
    case rv = wbuf.wbuf_close(self)
    when :ignore # hijacked
      @state = rv
    when Yahns::StreamFile
      @state = rv
      :wait_writable
    when true, false
      http_response_done(rv)
    end
  else
    @state = wbuf
    rv
  end
end