/usr/lib/ruby/1.9.1/gserver.rb is in libruby1.9.1 1.9.3.0-1ubuntu1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 | #
# Copyright (C) 2001 John W. Small All Rights Reserved
#
# Author:: John W. Small
# Documentation:: Gavin Sinclair
# Licence:: Freeware.
require "socket"
require "thread"
#
# GServer implements a generic server, featuring thread pool management,
# simple logging, and multi-server management. See HttpServer in
# <tt>xmlrpc/httpserver.rb</tt> in the Ruby standard library for an example of
# GServer in action.
#
# Any kind of application-level server can be implemented using this class.
# It accepts multiple simultaneous connections from clients, up to an optional
# maximum number. Several _services_ (i.e. one service per TCP port) can be
# run simultaneously, and stopped at any time through the class method
# <tt>GServer.stop(port)</tt>. All the threading issues are handled, saving
# you the effort. All events are optionally logged, but you can provide your
# own event handlers if you wish.
#
# == Example
#
# Using GServer is simple. Below we implement a simple time server, run it,
# query it, and shut it down. Try this code in +irb+:
#
# require 'gserver'
#
# #
# # A server that returns the time in seconds since 1970.
# #
# class TimeServer < GServer
# def initialize(port=10001, *args)
# super(port, *args)
# end
# def serve(io)
# io.puts(Time.now.to_s)
# end
# end
#
# # Run the server with logging enabled (it's a separate thread).
# server = TimeServer.new
# server.audit = true # Turn logging on.
# server.start
#
# # *** Now point your browser to http://localhost:10001 to see it working ***
#
# # See if it's still running.
# GServer.in_service?(10001) # -> true
# server.stopped? # -> false
#
# # Shut the server down gracefully.
# server.shutdown
#
# # Alternatively, stop it immediately.
# GServer.stop(10001)
# # or, of course, "server.stop".
#
# All the business of accepting connections and exception handling is taken
# care of. All we have to do is implement the method that actually serves the
# client.
#
# === Advanced
#
# As the example above shows, the way to use GServer is to subclass it to
# create a specific server, overriding the +serve+ method. You can override
# other methods as well if you wish, perhaps to collect statistics, or emit
# more detailed logging.
#
# * #connecting
# * #disconnecting
# * #starting
# * #stopping
#
# The above methods are only called if auditing is enabled, via #audit=.
#
# You can also override #log and #error if, for example, you wish to use a
# more sophisticated logging system.
#
class GServer
DEFAULT_HOST = "127.0.0.1"
def serve(io)
end
@@services = {} # Hash of opened ports, i.e. services
@@servicesMutex = Mutex.new
# Stop the server running on the given port, bound to the given host
#
# +port+:: port, as a FixNum, of the server to stop
# +host+:: host on which to find the server to stop
def GServer.stop(port, host = DEFAULT_HOST)
@@servicesMutex.synchronize {
@@services[host][port].stop
}
end
# Check if a server is running on the given port and host
#
# +port+:: port, as a FixNum, of the server to check
# +host+:: host on which to find the server to check
#
# Returns true if a server is running on that port and host.
def GServer.in_service?(port, host = DEFAULT_HOST)
@@services.has_key?(host) and
@@services[host].has_key?(port)
end
# Stop the server
def stop
@connectionsMutex.synchronize {
if @tcpServerThread
@tcpServerThread.raise "stop"
end
}
end
# Returns true if the server has stopped.
def stopped?
@tcpServerThread == nil
end
# Schedule a shutdown for the server
def shutdown
@shutdown = true
end
# Return the current number of connected clients
def connections
@connections.size
end
# Join with the server thread
def join
@tcpServerThread.join if @tcpServerThread
end
# Port on which to listen, as a FixNum
attr_reader :port
# Host on which to bind, as a String
attr_reader :host
# Maximum number of connections to accept at at ime, as a FixNum
attr_reader :maxConnections
# IO Device on which log messages should be written
attr_accessor :stdlog
# Set to true to cause the callbacks #connecting, #disconnecting, #starting,
# and #stopping to be called during the server's lifecycle
attr_accessor :audit
# Set to true to show more detailed logging
attr_accessor :debug
# Called when a client connects, if auditing is enabled.
#
# +client+:: a TCPSocket instances representing the client that connected
#
# Return true to allow this client to connect, false to prevent it.
def connecting(client)
addr = client.peeraddr
log("#{self.class.to_s} #{@host}:#{@port} client:#{addr[1]} " +
"#{addr[2]}<#{addr[3]}> connect")
true
end
# Called when a client disconnects, if audition is enabled.
#
# +clientPort+:: the port of the client that is connecting
def disconnecting(clientPort)
log("#{self.class.to_s} #{@host}:#{@port} " +
"client:#{clientPort} disconnect")
end
protected :connecting, :disconnecting
# Called when the server is starting up, if auditing is enabled.
def starting()
log("#{self.class.to_s} #{@host}:#{@port} start")
end
# Called when the server is shutting down, if auditing is enabled.
def stopping()
log("#{self.class.to_s} #{@host}:#{@port} stop")
end
protected :starting, :stopping
# Called if #debug is true whenever an unhandled exception is raised.
# This implementation simply logs the backtrace.
#
# +detail+:: The Exception that was caught
def error(detail)
log(detail.backtrace.join("\n"))
end
# Log a message to #stdlog, if it's defined. This implementation
# outputs the timestamp and message to the log.
#
# +msg+:: the message to log
def log(msg)
if @stdlog
@stdlog.puts("[#{Time.new.ctime}] %s" % msg)
@stdlog.flush
end
end
protected :error, :log
# Create a new server
#
# +port+:: the port, as a FixNum, on which to listen.
# +host+:: the host to bind to
# +maxConnections+:: The maximum number of simultaneous connections to
# accept
# +stdlog+:: IO device on which to log messages
# +audit+:: if true, lifecycle callbacks will be called. See #audit
# +debug+:: if true, error messages are logged. See #debug
def initialize(port, host = DEFAULT_HOST, maxConnections = 4,
stdlog = $stderr, audit = false, debug = false)
@tcpServerThread = nil
@port = port
@host = host
@maxConnections = maxConnections
@connections = []
@connectionsMutex = Mutex.new
@connectionsCV = ConditionVariable.new
@stdlog = stdlog
@audit = audit
@debug = debug
end
# Start the server if it isn't already running
#
# +maxConnections+::
# override +maxConnections+ given to the constructor. A negative
# value indicates that the value from the constructor should be used.
def start(maxConnections = -1)
raise "server is already running" if !stopped?
@shutdown = false
@maxConnections = maxConnections if maxConnections > 0
@@servicesMutex.synchronize {
if GServer.in_service?(@port,@host)
raise "Port already in use: #{host}:#{@port}!"
end
@tcpServer = TCPServer.new(@host,@port)
@port = @tcpServer.addr[1]
@@services[@host] = {} unless @@services.has_key?(@host)
@@services[@host][@port] = self;
}
@tcpServerThread = Thread.new {
begin
starting if @audit
while !@shutdown
@connectionsMutex.synchronize {
while @connections.size >= @maxConnections
@connectionsCV.wait(@connectionsMutex)
end
}
client = @tcpServer.accept
@connections << Thread.new(client) { |myClient|
begin
myPort = myClient.peeraddr[1]
serve(myClient) if !@audit or connecting(myClient)
rescue => detail
error(detail) if @debug
ensure
begin
myClient.close
rescue
end
@connectionsMutex.synchronize {
@connections.delete(Thread.current)
@connectionsCV.signal
}
disconnecting(myPort) if @audit
end
}
end
rescue => detail
error(detail) if @debug
ensure
begin
@tcpServer.close
rescue
end
if @shutdown
@connectionsMutex.synchronize {
while @connections.size > 0
@connectionsCV.wait(@connectionsMutex)
end
}
else
@connections.each { |c| c.raise "stop" }
end
@tcpServerThread = nil
@@servicesMutex.synchronize {
@@services[@host].delete(@port)
}
stopping if @audit
end
}
self
end
end
|