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

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 ' ].map!(&:freeze)
RESPONSE_START =
CCC_RESPONSE_START.join('')
R100_RAW =
"HTTP/1.1 100 Continue\r\n\r\n"
R100_CCC =
"100 Continue\r\n\r\nHTTP/1.1 "
HTTP_EXPECT =
"HTTP_EXPECT"

Instance Method Summary collapse

Instance Method Details

#do_cccObject

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



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 197

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



41
42
43
# File 'lib/yahns/http_response.rb', line 41

def err_response(code)
  "#{response_start}#{CODES[code]}\r\n\r\n"
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



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/yahns/http_response.rb', line 228

def http_100_response(env)
  env.delete(HTTP_EXPECT) =~ /\A100-continue\z/i or return nil
  buf = @response_start_sent ? R100_CCC : R100_RAW
  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



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

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



107
108
109
110
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
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
# File 'lib/yahns/http_response.rb', line 107

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

  if @hs.headers?
    buf = "#{response_start}#{status}\r\nDate: #{httpdate}\r\n"
    headers.each do |key, value|
      case key
      when %r{\ADate\z}
        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 "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_trywrite(buf)
    when nil # all done, likely
      break
    when String
      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

#kv_str(key, value) ⇒ Object



96
97
98
99
100
101
102
103
# File 'lib/yahns/http_response.rb', line 96

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



45
46
47
48
49
50
51
52
53
54
# File 'lib/yahns/http_response.rb', line 45

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



28
29
30
# File 'lib/yahns/http_response.rb', line 28

def response_start
  @response_start_sent ? Z : RESPONSE_START
end

#response_wait_write(rv) ⇒ Object



32
33
34
35
36
37
38
39
# File 'lib/yahns/http_response.rb', line 32

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



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/yahns/http_response.rb', line 56

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