[18425] | 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 | # Determin 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"
|
---|
| 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
|
---|