123456789_123456789_123456789_123456789_123456789_

Class: WEBrick::HTTPResponse

Relationships & Source Files
Namespace Children
Classes:
Exceptions:
Inherits: Object
Defined in: lib/webrick/httpresponse.rb

Overview

An HTTP response. This is filled in by the service or do_* methods of a ::WEBrick HTTP Servlet.

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(config) ⇒ HTTPResponse

Creates a new HTTP response object. Config::HTTP is the default configuration.

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 112

def initialize(config)
  @config = config
  @buffer_size = config[:OutputBufferSize]
  @logger = config[:Logger]
  @header = Hash.new
  @status = HTTPStatus::RC_OK
  @reason_phrase = nil
  @http_version = HTTPVersion::convert(@config[:HTTPVersion])
  @body = ''
  @keep_alive = true
  @cookies = []
  @request_method = nil
  @request_uri = nil
  @request_http_version = @http_version  # temporary
  @chunked = false
  @filename = nil
  @sent_size = 0
  @bodytempfile = nil
end

Instance Attribute Details

#body (rw)

Body may be:

  • a String;

  • an IO-like object that responds to #read and #readpartial;

  • a Proc-like object that responds to #call.

In the latter case, either #chunked= should be set to true, or header['content-length'] explicitly provided. Example:

server.mount_proc '/' do |req, res|
  res.chunked = true
  # or
  # res.header['content-length'] = 10
  res.body = proc { |out| out.write(Time.now.to_s) }
end
[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 70

attr_accessor :body

#chunked=(val) (rw)

Enables chunked transfer encoding.

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 209

def chunked=(val)
  @chunked = val ? true : false
end

#chunked?Boolean (rw)

Will this response body be returned using chunked transfer-encoding?

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 202

def chunked?
  @chunked
end

#config (readonly)

Configuration for this response

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 101

attr_reader :config

#content_length (rw)

The content-length header

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 165

def content_length
  if len = self['content-length']
    return Integer(len)
  end
end

#content_length=(len) (rw)

Sets the content-length header to len

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 174

def content_length=(len)
  self['content-length'] = len.to_s
end

#content_type (rw)

The content-type header

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 181

def content_type
  self['content-type']
end

#content_type=(type) (rw)

Sets the content-type header to type

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 188

def content_type=(type)
  self['content-type'] = type
end

#cookies (readonly)

Response cookies

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 46

attr_reader :cookies

#filename (rw)

Filename of the static file in this response. Only used by the FileHandler servlet.

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 91

attr_accessor :filename

#header (readonly)

Response header

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 41

attr_reader :header

#http_version (readonly)

HTTP Response version

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 31

attr_reader :http_version

#keep_alive (rw)

Is this a keep-alive response?

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 96

attr_accessor :keep_alive

#keep_alive?Boolean (rw)

Will this response’s connection be kept alive?

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 216

def keep_alive?
  @keep_alive
end

#reason_phrase (rw)

Response reason phrase (“OK”)

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 51

attr_accessor :reason_phrase

#request_http_version (rw)

Request HTTP version for this response

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 85

attr_accessor :request_http_version

#request_method (rw)

Request method for this response

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 75

attr_accessor :request_method

#request_uri (rw)

Request URI for this response

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 80

attr_accessor :request_uri

#sent_size (readonly)

Bytes sent in this response

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 106

attr_reader :sent_size

#status (rw)

Response status code (200)

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 36

attr_reader :status

#status=(status) (rw)

Sets the response’s status to the #status code

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 142

def status=(status)
  @status = status
  @reason_phrase = HTTPStatus::reason_phrase(status)
end

Instance Method Details

#[](field)

Retrieves the response header field

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 150

def [](field)
  @header[field.downcase]
end

#[]=(field, value)

Sets the response header field to value

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 157

def []=(field, value)
  @chunked = value.to_s.downcase == 'chunked' if field.downcase == 'transfer-encoding'
  @header[field.downcase] = value.to_s
end

#_write_data(socket, data)

This method is for internal use only.

preserved for compatibility with some 3rd-party handlers

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 557

def _write_data(socket, data)
  socket << data
end

#check_header(header_value) (private)

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 410

def check_header(header_value)
  header_value = header_value.to_s
  if /[\r\n]/ =~ header_value
    raise InvalidHeader
  else
    header_value
  end
end

#each

Iterates over each header in the response

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 195

def each
  @header.each{|field, value|  yield(field, value) }
end

#error_body(backtrace, ex, host, port) (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 421

def error_body(backtrace, ex, host, port)
  @body = ''
  @body << <<-_end_of_html_
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
<HTML>
  <HEAD><TITLE>#{HTMLUtils::escape(@reason_phrase)}</TITLE></HEAD>
  <BODY>
<H1>#{HTMLUtils::escape(@reason_phrase)}</H1>
#{HTMLUtils::escape(ex.message)}
<HR>
  _end_of_html_

  if backtrace && $DEBUG
    @body << "backtrace of `#{HTMLUtils::escape(ex.class.to_s)}' "
    @body << "#{HTMLUtils::escape(ex.message)}"
    @body << "<PRE>"
    ex.backtrace.each{|line| @body << "\t#{line}\n"}
    @body << "</PRE><HR>"
  end

  @body << <<-_end_of_html_
<ADDRESS>
 #{HTMLUtils::escape(@config[:ServerSoftware])} at
 #{host}:#{port}
</ADDRESS>
  </BODY>
</HTML>
  _end_of_html_
end

#make_body_tempfile

This method is for internal use only.
[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 303

def make_body_tempfile # :nodoc:
  return if @bodytempfile
  bodytempfile = Tempfile.create("webrick")
  if @body.nil?
    # nothing
  elsif @body.respond_to? :readpartial
    IO.copy_stream(@body, bodytempfile)
    @body.close
  elsif @body.respond_to? :call
    @body.call(bodytempfile)
  else
    bodytempfile.write @body
  end
  bodytempfile.rewind
  @body = @bodytempfile = bodytempfile
  @header['content-length'] = bodytempfile.stat.size.to_s
end

#remove_body_tempfile

This method is for internal use only.
[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 321

def remove_body_tempfile # :nodoc:
  if @bodytempfile
    @bodytempfile.close
    File.unlink @bodytempfile.path
    @bodytempfile = nil
  end
end

#send_body(socket)

This method is for internal use only.

Sends the body on socket

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 356

def send_body(socket) # :nodoc:
  if @body.respond_to? :readpartial then
    send_body_io(socket)
  elsif @body.respond_to?(:call) then
    send_body_proc(socket)
  else
    send_body_string(socket)
  end
end

#send_body_io(socket) (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 451

def send_body_io(socket)
  begin
    if @request_method == "HEAD"
      # do nothing
    elsif chunked?
      buf  = ''
      begin
        @body.readpartial(@buffer_size, buf)
        size = buf.bytesize
        data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
        socket.write(data)
        data.clear
        @sent_size += size
      rescue EOFError
        break
      end while true
      buf.clear
      socket.write("0#{CRLF}#{CRLF}")
    else
      if %r{\Abytes (\d)-(\d)/\d+\z} =~ @header['content-range']
        offset = $1.to_i
        size = $2.to_i - offset + 1
      else
        offset = nil
        size = @header['content-length']
        size = size.to_i if size
      end
      begin
        @sent_size = IO.copy_stream(@body, socket, size, offset)
      rescue NotImplementedError
        @body.seek(offset, IO::SEEK_SET)
        @sent_size = IO.copy_stream(@body, socket, size)
      end
    end
  ensure
    @body.close
  end
  remove_body_tempfile
end

#send_body_proc(socket) (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 513

def send_body_proc(socket)
  if @request_method == "HEAD"
    # do nothing
  elsif chunked?
    @body.call(ChunkedWrapper.new(socket, self))
    socket.write("0#{CRLF}#{CRLF}")
  else
    size = @header['content-length'].to_i
    if @bodytempfile
      @bodytempfile.rewind
      IO.copy_stream(@bodytempfile, socket)
    else
      @body.call(socket)
    end
    @sent_size = size
  end
end

#send_body_string(socket) (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 491

def send_body_string(socket)
  if @request_method == "HEAD"
    # do nothing
  elsif chunked?
    body ? @body.bytesize : 0
    while buf = @body[@sent_size, @buffer_size]
      break if buf.empty?
      size = buf.bytesize
      data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
      buf.clear
      socket.write(data)
      @sent_size += size
    end
    socket.write("0#{CRLF}#{CRLF}")
  else
    if @body && @body.bytesize > 0
      socket.write(@body)
      @sent_size = @body.bytesize
    end
  end
end

#send_header(socket)

This method is for internal use only.

Sends the headers on socket

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 333

def send_header(socket) # :nodoc:
  if @http_version.major > 0
    data = status_line()
    @header.each{|key, value|
      tmp = key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }
      data << "#{tmp}: #{check_header(value)}" << CRLF
    }
    @cookies.each{|cookie|
      data << "Set-Cookie: " << check_header(cookie.to_s) << CRLF
    }
    data << CRLF
    socket.write(data)
  end
rescue InvalidHeader => e
  @header.clear
  @cookies.clear
  set_error e
  retry
end

#send_response(socket)

This method is for internal use only.

Sends the response on socket

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 223

def send_response(socket) # :nodoc:
  begin
    setup_header()
    send_header(socket)
    send_body(socket)
  rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN => ex
    @logger.debug(ex)
    @keep_alive = false
  rescue Exception => ex
    @logger.error(ex)
    @keep_alive = false
  end
end

#set_error(ex, backtrace = false)

Creates an error page for exception ex with an optional backtrace

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 383

def set_error(ex, backtrace=false)
  case ex
  when HTTPStatus::Status
    @keep_alive = false if HTTPStatus::error?(ex.code)
    self.status = ex.code
  else
    @keep_alive = false
    self.status = HTTPStatus::RC_INTERNAL_SERVER_ERROR
  end
  @header['content-type'] = "text/html; charset=ISO-8859-1"

  if respond_to?(:create_error_page)
    create_error_page()
    return
  end

  if @request_uri
    host, port = @request_uri.host, @request_uri.port
  else
    host, port = @config[:ServerName], @config[:Port]
  end

  error_body(backtrace, ex, host, port)
end

#set_redirect(status, url)

Redirects to url with a HTTPStatus::Redirect #status.

Example:

res.set_redirect WEBrick::HTTPStatus::TemporaryRedirect
[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 373

def set_redirect(status, url)
  url = URI(url).to_s
  @body = "<HTML><A HREF=\"#{url}\">#{url}</A>.</HTML>\n"
  @header['location'] = url
  raise status
end

#setup_header

This method is for internal use only.

Sets up the headers for sending

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 240

def setup_header() # :nodoc:
  @reason_phrase    ||= HTTPStatus::reason_phrase(@status)
  @header['server'] ||= @config[:ServerSoftware]
  @header['date']   ||= Time.now.httpdate

  # HTTP/0.9 features
  if @request_http_version < "1.0"
    @http_version = HTTPVersion.new("0.9")
    @keep_alive = false
  end

  # HTTP/1.0 features
  if @request_http_version < "1.1"
    if chunked?
      @chunked = false
      ver = @request_http_version.to_s
      msg = "chunked is set for an HTTP/#{ver} request. (ignored)"
      @logger.warn(msg)
    end
  end

  # Determine the message length (RFC2616 -- 4.4 Message Length)
  if @status == 304 || @status == 204 || HTTPStatus::info?(@status)
    @header.delete('content-length')
    @body = ""
  elsif chunked?
    @header["transfer-encoding"] = "chunked"
    @header.delete('content-length')
  elsif %r{^multipart/byteranges} =~ @header['content-type']
    @header.delete('content-length')
  elsif @header['content-length'].nil?
    if @body.respond_to? :readpartial
    elsif @body.respond_to? :call
      make_body_tempfile
    else
      @header['content-length'] = (@body ? @body.bytesize : 0).to_s
    end
  end

  # Keep-Alive connection.
  if @header['connection'] == "close"
     @keep_alive = false
  elsif keep_alive?
    if chunked? || @header['content-length'] || @status == 304 || @status == 204 || HTTPStatus.info?(@status)
      @header['connection'] = "Keep-Alive"
    else
      msg = "Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true"
      @logger.warn(msg)
      @header['connection'] = "close"
      @keep_alive = false
    end
  else
    @header['connection'] = "close"
  end

  # Location is a single absoluteURI.
  if location = @header['location']
    if @request_uri
      @header['location'] = @request_uri.merge(location).to_s
    end
  end
end

#status_line

The response’s HTTP status line

[ GitHub ]

  
# File 'lib/webrick/httpresponse.rb', line 135

def status_line
  "HTTP/#@http_version #@status #@reason_phrase".rstrip << CRLF
end