1# frozen_string_literal: false 2# 3# server.rb -- GenericServer 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: server.rb,v 1.62 2003/07/22 19:20:43 gotoyuzo Exp $ 11 12require 'socket' 13require_relative 'config' 14require_relative 'log' 15 16module WEBrick 17 18 ## 19 # Server error exception 20 21 class ServerError < StandardError; end 22 23 ## 24 # Base server class 25 26 class SimpleServer 27 28 ## 29 # A SimpleServer only yields when you start it 30 31 def SimpleServer.start 32 yield 33 end 34 end 35 36 ## 37 # A generic module for daemonizing a process 38 39 class Daemon 40 41 ## 42 # Performs the standard operations for daemonizing a process. Runs a 43 # block, if given. 44 45 def Daemon.start 46 Process.daemon 47 File.umask(0) 48 yield if block_given? 49 end 50 end 51 52 ## 53 # Base TCP server class. You must subclass GenericServer and provide a #run 54 # method. 55 56 class GenericServer 57 58 ## 59 # The server status. One of :Stop, :Running or :Shutdown 60 61 attr_reader :status 62 63 ## 64 # The server configuration 65 66 attr_reader :config 67 68 ## 69 # The server logger. This is independent from the HTTP access log. 70 71 attr_reader :logger 72 73 ## 74 # Tokens control the number of outstanding clients. The 75 # <code>:MaxClients</code> configuration sets this. 76 77 attr_reader :tokens 78 79 ## 80 # Sockets listening for connections. 81 82 attr_reader :listeners 83 84 ## 85 # Creates a new generic server from +config+. The default configuration 86 # comes from +default+. 87 88 def initialize(config={}, default=Config::General) 89 @config = default.dup.update(config) 90 @status = :Stop 91 @config[:Logger] ||= Log::new 92 @logger = @config[:Logger] 93 94 @tokens = Thread::SizedQueue.new(@config[:MaxClients]) 95 @config[:MaxClients].times{ @tokens.push(nil) } 96 97 webrickv = WEBrick::VERSION 98 rubyv = "#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" 99 @logger.info("WEBrick #{webrickv}") 100 @logger.info("ruby #{rubyv}") 101 102 @listeners = [] 103 @shutdown_pipe = nil 104 unless @config[:DoNotListen] 105 if @config[:Listen] 106 warn(":Listen option is deprecated; use GenericServer#listen", uplevel: 1) 107 end 108 listen(@config[:BindAddress], @config[:Port]) 109 if @config[:Port] == 0 110 @config[:Port] = @listeners[0].addr[1] 111 end 112 end 113 end 114 115 ## 116 # Retrieves +key+ from the configuration 117 118 def [](key) 119 @config[key] 120 end 121 122 ## 123 # Adds listeners from +address+ and +port+ to the server. See 124 # WEBrick::Utils::create_listeners for details. 125 126 def listen(address, port) 127 @listeners += Utils::create_listeners(address, port) 128 end 129 130 ## 131 # Starts the server and runs the +block+ for each connection. This method 132 # does not return until the server is stopped from a signal handler or 133 # another thread using #stop or #shutdown. 134 # 135 # If the block raises a subclass of StandardError the exception is logged 136 # and ignored. If an IOError or Errno::EBADF exception is raised the 137 # exception is ignored. If an Exception subclass is raised the exception 138 # is logged and re-raised which stops the server. 139 # 140 # To completely shut down a server call #shutdown from ensure: 141 # 142 # server = WEBrick::GenericServer.new 143 # # or WEBrick::HTTPServer.new 144 # 145 # begin 146 # server.start 147 # ensure 148 # server.shutdown 149 # end 150 151 def start(&block) 152 raise ServerError, "already started." if @status != :Stop 153 server_type = @config[:ServerType] || SimpleServer 154 155 setup_shutdown_pipe 156 157 server_type.start{ 158 @logger.info \ 159 "#{self.class}#start: pid=#{$$} port=#{@config[:Port]}" 160 @status = :Running 161 call_callback(:StartCallback) 162 163 shutdown_pipe = @shutdown_pipe 164 165 thgroup = ThreadGroup.new 166 begin 167 while @status == :Running 168 begin 169 sp = shutdown_pipe[0] 170 if svrs = IO.select([sp, *@listeners]) 171 if svrs[0].include? sp 172 # swallow shutdown pipe 173 buf = String.new 174 nil while String === 175 sp.read_nonblock([sp.nread, 8].max, buf, exception: false) 176 break 177 end 178 svrs[0].each{|svr| 179 @tokens.pop # blocks while no token is there. 180 if sock = accept_client(svr) 181 unless config[:DoNotReverseLookup].nil? 182 sock.do_not_reverse_lookup = !!config[:DoNotReverseLookup] 183 end 184 th = start_thread(sock, &block) 185 th[:WEBrickThread] = true 186 thgroup.add(th) 187 else 188 @tokens.push(nil) 189 end 190 } 191 end 192 rescue Errno::EBADF, Errno::ENOTSOCK, IOError => ex 193 # if the listening socket was closed in GenericServer#shutdown, 194 # IO::select raise it. 195 rescue StandardError => ex 196 msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}" 197 @logger.error msg 198 rescue Exception => ex 199 @logger.fatal ex 200 raise 201 end 202 end 203 ensure 204 cleanup_shutdown_pipe(shutdown_pipe) 205 cleanup_listener 206 @status = :Shutdown 207 @logger.info "going to shutdown ..." 208 thgroup.list.each{|th| th.join if th[:WEBrickThread] } 209 call_callback(:StopCallback) 210 @logger.info "#{self.class}#start done." 211 @status = :Stop 212 end 213 } 214 end 215 216 ## 217 # Stops the server from accepting new connections. 218 219 def stop 220 if @status == :Running 221 @status = :Shutdown 222 end 223 224 alarm_shutdown_pipe {|f| f.write_nonblock("\0")} 225 end 226 227 ## 228 # Shuts down the server and all listening sockets. New listeners must be 229 # provided to restart the server. 230 231 def shutdown 232 stop 233 234 alarm_shutdown_pipe(&:close) 235 end 236 237 ## 238 # You must subclass GenericServer and implement \#run which accepts a TCP 239 # client socket 240 241 def run(sock) 242 @logger.fatal "run() must be provided by user." 243 end 244 245 private 246 247 # :stopdoc: 248 249 ## 250 # Accepts a TCP client socket from the TCP server socket +svr+ and returns 251 # the client socket. 252 253 def accept_client(svr) 254 case sock = svr.to_io.accept_nonblock(exception: false) 255 when :wait_readable 256 nil 257 else 258 if svr.respond_to?(:start_immediately) 259 sock = OpenSSL::SSL::SSLSocket.new(sock, ssl_context) 260 sock.sync_close = true 261 # we cannot do OpenSSL::SSL::SSLSocket#accept here because 262 # a slow client can prevent us from accepting connections 263 # from other clients 264 end 265 sock 266 end 267 rescue Errno::ECONNRESET, Errno::ECONNABORTED, 268 Errno::EPROTO, Errno::EINVAL 269 nil 270 rescue StandardError => ex 271 msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}" 272 @logger.error msg 273 nil 274 end 275 276 ## 277 # Starts a server thread for the client socket +sock+ that runs the given 278 # +block+. 279 # 280 # Sets the socket to the <code>:WEBrickSocket</code> thread local variable 281 # in the thread. 282 # 283 # If any errors occur in the block they are logged and handled. 284 285 def start_thread(sock, &block) 286 Thread.start{ 287 begin 288 Thread.current[:WEBrickSocket] = sock 289 begin 290 addr = sock.peeraddr 291 @logger.debug "accept: #{addr[3]}:#{addr[1]}" 292 rescue SocketError 293 @logger.debug "accept: <address unknown>" 294 raise 295 end 296 if sock.respond_to?(:sync_close=) && @config[:SSLStartImmediately] 297 WEBrick::Utils.timeout(@config[:RequestTimeout]) do 298 begin 299 sock.accept # OpenSSL::SSL::SSLSocket#accept 300 rescue Errno::ECONNRESET, Errno::ECONNABORTED, 301 Errno::EPROTO, Errno::EINVAL 302 Thread.exit 303 end 304 end 305 end 306 call_callback(:AcceptCallback, sock) 307 block ? block.call(sock) : run(sock) 308 rescue Errno::ENOTCONN 309 @logger.debug "Errno::ENOTCONN raised" 310 rescue ServerError => ex 311 msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}" 312 @logger.error msg 313 rescue Exception => ex 314 @logger.error ex 315 ensure 316 @tokens.push(nil) 317 Thread.current[:WEBrickSocket] = nil 318 if addr 319 @logger.debug "close: #{addr[3]}:#{addr[1]}" 320 else 321 @logger.debug "close: <address unknown>" 322 end 323 sock.close 324 end 325 } 326 end 327 328 ## 329 # Calls the callback +callback_name+ from the configuration with +args+ 330 331 def call_callback(callback_name, *args) 332 @config[callback_name]&.call(*args) 333 end 334 335 def setup_shutdown_pipe 336 return @shutdown_pipe ||= IO.pipe 337 end 338 339 def cleanup_shutdown_pipe(shutdown_pipe) 340 @shutdown_pipe = nil 341 shutdown_pipe&.each(&:close) 342 end 343 344 def alarm_shutdown_pipe 345 _, pipe = @shutdown_pipe # another thread may modify @shutdown_pipe. 346 if pipe 347 if !pipe.closed? 348 begin 349 yield pipe 350 rescue IOError # closed by another thread. 351 end 352 end 353 end 354 end 355 356 def cleanup_listener 357 @listeners.each{|s| 358 if @logger.debug? 359 addr = s.addr 360 @logger.debug("close TCPSocket(#{addr[2]}, #{addr[1]})") 361 end 362 begin 363 s.shutdown 364 rescue Errno::ENOTCONN 365 # when `Errno::ENOTCONN: Socket is not connected' on some platforms, 366 # call #close instead of #shutdown. 367 # (ignore @config[:ShutdownSocketWithoutClose]) 368 s.close 369 else 370 unless @config[:ShutdownSocketWithoutClose] 371 s.close 372 end 373 end 374 } 375 @listeners.clear 376 end 377 end # end of GenericServer 378end 379