webricknio 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,469 @@
1
+ #
2
+ # httpresponse.rb -- HTTPResponse Class
3
+ #
4
+ # Author: IPR -- Internet Programming with Ruby -- writers, Pradeep Singh
5
+ # Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
6
+ # Copyright (c) 2002 Internet Programming with Ruby writers
7
+ # Copyright (c) 2012 Pradeep Singh
8
+ # All rights reserved.
9
+ #
10
+ # $IPR: httpresponse.rb,v 1.45 2003/07/11 11:02:25 gotoyuzo Exp $
11
+
12
+ require 'time'
13
+ require 'webrick/httpversion'
14
+ require 'webrick/htmlutils'
15
+ require 'webrick/httputils'
16
+ require 'webrick/httpstatus'
17
+ require 'webrick/utils'
18
+
19
+ require 'java'
20
+
21
+ java_import 'java.nio.ByteBuffer'
22
+
23
+ module WEBrickNIO
24
+ ##
25
+ # An HTTP response.
26
+
27
+ class HTTPResponse
28
+ attr_reader :http_version, :status, :header
29
+ attr_reader :cookies
30
+ attr_accessor :reason_phrase
31
+
32
+ ##
33
+ # Body may be a String or IO subclass.
34
+
35
+ attr_accessor :body
36
+
37
+ attr_accessor :request_method, :request_uri, :request_http_version
38
+ attr_accessor :filename
39
+ attr_accessor :keep_alive
40
+ attr_reader :config, :sent_size
41
+
42
+ ##
43
+ # Creates a new HTTP response object
44
+
45
+ def initialize(config)
46
+ @config = config
47
+ @buffer_size = config[:OutputBufferSize]
48
+ @logger = config[:Logger]
49
+ @header = Hash.new
50
+ @status = ::WEBrick::HTTPStatus::RC_OK
51
+ @reason_phrase = nil
52
+ @http_version = ::WEBrick::HTTPVersion::convert(@config[:HTTPVersion])
53
+ @body = ''
54
+ @keep_alive = true
55
+ @cookies = []
56
+ @request_method = nil
57
+ @request_uri = nil
58
+ @request_http_version = @http_version # temporary
59
+ @chunked = false
60
+ @filename = nil
61
+ @sent_size = 0
62
+ end
63
+
64
+ ##
65
+ # The response's HTTP status line
66
+
67
+ def status_line
68
+ "HTTP/#@http_version #@status #@reason_phrase #{::WEBrick::CRLF}"
69
+ end
70
+
71
+ ##
72
+ # Sets the response's status to the +status+ code
73
+
74
+ def status=(status)
75
+ @status = status
76
+ @reason_phrase = ::WEBrick::HTTPStatus::reason_phrase(status)
77
+ end
78
+
79
+ ##
80
+ # Retrieves the response header +field+
81
+
82
+ def [](field)
83
+ @header[field.downcase]
84
+ end
85
+
86
+ ##
87
+ # Sets the response header +field+ to +value+
88
+
89
+ def []=(field, value)
90
+ @header[field.downcase] = value.to_s
91
+ end
92
+
93
+ ##
94
+ # The content-length header
95
+
96
+ def content_length
97
+ if len = self['content-length']
98
+ return Integer(len)
99
+ end
100
+ end
101
+
102
+ ##
103
+ # Sets the content-length header to +len+
104
+
105
+ def content_length=(len)
106
+ self['content-length'] = len.to_s
107
+ end
108
+
109
+ ##
110
+ # The content-type header
111
+
112
+ def content_type
113
+ self['content-type']
114
+ end
115
+
116
+ ##
117
+ # Sets the content-type header to +type+
118
+
119
+ def content_type=(type)
120
+ self['content-type'] = type
121
+ end
122
+
123
+ ##
124
+ # Iterates over each header in the resopnse
125
+
126
+ def each
127
+ @header.each{|field, value| yield(field, value) }
128
+ end
129
+
130
+ ##
131
+ # Will this response body be returned using chunked transfer-encoding?
132
+
133
+ def chunked?
134
+ @chunked
135
+ end
136
+
137
+ ##
138
+ # Enables chunked transfer encoding.
139
+
140
+ def chunked=(val)
141
+ @chunked = val ? true : false
142
+ end
143
+
144
+ ##
145
+ # Will this response's connection be kept alive?
146
+
147
+ def keep_alive?
148
+ @keep_alive
149
+ end
150
+
151
+ ##
152
+ # Sends the response on +socket+
153
+
154
+ def send_response(sock_channel)
155
+ if !sock_channel.is_open
156
+ @logger.debug "client closed connection"
157
+ @keep_alive = false
158
+ return
159
+ end
160
+
161
+ begin
162
+ buffer = ByteBuffer.wrap(get_header.to_java_bytes)
163
+ while(buffer.has_remaining)
164
+ num_sent = sock_channel.java_send :write, [Java::JavaNio::ByteBuffer], buffer
165
+ raise ::WEBrick::HTTPStatus::EOFError if num_sent == -1 # stream is dead
166
+ end
167
+ @logger.debug "header byte size sent: #{get_header.bytesize}"
168
+ buffer.clear
169
+ if @body && @body.length > 0
170
+ buffer = ByteBuffer.wrap(@body.to_java_bytes)
171
+ while(buffer.has_remaining)
172
+ num_sent = sock_channel.java_send :write, [Java::JavaNio::ByteBuffer], buffer
173
+ raise ::WEBrick::HTTPStatus::EOFError if num_sent == -1 # stream is dead
174
+ end
175
+ @logger.debug "body byte size sent: #{@body.bytesize}"
176
+ else
177
+ @logger.debug "no body to be sent"
178
+ end
179
+ rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN => ex
180
+ @logger.error(ex)
181
+ @keep_alive = false
182
+ rescue Exception => ex
183
+ if ex.java_class.name.nil?
184
+ @logger.error(ex)
185
+ else
186
+ @logger.debug "error sending response: #{ex.java_class.name}"
187
+ end
188
+ @keep_alive = false
189
+ end
190
+ end
191
+
192
+
193
+ ##
194
+ # Sets up the headers for sending
195
+
196
+ def setup_header()
197
+ @reason_phrase ||= ::WEBrick::HTTPStatus::reason_phrase(@status)
198
+ @header['server'] ||= @config[:ServerSoftware]
199
+ @header['date'] ||= Time.now.httpdate
200
+
201
+ # HTTP/0.9 features
202
+ if @request_http_version < "1.0"
203
+ @http_version = ::WEBrick::HTTPVersion.new("0.9")
204
+ @keep_alive = false
205
+ end
206
+
207
+ # HTTP/1.0 features
208
+ if @request_http_version < "1.1"
209
+ if chunked?
210
+ @chunked = false
211
+ ver = @request_http_version.to_s
212
+ msg = "chunked is set for an HTTP/#{ver} request. (ignored)"
213
+ @logger.warn(msg)
214
+ end
215
+ end
216
+
217
+ # Determine the message length (RFC2616 -- 4.4 Message Length)
218
+ if @status == 304 || @status == 204 || ::WEBrick::HTTPStatus::info?(@status)
219
+ @header.delete('content-length')
220
+ @body = ""
221
+ elsif chunked?
222
+ @header["transfer-encoding"] = "chunked"
223
+ @header.delete('content-length')
224
+ elsif %r{^multipart/byteranges} =~ @header['content-type']
225
+ @header.delete('content-length')
226
+ elsif @header['content-length'].nil?
227
+ unless @body.is_a?(IO)
228
+ @header['content-length'] = @body ? @body.bytesize : 0
229
+ end
230
+ end
231
+
232
+ # Keep-Alive connection.
233
+ if @header['connection'] == "close"
234
+ @keep_alive = false
235
+ elsif keep_alive?
236
+ if chunked? || @header['content-length'] || @status == 304 || @status == 204
237
+ @header['connection'] = "Keep-Alive"
238
+ else
239
+ msg = "Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true"
240
+ @logger.warn(msg)
241
+ @header['connection'] = "close"
242
+ @keep_alive = false
243
+ end
244
+ else
245
+ @header['connection'] = "close"
246
+ end
247
+
248
+ # Location is a single absoluteURI.
249
+ if location = @header['location']
250
+ if @request_uri
251
+ @header['location'] = @request_uri.merge(location)
252
+ end
253
+ end
254
+ end
255
+
256
+
257
+ ##
258
+ # Sends the headers on +socket+
259
+
260
+ def get_header
261
+ @final_header ||=
262
+ if @http_version.major > 0 then
263
+ setup_header()
264
+ data = status_line()
265
+ @header.each{|key, value|
266
+ tmp = key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }
267
+ data << "#{tmp}: #{value}" << ::WEBrick::CRLF
268
+ }
269
+ @cookies.each{|cookie|
270
+ data << "Set-Cookie: " << cookie.to_s << ::WEBrick::CRLF
271
+ }
272
+ data << ::WEBrick::CRLF
273
+ data
274
+ else
275
+ ""
276
+ end
277
+ end
278
+
279
+ ##
280
+ # Sends the body on +socket+
281
+
282
+ def send_body(socket)
283
+ case @body
284
+ when IO then send_body_io(socket)
285
+ else send_body_string(socket)
286
+ end
287
+ end
288
+
289
+ def to_s # :nodoc:
290
+ ret = ""
291
+ send_response(ret)
292
+ ret
293
+ end
294
+
295
+ ##
296
+ # Redirects to +url+ with a WEBrick::HTTPStatus::Redirect +status+.
297
+ #
298
+ # Example:
299
+ #
300
+ # res.set_redirect WEBrick::HTTPStatus::TemporaryRedirect
301
+
302
+ def set_redirect(status, url)
303
+ @body = "<HTML><A HREF=\"#{url.to_s}\">#{url.to_s}</A>.</HTML>\n"
304
+ @header['location'] = url.to_s
305
+ raise status
306
+ end
307
+
308
+ ##
309
+ # Creates an error page for exception +ex+ with an optional +backtrace+
310
+
311
+ def set_error(ex, backtrace=false)
312
+ case ex
313
+ when ::WEBrick::HTTPStatus::Status
314
+ @keep_alive = false if ::WEBrick::HTTPStatus::error?(ex.code)
315
+ self.status = ex.code
316
+ else
317
+ @keep_alive = false
318
+ self.status = ::WEBrick::HTTPStatus::RC_INTERNAL_SERVER_ERROR
319
+ end
320
+ @header['content-type'] = "text/html; charset=ISO-8859-1"
321
+
322
+ if respond_to?(:create_error_page)
323
+ create_error_page()
324
+ return
325
+ end
326
+
327
+ if @request_uri
328
+ host, port = @request_uri.host, @request_uri.port
329
+ else
330
+ host, port = @config[:ServerName], @config[:Port]
331
+ end
332
+
333
+ @body = ''
334
+ @body << <<-_end_of_html_
335
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
336
+ <HTML>
337
+ <HEAD><TITLE>#{::WEBrick::HTMLUtils::escape(@reason_phrase)}</TITLE></HEAD>
338
+ <BODY>
339
+ <H1>#{::WEBrick::HTMLUtils::escape(@reason_phrase)}</H1>
340
+ #{::WEBrick::HTMLUtils::escape(ex.message)}
341
+ <HR>
342
+ _end_of_html_
343
+
344
+ if backtrace && $DEBUG
345
+ @body << "backtrace of `#{::WEBrick::HTMLUtils::escape(ex.class.to_s)}' "
346
+ @body << "#{::WEBrick::HTMLUtils::escape(ex.message)}"
347
+ @body << "<PRE>"
348
+ ex.backtrace.each{|line| @body << "\t#{line}\n"}
349
+ @body << "</PRE><HR>"
350
+ end
351
+
352
+ @body << <<-_end_of_html_
353
+ <ADDRESS>
354
+ #{::WEBrick::HTMLUtils::escape(@config[:ServerSoftware])} at
355
+ #{host}:#{port}
356
+ </ADDRESS>
357
+ </BODY>
358
+ </HTML>
359
+ _end_of_html_
360
+ end
361
+
362
+ def create_error_page()
363
+ if @request_uri
364
+ host, port = @request_uri.host, @request_uri.port
365
+ else
366
+ #host, port = @config[:ServerName], @config[:Port]
367
+ end
368
+
369
+ @body << <<-_end_of_html_
370
+ <!DOCTYPE html>
371
+ <HTML>
372
+ <HEAD><TITLE>#{::WEBrick::HTMLUtils::escape(@reason_phrase)}</TITLE></HEAD>
373
+ <BODY>
374
+ <H1>#{::WEBrick::HTMLUtils::escape(@reason_phrase)}</H1>
375
+ <HR>
376
+ <ADDRESS>
377
+ #{::WEBrick::HTMLUtils::escape(@config[:ServerSoftware])}
378
+ _end_of_html_
379
+
380
+ if defined?(host) && defined?(port) && !host.nil? && !port.nil?
381
+ @body << <<-_end_of_html_
382
+ at
383
+ #{host}:#{port}
384
+ _end_of_html_
385
+ end
386
+
387
+ @body << <<-_end_of_html_
388
+ </ADDRESS>
389
+ </BODY>
390
+ </HTML>
391
+ _end_of_html_
392
+ end
393
+
394
+
395
+ private
396
+
397
+
398
+ def send_body_io(socket)
399
+ begin
400
+ if @request_method == "HEAD"
401
+ # do nothing
402
+ elsif chunked?
403
+ while buf = @body.read(@buffer_size)
404
+ next if buf.empty?
405
+ data = ""
406
+ data << format("%x", buf.bytesize) << CRLF
407
+ data << buf << CRLF
408
+ _write_data(socket, data)
409
+ @sent_size += buf.bytesize
410
+ end
411
+ _write_data(socket, "0#{CRLF}#{CRLF}")
412
+ else
413
+ size = @header['content-length'].to_i
414
+ _send_file(socket, @body, 0, size)
415
+ @sent_size = size
416
+ end
417
+ ensure
418
+ @body.close
419
+ end
420
+ end
421
+
422
+ def send_body_string(socket)
423
+ if @request_method == "HEAD"
424
+ # do nothing
425
+ elsif chunked?
426
+ body ? @body.bytesize : 0
427
+ while buf = @body[@sent_size, @buffer_size]
428
+ break if buf.empty?
429
+ data = ""
430
+ data << format("%x", buf.bytesize) << CRLF
431
+ data << buf << CRLF
432
+ _write_data(socket, data)
433
+ @sent_size += buf.bytesize
434
+ end
435
+ _write_data(socket, "0#{CRLF}#{CRLF}")
436
+ else
437
+ if @body && @body.bytesize > 0
438
+ _write_data(socket, @body)
439
+ @sent_size = @body.bytesize
440
+ end
441
+ end
442
+ end
443
+
444
+ def _send_file(output, input, offset, size)
445
+ while offset > 0
446
+ sz = @buffer_size < size ? @buffer_size : size
447
+ buf = input.read(sz)
448
+ offset -= buf.bytesize
449
+ end
450
+
451
+ if size == 0
452
+ while buf = input.read(@buffer_size)
453
+ _write_data(output, buf)
454
+ end
455
+ else
456
+ while size > 0
457
+ sz = @buffer_size < size ? @buffer_size : size
458
+ buf = input.read(sz)
459
+ _write_data(output, buf)
460
+ size -= buf.bytesize
461
+ end
462
+ end
463
+ end
464
+
465
+ def _write_data(socket, data)
466
+ socket << data
467
+ end
468
+ end
469
+ end