rubysl-webrick 1.0.0 → 2.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 (39) hide show
  1. checksums.yaml +14 -6
  2. data/.travis.yml +5 -6
  3. data/lib/rubysl/webrick/version.rb +1 -1
  4. data/lib/rubysl/webrick/webrick.rb +199 -2
  5. data/lib/webrick/accesslog.rb +96 -5
  6. data/lib/webrick/cgi.rb +80 -29
  7. data/lib/webrick/compat.rb +20 -0
  8. data/lib/webrick/config.rb +59 -5
  9. data/lib/webrick/cookie.rb +66 -5
  10. data/lib/webrick/htmlutils.rb +4 -1
  11. data/lib/webrick/httpauth.rb +53 -3
  12. data/lib/webrick/httpauth/authenticator.rb +53 -16
  13. data/lib/webrick/httpauth/basicauth.rb +45 -2
  14. data/lib/webrick/httpauth/digestauth.rb +82 -17
  15. data/lib/webrick/httpauth/htdigest.rb +38 -1
  16. data/lib/webrick/httpauth/htgroup.rb +32 -0
  17. data/lib/webrick/httpauth/htpasswd.rb +40 -2
  18. data/lib/webrick/httpauth/userdb.rb +27 -4
  19. data/lib/webrick/httpproxy.rb +197 -112
  20. data/lib/webrick/httprequest.rb +268 -50
  21. data/lib/webrick/httpresponse.rb +170 -33
  22. data/lib/webrick/https.rb +26 -3
  23. data/lib/webrick/httpserver.rb +75 -7
  24. data/lib/webrick/httpservlet/abstract.rb +88 -6
  25. data/lib/webrick/httpservlet/cgi_runner.rb +5 -4
  26. data/lib/webrick/httpservlet/cgihandler.rb +37 -18
  27. data/lib/webrick/httpservlet/erbhandler.rb +40 -7
  28. data/lib/webrick/httpservlet/filehandler.rb +116 -28
  29. data/lib/webrick/httpservlet/prochandler.rb +17 -4
  30. data/lib/webrick/httpstatus.rb +86 -18
  31. data/lib/webrick/httputils.rb +131 -23
  32. data/lib/webrick/httpversion.rb +28 -2
  33. data/lib/webrick/log.rb +72 -5
  34. data/lib/webrick/server.rb +158 -33
  35. data/lib/webrick/ssl.rb +78 -9
  36. data/lib/webrick/utils.rb +151 -5
  37. data/lib/webrick/version.rb +5 -1
  38. data/rubysl-webrick.gemspec +0 -1
  39. metadata +12 -24
@@ -15,21 +15,85 @@ require 'webrick/httputils'
15
15
  require 'webrick/httpstatus'
16
16
 
17
17
  module WEBrick
18
+ ##
19
+ # An HTTP response. This is filled in by the service or do_* methods of a
20
+ # WEBrick HTTP Servlet.
21
+
18
22
  class HTTPResponse
19
- BUFSIZE = 1024*4
20
23
 
21
- attr_reader :http_version, :status, :header
24
+ ##
25
+ # HTTP Response version
26
+
27
+ attr_reader :http_version
28
+
29
+ ##
30
+ # Response status code (200)
31
+
32
+ attr_reader :status
33
+
34
+ ##
35
+ # Response header
36
+
37
+ attr_reader :header
38
+
39
+ ##
40
+ # Response cookies
41
+
22
42
  attr_reader :cookies
43
+
44
+ ##
45
+ # Response reason phrase ("OK")
46
+
23
47
  attr_accessor :reason_phrase
48
+
49
+ ##
50
+ # Body may be a String or IO subclass.
51
+
24
52
  attr_accessor :body
25
53
 
26
- attr_accessor :request_method, :request_uri, :request_http_version
54
+ ##
55
+ # Request method for this response
56
+
57
+ attr_accessor :request_method
58
+
59
+ ##
60
+ # Request URI for this response
61
+
62
+ attr_accessor :request_uri
63
+
64
+ ##
65
+ # Request HTTP version for this response
66
+
67
+ attr_accessor :request_http_version
68
+
69
+ ##
70
+ # Filename of the static file in this response. Only used by the
71
+ # FileHandler servlet.
72
+
27
73
  attr_accessor :filename
74
+
75
+ ##
76
+ # Is this a keep-alive response?
77
+
28
78
  attr_accessor :keep_alive
29
- attr_reader :config, :sent_size
79
+
80
+ ##
81
+ # Configuration for this response
82
+
83
+ attr_reader :config
84
+
85
+ ##
86
+ # Bytes sent in this response
87
+
88
+ attr_reader :sent_size
89
+
90
+ ##
91
+ # Creates a new HTTP response object. WEBrick::Config::HTTP is the
92
+ # default configuration.
30
93
 
31
94
  def initialize(config)
32
95
  @config = config
96
+ @buffer_size = config[:OutputBufferSize]
33
97
  @logger = config[:Logger]
34
98
  @header = Hash.new
35
99
  @status = HTTPStatus::RC_OK
@@ -46,58 +110,97 @@ module WEBrick
46
110
  @sent_size = 0
47
111
  end
48
112
 
113
+ ##
114
+ # The response's HTTP status line
115
+
49
116
  def status_line
50
117
  "HTTP/#@http_version #@status #@reason_phrase #{CRLF}"
51
118
  end
52
119
 
120
+ ##
121
+ # Sets the response's status to the +status+ code
122
+
53
123
  def status=(status)
54
124
  @status = status
55
125
  @reason_phrase = HTTPStatus::reason_phrase(status)
56
126
  end
57
127
 
128
+ ##
129
+ # Retrieves the response header +field+
130
+
58
131
  def [](field)
59
132
  @header[field.downcase]
60
133
  end
61
134
 
135
+ ##
136
+ # Sets the response header +field+ to +value+
137
+
62
138
  def []=(field, value)
63
139
  @header[field.downcase] = value.to_s
64
140
  end
65
141
 
142
+ ##
143
+ # The content-length header
144
+
66
145
  def content_length
67
146
  if len = self['content-length']
68
147
  return Integer(len)
69
148
  end
70
149
  end
71
150
 
151
+ ##
152
+ # Sets the content-length header to +len+
153
+
72
154
  def content_length=(len)
73
155
  self['content-length'] = len.to_s
74
156
  end
75
157
 
158
+ ##
159
+ # The content-type header
160
+
76
161
  def content_type
77
162
  self['content-type']
78
163
  end
79
164
 
165
+ ##
166
+ # Sets the content-type header to +type+
167
+
80
168
  def content_type=(type)
81
169
  self['content-type'] = type
82
170
  end
83
171
 
172
+ ##
173
+ # Iterates over each header in the resopnse
174
+
84
175
  def each
85
- @header.each{|k, v| yield(k, v) }
176
+ @header.each{|field, value| yield(field, value) }
86
177
  end
87
178
 
179
+ ##
180
+ # Will this response body be returned using chunked transfer-encoding?
181
+
88
182
  def chunked?
89
183
  @chunked
90
184
  end
91
185
 
186
+ ##
187
+ # Enables chunked transfer encoding.
188
+
92
189
  def chunked=(val)
93
190
  @chunked = val ? true : false
94
191
  end
95
192
 
193
+ ##
194
+ # Will this response's connection be kept alive?
195
+
96
196
  def keep_alive?
97
197
  @keep_alive
98
198
  end
99
199
 
100
- def send_response(socket)
200
+ ##
201
+ # Sends the response on +socket+
202
+
203
+ def send_response(socket) # :nodoc:
101
204
  begin
102
205
  setup_header()
103
206
  send_header(socket)
@@ -111,7 +214,10 @@ module WEBrick
111
214
  end
112
215
  end
113
216
 
114
- def setup_header()
217
+ ##
218
+ # Sets up the headers for sending
219
+
220
+ def setup_header() # :nodoc:
115
221
  @reason_phrase ||= HTTPStatus::reason_phrase(@status)
116
222
  @header['server'] ||= @config[:ServerSoftware]
117
223
  @header['date'] ||= Time.now.httpdate
@@ -143,7 +249,7 @@ module WEBrick
143
249
  @header.delete('content-length')
144
250
  elsif @header['content-length'].nil?
145
251
  unless @body.is_a?(IO)
146
- @header['content-length'] = @body ? @body.size : 0
252
+ @header['content-length'] = @body ? @body.bytesize : 0
147
253
  end
148
254
  end
149
255
 
@@ -151,8 +257,13 @@ module WEBrick
151
257
  if @header['connection'] == "close"
152
258
  @keep_alive = false
153
259
  elsif keep_alive?
154
- if chunked? || @header['content-length']
260
+ if chunked? || @header['content-length'] || @status == 304 || @status == 204 || HTTPStatus.info?(@status)
155
261
  @header['connection'] = "Keep-Alive"
262
+ else
263
+ msg = "Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true"
264
+ @logger.warn(msg)
265
+ @header['connection'] = "close"
266
+ @keep_alive = false
156
267
  end
157
268
  else
158
269
  @header['connection'] = "close"
@@ -166,11 +277,14 @@ module WEBrick
166
277
  end
167
278
  end
168
279
 
169
- def send_header(socket)
280
+ ##
281
+ # Sends the headers on +socket+
282
+
283
+ def send_header(socket) # :nodoc:
170
284
  if @http_version.major > 0
171
285
  data = status_line()
172
286
  @header.each{|key, value|
173
- tmp = key.gsub(/\bwww|^te$|\b\w/){|s| s.upcase }
287
+ tmp = key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }
174
288
  data << "#{tmp}: #{value}" << CRLF
175
289
  }
176
290
  @cookies.each{|cookie|
@@ -181,31 +295,44 @@ module WEBrick
181
295
  end
182
296
  end
183
297
 
184
- def send_body(socket)
298
+ ##
299
+ # Sends the body on +socket+
300
+
301
+ def send_body(socket) # :nodoc:
185
302
  case @body
186
303
  when IO then send_body_io(socket)
187
304
  else send_body_string(socket)
188
305
  end
189
306
  end
190
307
 
191
- def to_s
308
+ def to_s # :nodoc:
192
309
  ret = ""
193
310
  send_response(ret)
194
311
  ret
195
312
  end
196
313
 
314
+ ##
315
+ # Redirects to +url+ with a WEBrick::HTTPStatus::Redirect +status+.
316
+ #
317
+ # Example:
318
+ #
319
+ # res.set_redirect WEBrick::HTTPStatus::TemporaryRedirect
320
+
197
321
  def set_redirect(status, url)
198
322
  @body = "<HTML><A HREF=\"#{url.to_s}\">#{url.to_s}</A>.</HTML>\n"
199
323
  @header['location'] = url.to_s
200
324
  raise status
201
325
  end
202
326
 
327
+ ##
328
+ # Creates an error page for exception +ex+ with an optional +backtrace+
329
+
203
330
  def set_error(ex, backtrace=false)
204
331
  case ex
205
- when HTTPStatus::Status
332
+ when HTTPStatus::Status
206
333
  @keep_alive = false if HTTPStatus::error?(ex.code)
207
334
  self.status = ex.code
208
- else
335
+ else
209
336
  @keep_alive = false
210
337
  self.status = HTTPStatus::RC_INTERNAL_SERVER_ERROR
211
338
  end
@@ -253,18 +380,25 @@ module WEBrick
253
380
 
254
381
  private
255
382
 
383
+ # :stopdoc:
384
+
256
385
  def send_body_io(socket)
257
386
  begin
258
387
  if @request_method == "HEAD"
259
388
  # do nothing
260
389
  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
390
+ begin
391
+ buf = ''
392
+ data = ''
393
+ while true
394
+ @body.readpartial( @buffer_size, buf ) # there is no need to clear buf?
395
+ data << format("%x", buf.bytesize) << CRLF
396
+ data << buf << CRLF
397
+ _write_data(socket, data)
398
+ data.clear
399
+ @sent_size += buf.bytesize
400
+ end
401
+ rescue EOFError # do nothing
268
402
  end
269
403
  _write_data(socket, "0#{CRLF}#{CRLF}")
270
404
  else
@@ -281,41 +415,41 @@ module WEBrick
281
415
  if @request_method == "HEAD"
282
416
  # do nothing
283
417
  elsif chunked?
284
- remain = body ? @body.size : 0
285
- while buf = @body[@sent_size, BUFSIZE]
418
+ body ? @body.bytesize : 0
419
+ while buf = @body[@sent_size, @buffer_size]
286
420
  break if buf.empty?
287
421
  data = ""
288
- data << format("%x", buf.size) << CRLF
422
+ data << format("%x", buf.bytesize) << CRLF
289
423
  data << buf << CRLF
290
424
  _write_data(socket, data)
291
- @sent_size += buf.size
425
+ @sent_size += buf.bytesize
292
426
  end
293
427
  _write_data(socket, "0#{CRLF}#{CRLF}")
294
428
  else
295
- if @body && @body.size > 0
429
+ if @body && @body.bytesize > 0
296
430
  _write_data(socket, @body)
297
- @sent_size = @body.size
431
+ @sent_size = @body.bytesize
298
432
  end
299
433
  end
300
434
  end
301
435
 
302
436
  def _send_file(output, input, offset, size)
303
437
  while offset > 0
304
- sz = BUFSIZE < offset ? BUFSIZE : offset
438
+ sz = @buffer_size < size ? @buffer_size : size
305
439
  buf = input.read(sz)
306
- offset -= buf.size
440
+ offset -= buf.bytesize
307
441
  end
308
442
 
309
443
  if size == 0
310
- while buf = input.read(BUFSIZE)
444
+ while buf = input.read(@buffer_size)
311
445
  _write_data(output, buf)
312
446
  end
313
447
  else
314
448
  while size > 0
315
- sz = BUFSIZE < size ? BUFSIZE : size
449
+ sz = @buffer_size < size ? @buffer_size : size
316
450
  buf = input.read(sz)
317
451
  _write_data(output, buf)
318
- size -= buf.size
452
+ size -= buf.bytesize
319
453
  end
320
454
  end
321
455
  end
@@ -323,5 +457,8 @@ module WEBrick
323
457
  def _write_data(socket, data)
324
458
  socket << data
325
459
  end
460
+
461
+ # :startdoc:
326
462
  end
463
+
327
464
  end
@@ -15,8 +15,28 @@ module WEBrick
15
15
  HTTP.update(SSL)
16
16
  end
17
17
 
18
+ ##
19
+ #--
20
+ # Adds SSL functionality to WEBrick::HTTPRequest
21
+
18
22
  class HTTPRequest
19
- attr_reader :cipher, :server_cert, :client_cert
23
+
24
+ ##
25
+ # HTTP request SSL cipher
26
+
27
+ attr_reader :cipher
28
+
29
+ ##
30
+ # HTTP request server certificate
31
+
32
+ attr_reader :server_cert
33
+
34
+ ##
35
+ # HTTP request client certificate
36
+
37
+ attr_reader :client_cert
38
+
39
+ # :stopdoc:
20
40
 
21
41
  alias orig_parse parse
22
42
 
@@ -33,17 +53,18 @@ module WEBrick
33
53
  alias orig_parse_uri parse_uri
34
54
 
35
55
  def parse_uri(str, scheme="https")
36
- if @server_cert
56
+ if server_cert
37
57
  return orig_parse_uri(str, scheme)
38
58
  end
39
59
  return orig_parse_uri(str)
40
60
  end
61
+ private :parse_uri
41
62
 
42
63
  alias orig_meta_vars meta_vars
43
64
 
44
65
  def meta_vars
45
66
  meta = orig_meta_vars
46
- if @server_cert
67
+ if server_cert
47
68
  meta["HTTPS"] = "on"
48
69
  meta["SSL_SERVER_CERT"] = @server_cert.to_pem
49
70
  meta["SSL_CLIENT_CERT"] = @client_cert ? @client_cert.to_pem : ""
@@ -59,5 +80,7 @@ module WEBrick
59
80
  end
60
81
  meta
61
82
  end
83
+
84
+ # :startdoc:
62
85
  end
63
86
  end
@@ -19,9 +19,30 @@ require 'webrick/accesslog'
19
19
  module WEBrick
20
20
  class HTTPServerError < ServerError; end
21
21
 
22
+ ##
23
+ # An HTTP Server
24
+
22
25
  class HTTPServer < ::WEBrick::GenericServer
26
+ ##
27
+ # Creates a new HTTP server according to +config+
28
+ #
29
+ # An HTTP server uses the following attributes:
30
+ #
31
+ # :AccessLog:: An array of access logs. See WEBrick::AccessLog
32
+ # :BindAddress:: Local address for the server to bind to
33
+ # :DocumentRoot:: Root path to serve files from
34
+ # :DocumentRootOptions:: Options for the default HTTPServlet::FileHandler
35
+ # :HTTPVersion:: The HTTP version of this server
36
+ # :Port:: Port to listen on
37
+ # :RequestCallback:: Called with a request and response before each
38
+ # request is serviced.
39
+ # :RequestTimeout:: Maximum time to wait between requests
40
+ # :ServerAlias:: Array of alternate names for this server for virtual
41
+ # hosting
42
+ # :ServerName:: Name for this server for virtual hosting
43
+
23
44
  def initialize(config={}, default=Config::HTTP)
24
- super
45
+ super(config, default)
25
46
  @http_version = HTTPVersion::convert(@config[:HTTPVersion])
26
47
 
27
48
  @mount_tab = MountTable.new
@@ -36,12 +57,15 @@ module WEBrick
36
57
  [ $stderr, AccessLog::REFERER_LOG_FORMAT ]
37
58
  ]
38
59
  end
39
-
60
+
40
61
  @virtual_hosts = Array.new
41
62
  end
42
63
 
64
+ ##
65
+ # Processes requests on +sock+
66
+
43
67
  def run(sock)
44
- while true
68
+ while true
45
69
  res = HTTPResponse.new(@config)
46
70
  req = HTTPRequest.new(@config)
47
71
  server = self
@@ -52,14 +76,19 @@ module WEBrick
52
76
  timeout = 0 if @status != :Running
53
77
  timeout -= 0.5
54
78
  end
55
- raise HTTPStatus::EOFError if timeout <= 0 || sock.eof?
79
+ raise HTTPStatus::EOFError if timeout <= 0
80
+ raise HTTPStatus::EOFError if sock.eof?
56
81
  req.parse(sock)
57
82
  res.request_method = req.request_method
58
83
  res.request_uri = req.request_uri
59
84
  res.request_http_version = req.http_version
60
85
  res.keep_alive = req.keep_alive?
61
86
  server = lookup_server(req) || self
62
- if callback = server[:RequestCallback] || server[:RequestHandler]
87
+ if callback = server[:RequestCallback]
88
+ callback.call(req, res)
89
+ elsif callback = server[:RequestHandler]
90
+ msg = ":RequestHandler is deprecated, please use :RequestCallback"
91
+ @logger.warn(msg)
63
92
  callback.call(req, res)
64
93
  end
65
94
  server.service(req, res)
@@ -75,7 +104,9 @@ module WEBrick
75
104
  res.set_error(ex, true)
76
105
  ensure
77
106
  if req.request_line
78
- req.fixup()
107
+ if req.keep_alive? && res.keep_alive?
108
+ req.fixup()
109
+ end
79
110
  res.send_response(sock)
80
111
  server.access_log(@config, req, res)
81
112
  end
@@ -86,6 +117,9 @@ module WEBrick
86
117
  end
87
118
  end
88
119
 
120
+ ##
121
+ # Services +req+ and fills in +res+
122
+
89
123
  def service(req, res)
90
124
  if req.unparsed_uri == "*"
91
125
  if req.request_method == "OPTIONS"
@@ -104,27 +138,45 @@ module WEBrick
104
138
  si.service(req, res)
105
139
  end
106
140
 
141
+ ##
142
+ # The default OPTIONS request handler says GET, HEAD, POST and OPTIONS
143
+ # requests are allowed.
144
+
107
145
  def do_OPTIONS(req, res)
108
146
  res["allow"] = "GET,HEAD,POST,OPTIONS"
109
147
  end
110
148
 
149
+ ##
150
+ # Mounts +servlet+ on +dir+ passing +options+ to the servlet at creation
151
+ # time
152
+
111
153
  def mount(dir, servlet, *options)
112
154
  @logger.debug(sprintf("%s is mounted on %s.", servlet.inspect, dir))
113
155
  @mount_tab[dir] = [ servlet, options ]
114
156
  end
115
157
 
158
+ ##
159
+ # Mounts +proc+ or +block+ on +dir+ and calls it with a
160
+ # WEBrick::HTTPRequest and WEBrick::HTTPResponse
161
+
116
162
  def mount_proc(dir, proc=nil, &block)
117
163
  proc ||= block
118
164
  raise HTTPServerError, "must pass a proc or block" unless proc
119
165
  mount(dir, HTTPServlet::ProcHandler.new(proc))
120
166
  end
121
167
 
168
+ ##
169
+ # Unmounts +dir+
170
+
122
171
  def unmount(dir)
123
172
  @logger.debug(sprintf("unmount %s.", dir))
124
173
  @mount_tab.delete(dir)
125
174
  end
126
175
  alias umount unmount
127
176
 
177
+ ##
178
+ # Finds a servlet for +path+
179
+
128
180
  def search_servlet(path)
129
181
  script_name, path_info = @mount_tab.scan(path)
130
182
  servlet, options = @mount_tab[script_name]
@@ -133,6 +185,9 @@ module WEBrick
133
185
  end
134
186
  end
135
187
 
188
+ ##
189
+ # Adds +server+ as a virtual host.
190
+
136
191
  def virtual_host(server)
137
192
  @virtual_hosts << server
138
193
  @virtual_hosts = @virtual_hosts.sort_by{|s|
@@ -144,6 +199,9 @@ module WEBrick
144
199
  }
145
200
  end
146
201
 
202
+ ##
203
+ # Finds the appropriate virtual host to handle +req+
204
+
147
205
  def lookup_server(req)
148
206
  @virtual_hosts.find{|s|
149
207
  (s[:BindAddress].nil? || req.addr[3] == s[:BindAddress]) &&
@@ -153,6 +211,10 @@ module WEBrick
153
211
  }
154
212
  end
155
213
 
214
+ ##
215
+ # Logs +req+ and +res+ in the access logs. +config+ is used for the
216
+ # server name.
217
+
156
218
  def access_log(config, req, res)
157
219
  param = AccessLog::setup_params(config, req, res)
158
220
  @config[:AccessLog].each{|logger, fmt|
@@ -160,7 +222,13 @@ module WEBrick
160
222
  }
161
223
  end
162
224
 
163
- class MountTable
225
+ ##
226
+ # Mount table for the path a servlet is mounted on in the directory space
227
+ # of the server. Users of WEBrick can only access this indirectly via
228
+ # WEBrick::HTTPServer#mount, WEBrick::HTTPServer#unmount and
229
+ # WEBrick::HTTPServer#search_servlet
230
+
231
+ class MountTable # :nodoc:
164
232
  def initialize
165
233
  @tab = Hash.new
166
234
  compile