1 """a U{http://wsgi.org/wsgi wsgi} server based on L{wsgiref} and L{ThreadPool}
2
3 Whoever wrote SocketServer (and its various spawn, including BaseHTTPServer &
4 wsgiref) should be shot, and their corpse killed until it is dead. The author
5 of the WSGI standard itself should then be buried alive with the
6 aforementioned corpse. In the author's opinion, this would be a fate too kind
7 for both of them.
8 """
9
10 import time
11 import signal
12 import logging
13 from wsgiref import handlers
14 import wsgiref.simple_server
15
16 from grassyknoll.lib.meta import AutoLogger
17
18 import ThreadPool
19 import MailBox
20 import Worker
21 import Message
22 import Wrappers
23 from errors import *
24
25 __version__="0.3"
26
27 default_too_busy_content="""
28 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
29 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
30
31 <head>
32 <title>Service Unavailable</title>
33 </head>
34
35 <body>
36 <h1>Service Unavailable</h1>
37 <p>The server is too busy to handle your request. Please try again in a few minutes.</p>
38 </body>
39 </html>
40 """
41
43
44 server_version = "GrassyKnoll-WhiskeyServer/" + __version__
45
46 logger=AutoLogger()
47
49 self.logger.warn("[%s] "+format, self.address_string(), *args)
50
52 """Log an arbitrary message.
53
54 Prepends currentThread() to standard L{BaseHTTPServer.BaseHTTPRequestHandler.log_message}
55 """
56 self.logger.info("[%s] "+format, self.address_string(), *args)
57
58
60 logger=AutoLogger()
62 self.logger.error("Internal Error", exc_info=True)
63 if not self.headers_sent:
64 self.result = self.error_output(self.environ, self.start_response)
65 self.finish_response()
66 wsgiref.simple_server.ServerHandler=WhiskeyServerHandler
67
69 """a threadpool-based wsgi server.
70
71 @ivar threadpool: a threadpool to handle requests
72 @type threadpool: L{ThreadPool.ThreadPool}
73 """
74 logger=AutoLogger()
75
79 """
80 C{host}, C{port} and C{handler_class} are interpreted by L{WSGIServer}.
81
82 C{num_workers} and C{qsize} are interpreted by L{ThreadPool.ThreadPool}
83
84 @arg too_busy_content: text/html content to send to browsers when the
85 server is overloaded. This content is sent with a Status: 503 header
86 and the connection is closed.
87 @type too_busy_content: str
88 """
89 if not isinstance(too_busy_content, str):
90 raise TypeError("too_busy_content must be str, "
91 "not %r"%type(too_busy_content).__name__)
92
93 self.__too_busy_response=self.__make_too_busy_response(too_busy_content)
94
95 self.threadpool=ThreadPool.ThreadPool(
96 Wrappers.NullContextManager.factory(func=self.process_request_thread),
97 inbox=MailBox.ThreadMailBox(name="whiskeyserver", qsize=qsize),
98 name="whiskeyserver",
99 num_workers=num_workers,
100 unhandled="keeprunning")
101
102 wsgiref.simple_server.WSGIServer.__init__(self, host_port, handler_class)
103 self._stop=False
104
106 """Handle one request at a time until shutdown()."""
107 self.threadpool.start()
108 while not self._stop:
109 self.handle_request()
110
111 self.logger.info("Server shutting down")
112 self.server_close()
113 self.threadpool.stop()
114 self.logger.info("Shutdown complete")
115
117 """stop the server"""
118
119
120
121 self._stop=True
122
124 """install a handler for SIGTERM, providing clean shutdown"""
125 def signal_handler(foo, bar):
126 self.logger.debug("Got SIGTERM, shutting down")
127 self.shutdown()
128
129 return signal.signal(signal.SIGTERM, signal_handler)
130
131 @Wrappers.methodMessenger()
133 """process a single request, in a L{Worker.Worker} thread.
134
135 Derrived from L{SocketServer.ThreadingMixIn}
136
137 @arg request_client_address: a handle to a client request. a 2-tuple
138 of (socket, client_address).
139 @type request_client_address: tuple
140 """
141 try:
142 self.finish_request(request, client_address)
143 finally:
144 self.close_request(request)
145
147 """process incoming client requests.
148
149 This method delivers the requests to the server's
150 L{ThreadPool.ThreadPool} to be serviced.
151
152 If the threadpool raises L{BoxFullError}, call L{too_busy} and allow
153 the connection to be closed immediately.
154 """
155 mesg = Message.Message(Wrappers.FunctionCall(request, client_address))
156 try:
157 self.threadpool.inbox.sendNow(mesg)
158 except BoxFullError:
159 self.too_busy(request, client_address)
160
161 - def too_busy(self, request, client_address):
162 """Called when the threadpool won't accept new requests.
163
164 This method sends too_busy_content to client with a Status: 503 header.
165
166 It also calls L{log_too_busy}.
167 """
168 self.log_too_busy(client_address[0])
169 request.sendall(self.__too_busy_response)
170 self.close_request(request)
171
173 """Called to log L{too_busy}
174
175 Subclassers may override, but should run as quickly as possible, as
176 your server is on fire.
177
178 @arg remote_ip: the IP address of the remote connection
179 @type remote_ip: string
180 """
181
182 self.logger.warn("[%s] Too Busy", remote_ip)
183
185 status = '503'
186 protocol='HTTP/1.0'
187
188 headers=[('Content-Type', 'text/html'),
189 ('Date', handlers.format_date_time(time.time())),
190 ('Status', status),
191 ('Connection', 'close')]
192
193 return self.__format_simple_response(protocol, status, headers, msg)
194
205