1# frozen_string_literal: false 2# 3# httpserver.rb -- HTTPServer Class 4# 5# Author: IPR -- Internet Programming with Ruby -- writers 6# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou 7# Copyright (c) 2002 Internet Programming with Ruby writers. All rights 8# reserved. 9# 10# $IPR: httpserver.rb,v 1.63 2002/10/01 17:16:32 gotoyuzo Exp $ 11 12require 'io/wait' 13require_relative 'server' 14require_relative 'httputils' 15require_relative 'httpstatus' 16require_relative 'httprequest' 17require_relative 'httpresponse' 18require_relative 'httpservlet' 19require_relative 'accesslog' 20 21module WEBrick 22 class HTTPServerError < ServerError; end 23 24 ## 25 # An HTTP Server 26 27 class HTTPServer < ::WEBrick::GenericServer 28 ## 29 # Creates a new HTTP server according to +config+ 30 # 31 # An HTTP server uses the following attributes: 32 # 33 # :AccessLog:: An array of access logs. See WEBrick::AccessLog 34 # :BindAddress:: Local address for the server to bind to 35 # :DocumentRoot:: Root path to serve files from 36 # :DocumentRootOptions:: Options for the default HTTPServlet::FileHandler 37 # :HTTPVersion:: The HTTP version of this server 38 # :Port:: Port to listen on 39 # :RequestCallback:: Called with a request and response before each 40 # request is serviced. 41 # :RequestTimeout:: Maximum time to wait between requests 42 # :ServerAlias:: Array of alternate names for this server for virtual 43 # hosting 44 # :ServerName:: Name for this server for virtual hosting 45 46 def initialize(config={}, default=Config::HTTP) 47 super(config, default) 48 @http_version = HTTPVersion::convert(@config[:HTTPVersion]) 49 50 @mount_tab = MountTable.new 51 if @config[:DocumentRoot] 52 mount("/", HTTPServlet::FileHandler, @config[:DocumentRoot], 53 @config[:DocumentRootOptions]) 54 end 55 56 unless @config[:AccessLog] 57 @config[:AccessLog] = [ 58 [ $stderr, AccessLog::COMMON_LOG_FORMAT ], 59 [ $stderr, AccessLog::REFERER_LOG_FORMAT ] 60 ] 61 end 62 63 @virtual_hosts = Array.new 64 end 65 66 ## 67 # Processes requests on +sock+ 68 69 def run(sock) 70 while true 71 req = create_request(@config) 72 res = create_response(@config) 73 server = self 74 begin 75 timeout = @config[:RequestTimeout] 76 while timeout > 0 77 break if sock.to_io.wait_readable(0.5) 78 break if @status != :Running 79 timeout -= 0.5 80 end 81 raise HTTPStatus::EOFError if timeout <= 0 || @status != :Running 82 raise HTTPStatus::EOFError if sock.eof? 83 req.parse(sock) 84 res.request_method = req.request_method 85 res.request_uri = req.request_uri 86 res.request_http_version = req.http_version 87 res.keep_alive = req.keep_alive? 88 server = lookup_server(req) || self 89 if callback = server[:RequestCallback] 90 callback.call(req, res) 91 elsif callback = server[:RequestHandler] 92 msg = ":RequestHandler is deprecated, please use :RequestCallback" 93 @logger.warn(msg) 94 callback.call(req, res) 95 end 96 server.service(req, res) 97 rescue HTTPStatus::EOFError, HTTPStatus::RequestTimeout => ex 98 res.set_error(ex) 99 rescue HTTPStatus::Error => ex 100 @logger.error(ex.message) 101 res.set_error(ex) 102 rescue HTTPStatus::Status => ex 103 res.status = ex.code 104 rescue StandardError => ex 105 @logger.error(ex) 106 res.set_error(ex, true) 107 ensure 108 if req.request_line 109 if req.keep_alive? && res.keep_alive? 110 req.fixup() 111 end 112 res.send_response(sock) 113 server.access_log(@config, req, res) 114 end 115 end 116 break if @http_version < "1.1" 117 break unless req.keep_alive? 118 break unless res.keep_alive? 119 end 120 end 121 122 ## 123 # Services +req+ and fills in +res+ 124 125 def service(req, res) 126 if req.unparsed_uri == "*" 127 if req.request_method == "OPTIONS" 128 do_OPTIONS(req, res) 129 raise HTTPStatus::OK 130 end 131 raise HTTPStatus::NotFound, "`#{req.unparsed_uri}' not found." 132 end 133 134 servlet, options, script_name, path_info = search_servlet(req.path) 135 raise HTTPStatus::NotFound, "`#{req.path}' not found." unless servlet 136 req.script_name = script_name 137 req.path_info = path_info 138 si = servlet.get_instance(self, *options) 139 @logger.debug(format("%s is invoked.", si.class.name)) 140 si.service(req, res) 141 end 142 143 ## 144 # The default OPTIONS request handler says GET, HEAD, POST and OPTIONS 145 # requests are allowed. 146 147 def do_OPTIONS(req, res) 148 res["allow"] = "GET,HEAD,POST,OPTIONS" 149 end 150 151 ## 152 # Mounts +servlet+ on +dir+ passing +options+ to the servlet at creation 153 # time 154 155 def mount(dir, servlet, *options) 156 @logger.debug(sprintf("%s is mounted on %s.", servlet.inspect, dir)) 157 @mount_tab[dir] = [ servlet, options ] 158 end 159 160 ## 161 # Mounts +proc+ or +block+ on +dir+ and calls it with a 162 # WEBrick::HTTPRequest and WEBrick::HTTPResponse 163 164 def mount_proc(dir, proc=nil, &block) 165 proc ||= block 166 raise HTTPServerError, "must pass a proc or block" unless proc 167 mount(dir, HTTPServlet::ProcHandler.new(proc)) 168 end 169 170 ## 171 # Unmounts +dir+ 172 173 def unmount(dir) 174 @logger.debug(sprintf("unmount %s.", dir)) 175 @mount_tab.delete(dir) 176 end 177 alias umount unmount 178 179 ## 180 # Finds a servlet for +path+ 181 182 def search_servlet(path) 183 script_name, path_info = @mount_tab.scan(path) 184 servlet, options = @mount_tab[script_name] 185 if servlet 186 [ servlet, options, script_name, path_info ] 187 end 188 end 189 190 ## 191 # Adds +server+ as a virtual host. 192 193 def virtual_host(server) 194 @virtual_hosts << server 195 @virtual_hosts = @virtual_hosts.sort_by{|s| 196 num = 0 197 num -= 4 if s[:BindAddress] 198 num -= 2 if s[:Port] 199 num -= 1 if s[:ServerName] 200 num 201 } 202 end 203 204 ## 205 # Finds the appropriate virtual host to handle +req+ 206 207 def lookup_server(req) 208 @virtual_hosts.find{|s| 209 (s[:BindAddress].nil? || req.addr[3] == s[:BindAddress]) && 210 (s[:Port].nil? || req.port == s[:Port]) && 211 ((s[:ServerName].nil? || req.host == s[:ServerName]) || 212 (!s[:ServerAlias].nil? && s[:ServerAlias].find{|h| h === req.host})) 213 } 214 end 215 216 ## 217 # Logs +req+ and +res+ in the access logs. +config+ is used for the 218 # server name. 219 220 def access_log(config, req, res) 221 param = AccessLog::setup_params(config, req, res) 222 @config[:AccessLog].each{|logger, fmt| 223 logger << AccessLog::format(fmt+"\n", param) 224 } 225 end 226 227 ## 228 # Creates the HTTPRequest used when handling the HTTP 229 # request. Can be overridden by subclasses. 230 def create_request(with_webrick_config) 231 HTTPRequest.new(with_webrick_config) 232 end 233 234 ## 235 # Creates the HTTPResponse used when handling the HTTP 236 # request. Can be overridden by subclasses. 237 def create_response(with_webrick_config) 238 HTTPResponse.new(with_webrick_config) 239 end 240 241 ## 242 # Mount table for the path a servlet is mounted on in the directory space 243 # of the server. Users of WEBrick can only access this indirectly via 244 # WEBrick::HTTPServer#mount, WEBrick::HTTPServer#unmount and 245 # WEBrick::HTTPServer#search_servlet 246 247 class MountTable # :nodoc: 248 def initialize 249 @tab = Hash.new 250 compile 251 end 252 253 def [](dir) 254 dir = normalize(dir) 255 @tab[dir] 256 end 257 258 def []=(dir, val) 259 dir = normalize(dir) 260 @tab[dir] = val 261 compile 262 val 263 end 264 265 def delete(dir) 266 dir = normalize(dir) 267 res = @tab.delete(dir) 268 compile 269 res 270 end 271 272 def scan(path) 273 @scanner =~ path 274 [ $&, $' ] 275 end 276 277 private 278 279 def compile 280 k = @tab.keys 281 k.sort! 282 k.reverse! 283 k.collect!{|path| Regexp.escape(path) } 284 @scanner = Regexp.new("\\A(" + k.join("|") +")(?=/|\\z)") 285 end 286 287 def normalize(dir) 288 ret = dir ? dir.dup : "" 289 ret.sub!(%r|/+\z|, "") 290 ret 291 end 292 end 293 end 294end 295