Module: Pitchfork::HttpResponse

Included in:
HttpServer
Defined in:
lib/pitchfork/http_response.rb,
ext/pitchfork_http/httpdate.c

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(socket, status, headers, body)

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

Constant Summary collapse

STATUS_CODES =
defined?(Rack::Utils::HTTP_STATUS_CODES) ?
Rack::Utils::HTTP_STATUS_CODES : {}
ILLEGAL_HEADER_VALUE =
/[\x00-\x08\x0A-\x1F]/

Instance Method Summary collapse

Instance Method Details

#append_header(buf, key, value) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/pitchfork/http_response.rb', line 26

def append_header(buf, key, value)
  case value
  when Array # Rack 3
    value.each do |v|
      next if ILLEGAL_HEADER_VALUE.match?(v)
      buf << "#{key}: #{v}\r\n"
    end
  when /\n/ # Rack 2
    # avoiding blank, key-only cookies with /\n+/
    value.split(/\n+/).each do |v|
      next if ILLEGAL_HEADER_VALUE.match?(v)
      buf << "#{key}: #{v}\r\n"
    end
  else
    buf << "#{key}: #{value}\r\n"
  end
end

#err_response(code, response_start_sent) ⇒ Object

internal API, code will always be common-enough-for-even-old-Rack



21
22
23
24
# File 'lib/pitchfork/http_response.rb', line 21

def err_response(code, response_start_sent)
  "#{response_start_sent ? '' : 'HTTP/1.1 '}" \
    "#{code} #{STATUS_CODES[code]}\r\n\r\n"
end

#http_response_write(socket, status, headers, body, req = Pitchfork::HttpParser.new) ⇒ Object

writes the rack_response to socket as an HTTP response



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
# File 'lib/pitchfork/http_response.rb', line 45

def http_response_write(socket, status, headers, body,
                        req = Pitchfork::HttpParser.new)
  hijack = nil

  if headers
    code = status.to_i
    msg = STATUS_CODES[code]
    start = req.response_start_sent ? '' : 'HTTP/1.1 '
    buf = "#{start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \
          "Date: #{httpdate}\r\n" \
          "Connection: close\r\n".b
    headers.each do |key, value|
      case key
      when %r{\A(?:Date|Connection)\z}i
        next
      when "rack.hijack"
        # This should only be hit under Rack >= 1.5, as this was an illegal
        # key in Rack < 1.5
        hijack = value
      else
        append_header(buf, key, value)
      end
    end
    socket.write(buf << "\r\n")
  end

  if hijack
    req.hijacked!
    hijack.call(socket)
  elsif body.respond_to?(:each)
    body.each { |chunk| socket.write(chunk) }
  else
    body.call(socket)
  end
end

#httpdateObject

Returns a string which represents the time as rfc1123-date of HTTP-date defined by RFC 2616:

day-of-week, DD month-name CCYY hh:mm:ss GMT

Note that the result is always GMT.

This method is identical to Time#httpdate in the Ruby standard library, except it is implemented in C for performance. We always saw Time#httpdate at or near the top of the profiler output so we decided to rewrite this in C.

Caveats: it relies on a Ruby implementation with the global VM lock, a thread-safe version will be provided when a Unix-only, GVL-free Ruby implementation becomes viable.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'ext/pitchfork_http/httpdate.c', line 43

static VALUE httpdate(VALUE self)
{
  static time_t last;
  time_t now = time(NULL); /* not a syscall on modern 64-bit systems */
  struct tm tm;

  if (last == now)
    return buf;
  last = now;
  gmtime_r(&now, &tm);

  /* we can make this thread-safe later if our Ruby loses the GVL */
  snprintf(buf_ptr, buf_capa,
           "%s, %02d %s %4d %02d:%02d:%02d GMT",
           week + (tm.tm_wday * 4),
           tm.tm_mday,
           months + (tm.tm_mon * 4),
           tm.tm_year + 1900,
           tm.tm_hour,
           tm.tm_min,
           tm.tm_sec);

  return buf;
}