File: webrick/httpresponse.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: WEBrick#17
  class: HTTPResponse#18
inherits from
  Object ( Builtin-Module )
has properties
constant: BUFSIZE #19
attribute: http_version [R] #21
attribute: status [R] #21
attribute: header [R] #21
attribute: cookies [R] #22
attribute: reason_phrase [RW] #23
attribute: body [RW] #24
attribute: request_method [RW] #26
attribute: request_uri [RW] #26
attribute: request_http_version [RW] #26
attribute: filename [RW] #27
attribute: keep_alive [RW] #28
attribute: config [R] #29
attribute: sent_size [R] #29
method: initialize / 1 #31
method: status_line #49
method: status= / 1 #53
method: [] / 1 #58
method: []= / 2 #62
method: content_length #66
method: content_length= / 1 #72
method: content_type #76
method: content_type= / 1 #80
method: each #84
method: chunked? #88
method: chunked= / 1 #92
method: keep_alive? #96
method: send_response / 1 #100
method: setup_header #114
method: send_header / 1 #169
method: send_body / 1 #184
method: to_s #191
method: set_redirect / 2 #197
method: set_error / 2 #203
method: send_body_io / 1 #256
method: send_body_string / 1 #280
method: _send_file / 4 #302
method: _write_data / 2 #323

Class Hierarchy

Object ( Builtin-Module )
  HTTPResponse ( WEBrick ) #18

Code

   1  #
   2  # httpresponse.rb -- HTTPResponse Class
   3  #
   4  # Author: IPR -- Internet Programming with Ruby -- writers
   5  # Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
   6  # Copyright (c) 2002 Internet Programming with Ruby writers. All rights
   7  # reserved.
   8  #
   9  # $IPR: httpresponse.rb,v 1.45 2003/07/11 11:02:25 gotoyuzo Exp $
  10 
  11  require 'time'
  12  require 'webrick/httpversion'
  13  require 'webrick/htmlutils'
  14  require 'webrick/httputils'
  15  require 'webrick/httpstatus'
  16 
  17  module WEBrick
  18    class HTTPResponse
  19      BUFSIZE = 1024*4
  20 
  21      attr_reader :http_version, :status, :header
  22      attr_reader :cookies
  23      attr_accessor :reason_phrase
  24      attr_accessor :body
  25 
  26      attr_accessor :request_method, :request_uri, :request_http_version
  27      attr_accessor :filename
  28      attr_accessor :keep_alive
  29      attr_reader :config, :sent_size
  30 
  31      def initialize(config)
  32        @config = config
  33        @logger = config[:Logger]
  34        @header = Hash.new
  35        @status = HTTPStatus::RC_OK
  36        @reason_phrase = nil
  37        @http_version = HTTPVersion::convert(@config[:HTTPVersion])
  38        @body = ''
  39        @keep_alive = true
  40        @cookies = []
  41        @request_method = nil
  42        @request_uri = nil
  43        @request_http_version = @http_version  # temporary
  44        @chunked = false
  45        @filename = nil
  46        @sent_size = 0
  47      end
  48 
  49      def status_line
  50        "HTTP/#@http_version #@status #@reason_phrase #{CRLF}"
  51      end
  52 
  53      def status=(status)
  54        @status = status
  55        @reason_phrase = HTTPStatus::reason_phrase(status)
  56      end
  57 
  58      def [](field)
  59        @header[field.downcase]
  60      end
  61 
  62      def []=(field, value)
  63        @header[field.downcase] = value.to_s
  64      end
  65 
  66      def content_length
  67        if len = self['content-length']
  68          return Integer(len)
  69        end
  70      end
  71 
  72      def content_length=(len)
  73        self['content-length'] = len.to_s
  74      end
  75 
  76      def content_type
  77        self['content-type']
  78      end
  79 
  80      def content_type=(type)
  81        self['content-type'] = type
  82      end
  83 
  84      def each
  85        @header.each{|k, v|  yield(k, v) }
  86      end
  87 
  88      def chunked?
  89        @chunked
  90      end
  91 
  92      def chunked=(val)
  93        @chunked = val ? true : false
  94      end
  95 
  96      def keep_alive?
  97        @keep_alive
  98      end
  99 
 100      def send_response(socket)
 101        begin
 102          setup_header()
 103          send_header(socket)
 104          send_body(socket)
 105        rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN => ex
 106          @logger.debug(ex)
 107          @keep_alive = false
 108        rescue Exception => ex
 109          @logger.error(ex)
 110          @keep_alive = false
 111        end
 112      end
 113 
 114      def setup_header()
 115        @reason_phrase    ||= HTTPStatus::reason_phrase(@status)
 116        @header['server'] ||= @config[:ServerSoftware]
 117        @header['date']   ||= Time.now.httpdate
 118 
 119        # HTTP/0.9 features
 120        if @request_http_version < "1.0"
 121          @http_version = HTTPVersion.new("0.9")
 122          @keep_alive = false
 123        end
 124 
 125        # HTTP/1.0 features
 126        if @request_http_version < "1.1"
 127          if chunked?
 128            @chunked = false
 129            ver = @request_http_version.to_s
 130            msg = "chunked is set for an HTTP/#{ver} request. (ignored)"
 131            @logger.warn(msg)
 132          end
 133        end
 134 
 135        # Determine the message length (RFC2616 -- 4.4 Message Length)
 136        if @status == 304 || @status == 204 || HTTPStatus::info?(@status)
 137          @header.delete('content-length')
 138          @body = ""
 139        elsif chunked?
 140          @header["transfer-encoding"] = "chunked"
 141          @header.delete('content-length')
 142        elsif %r{^multipart/byteranges} =~ @header['content-type']
 143          @header.delete('content-length')
 144        elsif @header['content-length'].nil?
 145          unless @body.is_a?(IO)
 146            @header['content-length'] = @body ? @body.size : 0
 147          end
 148        end
 149 
 150        # Keep-Alive connection.
 151        if @header['connection'] == "close"
 152           @keep_alive = false
 153        elsif keep_alive?
 154          if chunked? || @header['content-length']
 155            @header['connection'] = "Keep-Alive"
 156          end
 157        else
 158          @header['connection'] = "close"
 159        end
 160 
 161        # Location is a single absoluteURI.
 162        if location = @header['location']
 163          if @request_uri
 164            @header['location'] = @request_uri.merge(location)
 165          end
 166        end
 167      end
 168 
 169      def send_header(socket)
 170        if @http_version.major > 0
 171          data = status_line()
 172          @header.each{|key, value|
 173            tmp = key.gsub(/\bwww|^te$|\b\w/){|s| s.upcase }
 174            data << "#{tmp}: #{value}" << CRLF
 175          }
 176          @cookies.each{|cookie|
 177            data << "Set-Cookie: " << cookie.to_s << CRLF
 178          }
 179          data << CRLF
 180          _write_data(socket, data)
 181        end
 182      end
 183 
 184      def send_body(socket)
 185        case @body
 186        when IO then send_body_io(socket)
 187        else send_body_string(socket)
 188        end
 189      end
 190 
 191      def to_s
 192        ret = ""
 193        send_response(ret)
 194        ret
 195      end
 196 
 197      def set_redirect(status, url)
 198        @body = "<HTML><A HREF=\"#{url.to_s}\">#{url.to_s}</A>.</HTML>\n"
 199        @header['location'] = url.to_s
 200        raise status
 201      end
 202 
 203      def set_error(ex, backtrace=false)
 204        case ex
 205        when HTTPStatus::Status 
 206          @keep_alive = false if HTTPStatus::error?(ex.code)
 207          self.status = ex.code
 208        else 
 209          @keep_alive = false
 210          self.status = HTTPStatus::RC_INTERNAL_SERVER_ERROR
 211        end
 212        @header['content-type'] = "text/html; charset=ISO-8859-1"
 213 
 214        if respond_to?(:create_error_page)
 215          create_error_page()
 216          return
 217        end
 218 
 219        if @request_uri
 220          host, port = @request_uri.host, @request_uri.port
 221        else
 222          host, port = @config[:ServerName], @config[:Port]
 223        end
 224 
 225        @body = ''
 226        @body << <<-_end_of_html_
 227  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
 228  <HTML>
 229    <HEAD><TITLE>#{HTMLUtils::escape(@reason_phrase)}</TITLE></HEAD>
 230    <BODY>
 231      <H1>#{HTMLUtils::escape(@reason_phrase)}</H1>
 232      #{HTMLUtils::escape(ex.message)}
 233      <HR>
 234        _end_of_html_
 235 
 236        if backtrace && $DEBUG
 237          @body << "backtrace of `#{HTMLUtils::escape(ex.class.to_s)}' "
 238          @body << "#{HTMLUtils::escape(ex.message)}"
 239          @body << "<PRE>"
 240          ex.backtrace.each{|line| @body << "\t#{line}\n"}
 241          @body << "</PRE><HR>"
 242        end
 243 
 244        @body << <<-_end_of_html_
 245      <ADDRESS>
 246       #{HTMLUtils::escape(@config[:ServerSoftware])} at
 247       #{host}:#{port}
 248      </ADDRESS>
 249    </BODY>
 250  </HTML>
 251        _end_of_html_
 252      end
 253 
 254      private
 255 
 256      def send_body_io(socket)
 257        begin
 258          if @request_method == "HEAD"
 259            # do nothing
 260          elsif chunked?
 261            while buf = @body.read(BUFSIZE)
 262              next if buf.empty?
 263              data = ""
 264              data << format("%x", buf.size) << CRLF
 265              data << buf << CRLF
 266              _write_data(socket, data)
 267              @sent_size += buf.size
 268            end
 269            _write_data(socket, "0#{CRLF}#{CRLF}")
 270          else
 271            size = @header['content-length'].to_i
 272            _send_file(socket, @body, 0, size)
 273            @sent_size = size
 274          end
 275        ensure
 276          @body.close
 277        end
 278      end
 279 
 280      def send_body_string(socket)
 281        if @request_method == "HEAD"
 282          # do nothing
 283        elsif chunked?
 284          remain = body ? @body.size : 0
 285          while buf = @body[@sent_size, BUFSIZE]
 286            break if buf.empty?
 287            data = ""
 288            data << format("%x", buf.size) << CRLF
 289            data << buf << CRLF
 290            _write_data(socket, data)
 291            @sent_size += buf.size
 292          end
 293          _write_data(socket, "0#{CRLF}#{CRLF}")
 294        else
 295          if @body && @body.size > 0
 296            _write_data(socket, @body)
 297            @sent_size = @body.size
 298          end
 299        end
 300      end
 301 
 302      def _send_file(output, input, offset, size)
 303        while offset > 0
 304          sz = BUFSIZE < offset ? BUFSIZE : offset
 305          buf = input.read(sz)
 306          offset -= buf.size
 307        end
 308 
 309        if size == 0
 310          while buf = input.read(BUFSIZE)
 311            _write_data(output, buf)
 312          end
 313        else
 314          while size > 0
 315            sz = BUFSIZE < size ? BUFSIZE : size
 316            buf = input.read(sz)
 317            _write_data(output, buf)
 318            size -= buf.size
 319          end
 320        end
 321      end
 322 
 323      def _write_data(socket, data)
 324        socket << data
 325      end
 326    end
 327  end