webricknio 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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