123456789_123456789_123456789_123456789_123456789_

Module: Unicorn::HttpResponse

Relationships & Source Files
Extension / Inclusion / Inheritance Descendants
Included In:
Defined in: lib/unicorn/http_response.rb,
ext/unicorn_http/httpdate.c

Constant Summary

Instance Method Summary

Instance Method Details

#append_header(buf, key, value)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/unicorn/http_response.rb', line 29

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

#err_response(code, response_start_sent)

This method is for internal use only.

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

[ GitHub ]

  
# File 'lib/unicorn/http_response.rb', line 24

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 = Unicorn::HttpRequest.new)

This method is for internal use only.

writes the rack_response to socket as an HTTP response

[ GitHub ]

  
# File 'lib/unicorn/http_response.rb', line 42

def http_response_write(socket, status, headers, body,
                        req = Unicorn::HttpRequest.new)
  hijack = nil
  do_chunk = false
  if headers
    code = status.to_i
    msg = STATUS_CODES[code]
    start = req.response_start_sent ? ''.freeze : 'HTTP/1.1 '.freeze
    term = STATUS_WITH_NO_ENTITY_BODY.include?(code) || false
    buf = "#{start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \
          "Date: #{httpdate}\r\n" \
          "Connection: close\r\n"
    headers.each do |key, value|
      case key
      when %r{\A(?:Date|Connection)\z}i
        next
      when %r{\AContent-Length\z}i
        append_header(buf, key, value)
        term = true
      when %r{\ATransfer-Encoding\z}i
        append_header(buf, key, value)
        term = true if /\bchunked\b/i === value # value may be Array :x
      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
    if !hijack && !term && req.chunkable_response?
      do_chunk = true
      buf << "Transfer-Encoding: chunked\r\n".freeze
    end
    socket.write(buf << "\r\n".freeze)
    buf.clear # remove this line if C Ruby gets escape analysis
  end

  if hijack
    req.hijacked!
    hijack.call(socket)
  elsif do_chunk
    begin
      body.each do |b|
        socket.write("#{b.bytesize.to_s(16)}\r\n", b, "\r\n".freeze)
      end
    ensure
      socket.write("0\r\n\r\n".freeze)
    end
  else
    body.each { |chunk| socket.write(chunk) }
  end
end

#httpdate

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.

[ GitHub ]

  
# File 'ext/unicorn_http/httpdate.c', line 44

static VALUE httpdate(VALUE self)
{
	static time_t last;
	struct timeval now;
	struct tm tm;

	/*
	 * Favor gettimeofday(2) over time(2), as the latter can return the
	 * wrong value in the first 1 .. 2.5 ms of every second(!)
	 *
	 * https://lore.kernel.org/git/20230320230507.3932018-1-gitster@pobox.com/
	 * https://inbox.sourceware.org/libc-alpha/20230306160321.2942372-1-adhemerval.zanella@linaro.org/T/
	 * https://sourceware.org/bugzilla/show_bug.cgi?id=30200
	 */
	if (gettimeofday(&now, NULL))
		rb_sys_fail("gettimeofday");

	if (last == now.tv_sec)
		return buf;
	last = now.tv_sec;
	gmtime_r(&now.tv_sec, &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;
}