OSDN Git Service

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