rubysl-webrick 1.0.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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +25 -0
  6. data/README.md +29 -0
  7. data/Rakefile +1 -0
  8. data/lib/rubysl/webrick.rb +2 -0
  9. data/lib/rubysl/webrick/version.rb +5 -0
  10. data/lib/rubysl/webrick/webrick.rb +29 -0
  11. data/lib/webrick.rb +1 -0
  12. data/lib/webrick/accesslog.rb +67 -0
  13. data/lib/webrick/cgi.rb +257 -0
  14. data/lib/webrick/compat.rb +15 -0
  15. data/lib/webrick/config.rb +97 -0
  16. data/lib/webrick/cookie.rb +110 -0
  17. data/lib/webrick/htmlutils.rb +25 -0
  18. data/lib/webrick/httpauth.rb +45 -0
  19. data/lib/webrick/httpauth/authenticator.rb +79 -0
  20. data/lib/webrick/httpauth/basicauth.rb +65 -0
  21. data/lib/webrick/httpauth/digestauth.rb +343 -0
  22. data/lib/webrick/httpauth/htdigest.rb +91 -0
  23. data/lib/webrick/httpauth/htgroup.rb +61 -0
  24. data/lib/webrick/httpauth/htpasswd.rb +83 -0
  25. data/lib/webrick/httpauth/userdb.rb +29 -0
  26. data/lib/webrick/httpproxy.rb +254 -0
  27. data/lib/webrick/httprequest.rb +365 -0
  28. data/lib/webrick/httpresponse.rb +327 -0
  29. data/lib/webrick/https.rb +63 -0
  30. data/lib/webrick/httpserver.rb +210 -0
  31. data/lib/webrick/httpservlet.rb +22 -0
  32. data/lib/webrick/httpservlet/abstract.rb +71 -0
  33. data/lib/webrick/httpservlet/cgi_runner.rb +45 -0
  34. data/lib/webrick/httpservlet/cgihandler.rb +104 -0
  35. data/lib/webrick/httpservlet/erbhandler.rb +54 -0
  36. data/lib/webrick/httpservlet/filehandler.rb +398 -0
  37. data/lib/webrick/httpservlet/prochandler.rb +33 -0
  38. data/lib/webrick/httpstatus.rb +126 -0
  39. data/lib/webrick/httputils.rb +391 -0
  40. data/lib/webrick/httpversion.rb +49 -0
  41. data/lib/webrick/log.rb +88 -0
  42. data/lib/webrick/server.rb +200 -0
  43. data/lib/webrick/ssl.rb +126 -0
  44. data/lib/webrick/utils.rb +100 -0
  45. data/lib/webrick/version.rb +13 -0
  46. data/rubysl-webrick.gemspec +23 -0
  47. metadata +145 -0
@@ -0,0 +1,365 @@
1
+ #
2
+ # httprequest.rb -- HTTPRequest 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: httprequest.rb,v 1.64 2003/07/13 17:18:22 gotoyuzo Exp $
10
+
11
+ require 'timeout'
12
+ require 'uri'
13
+
14
+ require 'webrick/httpversion'
15
+ require 'webrick/httpstatus'
16
+ require 'webrick/httputils'
17
+ require 'webrick/cookie'
18
+
19
+ module WEBrick
20
+
21
+ class HTTPRequest
22
+ BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ]
23
+ BUFSIZE = 1024*4
24
+
25
+ # Request line
26
+ attr_reader :request_line
27
+ attr_reader :request_method, :unparsed_uri, :http_version
28
+
29
+ # Request-URI
30
+ attr_reader :request_uri, :host, :port, :path
31
+ attr_accessor :script_name, :path_info, :query_string
32
+
33
+ # Header and entity body
34
+ attr_reader :raw_header, :header, :cookies
35
+ attr_reader :accept, :accept_charset
36
+ attr_reader :accept_encoding, :accept_language
37
+
38
+ # Misc
39
+ attr_accessor :user
40
+ attr_reader :addr, :peeraddr
41
+ attr_reader :attributes
42
+ attr_reader :keep_alive
43
+ attr_reader :request_time
44
+
45
+ def initialize(config)
46
+ @config = config
47
+ @logger = config[:Logger]
48
+
49
+ @request_line = @request_method =
50
+ @unparsed_uri = @http_version = nil
51
+
52
+ @request_uri = @host = @port = @path = nil
53
+ @script_name = @path_info = nil
54
+ @query_string = nil
55
+ @query = nil
56
+ @form_data = nil
57
+
58
+ @raw_header = Array.new
59
+ @header = nil
60
+ @cookies = []
61
+ @accept = []
62
+ @accept_charset = []
63
+ @accept_encoding = []
64
+ @accept_language = []
65
+ @body = ""
66
+
67
+ @addr = @peeraddr = nil
68
+ @attributes = {}
69
+ @user = nil
70
+ @keep_alive = false
71
+ @request_time = nil
72
+
73
+ @remaining_size = nil
74
+ @socket = nil
75
+ end
76
+
77
+ def parse(socket=nil)
78
+ @socket = socket
79
+ begin
80
+ @peeraddr = socket.respond_to?(:peeraddr) ? socket.peeraddr : []
81
+ @addr = socket.respond_to?(:addr) ? socket.addr : []
82
+ rescue Errno::ENOTCONN
83
+ raise HTTPStatus::EOFError
84
+ end
85
+
86
+ read_request_line(socket)
87
+ if @http_version.major > 0
88
+ read_header(socket)
89
+ @header['cookie'].each{|cookie|
90
+ @cookies += Cookie::parse(cookie)
91
+ }
92
+ @accept = HTTPUtils.parse_qvalues(self['accept'])
93
+ @accept_charset = HTTPUtils.parse_qvalues(self['accept-charset'])
94
+ @accept_encoding = HTTPUtils.parse_qvalues(self['accept-encoding'])
95
+ @accept_language = HTTPUtils.parse_qvalues(self['accept-language'])
96
+ end
97
+ return if @request_method == "CONNECT"
98
+ return if @unparsed_uri == "*"
99
+
100
+ begin
101
+ @request_uri = parse_uri(@unparsed_uri)
102
+ @path = HTTPUtils::unescape(@request_uri.path)
103
+ @path = HTTPUtils::normalize_path(@path)
104
+ @host = @request_uri.host
105
+ @port = @request_uri.port
106
+ @query_string = @request_uri.query
107
+ @script_name = ""
108
+ @path_info = @path.dup
109
+ rescue
110
+ raise HTTPStatus::BadRequest, "bad URI `#{@unparsed_uri}'."
111
+ end
112
+
113
+ if /close/io =~ self["connection"]
114
+ @keep_alive = false
115
+ elsif /keep-alive/io =~ self["connection"]
116
+ @keep_alive = true
117
+ elsif @http_version < "1.1"
118
+ @keep_alive = false
119
+ else
120
+ @keep_alive = true
121
+ end
122
+ end
123
+
124
+ def body(&block)
125
+ block ||= Proc.new{|chunk| @body << chunk }
126
+ read_body(@socket, block)
127
+ @body.empty? ? nil : @body
128
+ end
129
+
130
+ def query
131
+ unless @query
132
+ parse_query()
133
+ end
134
+ @query
135
+ end
136
+
137
+ def content_length
138
+ return Integer(self['content-length'])
139
+ end
140
+
141
+ def content_type
142
+ return self['content-type']
143
+ end
144
+
145
+ def [](header_name)
146
+ if @header
147
+ value = @header[header_name.downcase]
148
+ value.empty? ? nil : value.join(", ")
149
+ end
150
+ end
151
+
152
+ def each
153
+ @header.each{|k, v|
154
+ value = @header[k]
155
+ yield(k, value.empty? ? nil : value.join(", "))
156
+ }
157
+ end
158
+
159
+ def keep_alive?
160
+ @keep_alive
161
+ end
162
+
163
+ def to_s
164
+ ret = @request_line.dup
165
+ @raw_header.each{|line| ret << line }
166
+ ret << CRLF
167
+ ret << body if body
168
+ ret
169
+ end
170
+
171
+ def fixup()
172
+ begin
173
+ body{|chunk| } # read remaining body
174
+ rescue HTTPStatus::Error => ex
175
+ @logger.error("HTTPRequest#fixup: #{ex.class} occured.")
176
+ @keep_alive = false
177
+ rescue => ex
178
+ @logger.error(ex)
179
+ @keep_alive = false
180
+ end
181
+ end
182
+
183
+ def meta_vars
184
+ # This method provides the metavariables defined by the revision 3
185
+ # of ``The WWW Common Gateway Interface Version 1.1''.
186
+ # (http://Web.Golux.Com/coar/cgi/)
187
+
188
+ meta = Hash.new
189
+
190
+ cl = self["Content-Length"]
191
+ ct = self["Content-Type"]
192
+ meta["CONTENT_LENGTH"] = cl if cl.to_i > 0
193
+ meta["CONTENT_TYPE"] = ct.dup if ct
194
+ meta["GATEWAY_INTERFACE"] = "CGI/1.1"
195
+ meta["PATH_INFO"] = @path_info ? @path_info.dup : ""
196
+ #meta["PATH_TRANSLATED"] = nil # no plan to be provided
197
+ meta["QUERY_STRING"] = @query_string ? @query_string.dup : ""
198
+ meta["REMOTE_ADDR"] = @peeraddr[3]
199
+ meta["REMOTE_HOST"] = @peeraddr[2]
200
+ #meta["REMOTE_IDENT"] = nil # no plan to be provided
201
+ meta["REMOTE_USER"] = @user
202
+ meta["REQUEST_METHOD"] = @request_method.dup
203
+ meta["REQUEST_URI"] = @request_uri.to_s
204
+ meta["SCRIPT_NAME"] = @script_name.dup
205
+ meta["SERVER_NAME"] = @host
206
+ meta["SERVER_PORT"] = @port.to_s
207
+ meta["SERVER_PROTOCOL"] = "HTTP/" + @config[:HTTPVersion].to_s
208
+ meta["SERVER_SOFTWARE"] = @config[:ServerSoftware].dup
209
+
210
+ self.each{|key, val|
211
+ next if /^content-type$/i =~ key
212
+ next if /^content-length$/i =~ key
213
+ name = "HTTP_" + key
214
+ name.gsub!(/-/o, "_")
215
+ name.upcase!
216
+ meta[name] = val
217
+ }
218
+
219
+ meta
220
+ end
221
+
222
+ private
223
+
224
+ def read_request_line(socket)
225
+ @request_line = read_line(socket) if socket
226
+ @request_time = Time.now
227
+ raise HTTPStatus::EOFError unless @request_line
228
+ if /^(\S+)\s+(\S+?)(?:\s+HTTP\/(\d+\.\d+))?\r?\n/mo =~ @request_line
229
+ @request_method = $1
230
+ @unparsed_uri = $2
231
+ @http_version = HTTPVersion.new($3 ? $3 : "0.9")
232
+ else
233
+ rl = @request_line.sub(/\x0d?\x0a\z/o, '')
234
+ raise HTTPStatus::BadRequest, "bad Request-Line `#{rl}'."
235
+ end
236
+ end
237
+
238
+ def read_header(socket)
239
+ if socket
240
+ while line = read_line(socket)
241
+ break if /\A(#{CRLF}|#{LF})\z/om =~ line
242
+ @raw_header << line
243
+ end
244
+ end
245
+ begin
246
+ @header = HTTPUtils::parse_header(@raw_header)
247
+ rescue => ex
248
+ raise HTTPStatus::BadRequest, ex.message
249
+ end
250
+ end
251
+
252
+ def parse_uri(str, scheme="http")
253
+ if @config[:Escape8bitURI]
254
+ str = HTTPUtils::escape8bit(str)
255
+ end
256
+ uri = URI::parse(str)
257
+ return uri if uri.absolute?
258
+ if self["host"]
259
+ pattern = /\A(#{URI::REGEXP::PATTERN::HOST})(?::(\d+))?\z/n
260
+ host, port = *self['host'].scan(pattern)[0]
261
+ elsif @addr.size > 0
262
+ host, port = @addr[2], @addr[1]
263
+ else
264
+ host, port = @config[:ServerName], @config[:Port]
265
+ end
266
+ uri.scheme = scheme
267
+ uri.host = host
268
+ uri.port = port ? port.to_i : nil
269
+ return URI::parse(uri.to_s)
270
+ end
271
+
272
+ def read_body(socket, block)
273
+ return unless socket
274
+ if tc = self['transfer-encoding']
275
+ case tc
276
+ when /chunked/io then read_chunked(socket, block)
277
+ else raise HTTPStatus::NotImplemented, "Transfer-Encoding: #{tc}."
278
+ end
279
+ elsif self['content-length'] || @remaining_size
280
+ @remaining_size ||= self['content-length'].to_i
281
+ while @remaining_size > 0
282
+ sz = BUFSIZE < @remaining_size ? BUFSIZE : @remaining_size
283
+ break unless buf = read_data(socket, sz)
284
+ @remaining_size -= buf.size
285
+ block.call(buf)
286
+ end
287
+ if @remaining_size > 0 && @socket.eof?
288
+ raise HTTPStatus::BadRequest, "invalid body size."
289
+ end
290
+ elsif BODY_CONTAINABLE_METHODS.member?(@request_method)
291
+ raise HTTPStatus::LengthRequired
292
+ end
293
+ return @body
294
+ end
295
+
296
+ def read_chunk_size(socket)
297
+ line = read_line(socket)
298
+ if /^([0-9a-fA-F]+)(?:;(\S+))?/ =~ line
299
+ chunk_size = $1.hex
300
+ chunk_ext = $2
301
+ [ chunk_size, chunk_ext ]
302
+ else
303
+ raise HTTPStatus::BadRequest, "bad chunk `#{line}'."
304
+ end
305
+ end
306
+
307
+ def read_chunked(socket, block)
308
+ chunk_size, = read_chunk_size(socket)
309
+ while chunk_size > 0
310
+ data = ""
311
+ while data.size < chunk_size
312
+ tmp = read_data(socket, chunk_size-data.size) # read chunk-data
313
+ break unless tmp
314
+ data << tmp
315
+ end
316
+ if data.nil? || data.size != chunk_size
317
+ raise BadRequest, "bad chunk data size."
318
+ end
319
+ read_line(socket) # skip CRLF
320
+ block.call(data)
321
+ chunk_size, = read_chunk_size(socket)
322
+ end
323
+ read_header(socket) # trailer + CRLF
324
+ @header.delete("transfer-encoding")
325
+ @remaining_size = 0
326
+ end
327
+
328
+ def _read_data(io, method, arg)
329
+ begin
330
+ timeout(@config[:RequestTimeout]){
331
+ return io.__send__(method, arg)
332
+ }
333
+ rescue Errno::ECONNRESET
334
+ return nil
335
+ rescue TimeoutError
336
+ raise HTTPStatus::RequestTimeout
337
+ end
338
+ end
339
+
340
+ def read_line(io)
341
+ _read_data(io, :gets, LF)
342
+ end
343
+
344
+ def read_data(io, size)
345
+ _read_data(io, :read, size)
346
+ end
347
+
348
+ def parse_query()
349
+ begin
350
+ if @request_method == "GET" || @request_method == "HEAD"
351
+ @query = HTTPUtils::parse_query(@query_string)
352
+ elsif self['content-type'] =~ /^application\/x-www-form-urlencoded/
353
+ @query = HTTPUtils::parse_query(body)
354
+ elsif self['content-type'] =~ /^multipart\/form-data; boundary=(.+)/
355
+ boundary = HTTPUtils::dequote($1)
356
+ @query = HTTPUtils::parse_form_data(body, boundary)
357
+ else
358
+ @query = Hash.new
359
+ end
360
+ rescue => ex
361
+ raise HTTPStatus::BadRequest, ex.message
362
+ end
363
+ end
364
+ end
365
+ end
@@ -0,0 +1,327 @@
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