Re-enable spec/library for full CI runs.
[rbx.git] / lib / webrick / httpserver.rb
blob9edb0018f84309d07d6948c96d33409a55842452
2 # httpserver.rb -- HTTPServer Class
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.
9 # $IPR: httpserver.rb,v 1.63 2002/10/01 17:16:32 gotoyuzo Exp $
11 require 'webrick/server'
12 require 'webrick/httputils'
13 require 'webrick/httpstatus'
14 require 'webrick/httprequest'
15 require 'webrick/httpresponse'
16 require 'webrick/httpservlet'
17 require 'webrick/accesslog'
19 module WEBrick
20   class HTTPServerError < ServerError; end
22   class HTTPServer < ::WEBrick::GenericServer
23     def initialize(config={}, default=Config::HTTP)
24       super
25       @http_version = HTTPVersion::convert(@config[:HTTPVersion])
27       @mount_tab = MountTable.new
28       if @config[:DocumentRoot]
29         mount("/", HTTPServlet::FileHandler, @config[:DocumentRoot],
30               @config[:DocumentRootOptions])
31       end
33       unless @config[:AccessLog]
34         @config[:AccessLog] = [
35           [ $stderr, AccessLog::COMMON_LOG_FORMAT ],
36           [ $stderr, AccessLog::REFERER_LOG_FORMAT ]
37         ]
38       end
40       @virtual_hosts = Array.new
41     end
43     def run(sock)
44       while true 
45         res = HTTPResponse.new(@config)
46         req = HTTPRequest.new(@config)
47         server = self
48         begin
49           timeout = @config[:RequestTimeout]
50           while timeout > 0
51             break if IO.select([sock], nil, nil, 0.5)
52             timeout = 0 if @status != :Running
53             timeout -= 0.5
54           end
55           raise HTTPStatus::EOFError if timeout <= 0 || sock.eof?
56           req.parse(sock)
57           res.request_method = req.request_method
58           res.request_uri = req.request_uri
59           res.request_http_version = req.http_version
60           res.keep_alive = req.keep_alive?
61           server = lookup_server(req) || self
62           if callback = server[:RequestCallback] || server[:RequestHandler]
63             callback.call(req, res)
64           end
65           server.service(req, res)
66         rescue HTTPStatus::EOFError, HTTPStatus::RequestTimeout => ex
67           res.set_error(ex)
68         rescue HTTPStatus::Error => ex
69           @logger.error(ex.message)
70           res.set_error(ex)
71         rescue HTTPStatus::Status => ex
72           res.status = ex.code
73         rescue StandardError => ex
74           @logger.error(ex)
75           res.set_error(ex, true)
76         ensure
77           if req.request_line
78             req.fixup()
79             res.send_response(sock)
80             server.access_log(@config, req, res)
81           end
82         end
83         break if @http_version < "1.1"
84         break unless req.keep_alive?
85         break unless res.keep_alive?
86       end
87     end
89     def service(req, res)
90       if req.unparsed_uri == "*"
91         if req.request_method == "OPTIONS"
92           do_OPTIONS(req, res)
93           raise HTTPStatus::OK
94         end
95         raise HTTPStatus::NotFound, "`#{req.unparsed_uri}' not found."
96       end
98       servlet, options, script_name, path_info = search_servlet(req.path)
99       raise HTTPStatus::NotFound, "`#{req.path}' not found." unless servlet
100       req.script_name = script_name
101       req.path_info = path_info
102       si = servlet.get_instance(self, *options)
103       @logger.debug(format("%s is invoked.", si.class.name))
104       si.service(req, res)
105     end
107     def do_OPTIONS(req, res)
108       res["allow"] = "GET,HEAD,POST,OPTIONS"
109     end
111     def mount(dir, servlet, *options)
112       @logger.debug(sprintf("%s is mounted on %s.", servlet.inspect, dir))
113       @mount_tab[dir] = [ servlet, options ]
114     end
116     def mount_proc(dir, proc=nil, &block)
117       proc ||= block
118       raise HTTPServerError, "must pass a proc or block" unless proc
119       mount(dir, HTTPServlet::ProcHandler.new(proc))
120     end
122     def unmount(dir)
123       @logger.debug(sprintf("unmount %s.", dir))
124       @mount_tab.delete(dir)
125     end
126     alias umount unmount
128     def search_servlet(path)
129       script_name, path_info = @mount_tab.scan(path)
130       servlet, options = @mount_tab[script_name]
131       if servlet
132         [ servlet, options, script_name, path_info ]
133       end
134     end
136     def virtual_host(server)
137       @virtual_hosts << server
138       @virtual_hosts = @virtual_hosts.sort_by{|s|
139         num = 0
140         num -= 4 if s[:BindAddress]
141         num -= 2 if s[:Port]
142         num -= 1 if s[:ServerName]
143         num
144       }
145     end
147     def lookup_server(req)
148       @virtual_hosts.find{|s|
149         (s[:BindAddress].nil? || req.addr[3] == s[:BindAddress]) &&
150         (s[:Port].nil?        || req.port == s[:Port])           &&
151         ((s[:ServerName].nil?  || req.host == s[:ServerName]) ||
152          (!s[:ServerAlias].nil? && s[:ServerAlias].find{|h| h === req.host}))
153       }
154     end
156     def access_log(config, req, res)
157       param = AccessLog::setup_params(config, req, res)
158       @config[:AccessLog].each{|logger, fmt|
159         logger << AccessLog::format(fmt+"\n", param)
160       }
161     end
163     class MountTable
164       def initialize
165         @tab = Hash.new
166         compile
167       end
169       def [](dir)
170         dir = normalize(dir)
171         @tab[dir]
172       end
174       def []=(dir, val)
175         dir = normalize(dir)
176         @tab[dir] = val
177         compile
178         val
179       end
181       def delete(dir)
182         dir = normalize(dir)
183         res = @tab.delete(dir)
184         compile
185         res
186       end
188       def scan(path)
189         @scanner =~ path
190         [ $&, $' ]
191       end
193       private
195       def compile
196         k = @tab.keys
197         k.sort!
198         k.reverse!
199         k.collect!{|path| Regexp.escape(path) }
200         @scanner = Regexp.new("^(" + k.join("|") +")(?=/|$)")
201       end
203       def normalize(dir)
204         ret = dir ? dir.dup : ""
205         ret.sub!(%r|/+$|, "")
206         ret
207       end
208     end
209   end