3 # Copyright 2009, Google Inc.
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions are
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following disclaimer
14 # in the documentation and/or other materials provided with the
16 # * Neither the name of Google Inc. nor the names of its
17 # contributors may be used to endorse or promote products derived from
18 # this software without specific prior written permission.
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 """Standalone Web Socket server.
35 Use this server to run mod_pywebsocket without Apache HTTP Server.
38 python standalone.py [-p <ws_port>] [-w <websock_handlers>]
41 ... for other options, see _main below ...
43 <ws_port> is the port number to use for ws:// connection.
45 <document_root> is the path to the root directory of HTML files.
47 <websock_handlers> is the path to the root directory of Web Socket handlers.
48 See __init__.py for details of <websock_handlers> and how to write Web Socket
49 handlers. If this path is relative, <document_root> is used as the base.
51 <scan_dir> is a path under the root directory. If specified, only the handlers
52 under scan_dir are scanned. This is useful in saving scan time.
55 This server is derived from SocketServer.ThreadingMixIn. Hence a thread is
56 used for each request.
60 import SimpleHTTPServer
63 import logging.handlers
81 'debug': logging.DEBUG,
84 'error': logging.ERROR,
85 'critical': logging.CRITICAL};
87 _DEFAULT_LOG_MAX_BYTES = 1024 * 256
88 _DEFAULT_LOG_BACKUP_COUNT = 5
91 def _print_warnings_if_any(dispatcher):
92 warnings = dispatcher.source_warnings()
94 for warning in warnings:
95 logging.warning('mod_pywebsocket: %s' % warning)
98 class _StandaloneConnection(object):
99 """Mimic mod_python mp_conn."""
101 def __init__(self, request_handler):
102 """Construct an instance.
105 request_handler: A WebSocketRequestHandler instance.
107 self._request_handler = request_handler
109 def get_local_addr(self):
110 """Getter to mimic mp_conn.local_addr."""
111 return (self._request_handler.server.server_name,
112 self._request_handler.server.server_port)
113 local_addr = property(get_local_addr)
115 def write(self, data):
116 """Mimic mp_conn.write()."""
117 return self._request_handler.wfile.write(data)
119 def read(self, length):
120 """Mimic mp_conn.read()."""
121 return self._request_handler.rfile.read(length)
124 class _StandaloneRequest(object):
125 """Mimic mod_python request."""
127 def __init__(self, request_handler, use_tls):
128 """Construct an instance.
131 request_handler: A WebSocketRequestHandler instance.
133 self._request_handler = request_handler
134 self.connection = _StandaloneConnection(request_handler)
135 self._use_tls = use_tls
138 """Getter to mimic request.uri."""
139 return self._request_handler.path
140 uri = property(get_uri)
142 def get_headers_in(self):
143 """Getter to mimic request.headers_in."""
144 return self._request_handler.headers
145 headers_in = property(get_headers_in)
148 """Mimic request.is_https()."""
152 class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
153 """HTTPServer specialized for Web Socket."""
155 SocketServer.ThreadingMixIn.daemon_threads = True
157 def __init__(self, server_address, RequestHandlerClass):
158 """Override SocketServer.BaseServer.__init__."""
160 SocketServer.BaseServer.__init__(
161 self, server_address, RequestHandlerClass)
162 self.socket = self._create_socket()
164 self.server_activate()
166 def _create_socket(self):
167 socket_ = socket.socket(self.address_family, self.socket_type)
168 if WebSocketServer.options.use_tls:
169 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
170 ctx.use_privatekey_file(WebSocketServer.options.private_key)
171 ctx.use_certificate_file(WebSocketServer.options.certificate)
172 socket_ = OpenSSL.SSL.Connection(ctx, socket_)
176 class WebSocketRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
177 """SimpleHTTPRequestHandler specialized for Web Socket."""
180 """Override SocketServer.StreamRequestHandler.setup."""
182 self.connection = self.request
183 self.rfile = socket._fileobject(self.request, 'rb', self.rbufsize)
184 self.wfile = socket._fileobject(self.request, 'wb', self.wbufsize)
186 def __init__(self, *args, **keywords):
187 self._request = _StandaloneRequest(
188 self, WebSocketRequestHandler.options.use_tls)
189 self._dispatcher = WebSocketRequestHandler.options.dispatcher
190 self._print_warnings_if_any()
191 self._handshaker = handshake.Handshaker(self._request,
193 SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(
194 self, *args, **keywords)
196 def _print_warnings_if_any(self):
197 warnings = self._dispatcher.source_warnings()
199 for warning in warnings:
200 logging.warning('mod_pywebsocket: %s' % warning)
202 def parse_request(self):
203 """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request.
205 Return True to continue processing for HTTP(S), False otherwise.
207 result = SimpleHTTPServer.SimpleHTTPRequestHandler.parse_request(self)
210 self._handshaker.do_handshake()
211 self._dispatcher.transfer_data(self._request)
213 except handshake.HandshakeError, e:
214 # Handshake for ws(s) failed. Assume http(s).
215 logging.info('mod_pywebsocket: %s' % e)
217 except dispatch.DispatchError, e:
218 logging.warning('mod_pywebsocket: %s' % e)
222 def log_request(self, code='-', size='-'):
223 """Override BaseHTTPServer.log_request."""
225 logging.info('"%s" %s %s',
226 self.requestline, str(code), str(size))
228 def log_error(self, *args):
229 """Override BaseHTTPServer.log_error."""
231 # Despite the name, this method is for warnings than for errors.
232 # For example, HTTP status code is logged by this method.
233 logging.warn('%s - %s' % (self.address_string(), (args[0] % args[1:])))
236 def _configure_logging(options):
237 logger = logging.getLogger()
238 logger.setLevel(_LOG_LEVELS[options.log_level])
240 handler = logging.handlers.RotatingFileHandler(
241 options.log_file, 'a', options.log_max, options.log_count)
243 handler = logging.StreamHandler()
244 formatter = logging.Formatter(
245 "[%(asctime)s] [%(levelname)s] %(name)s: %(message)s")
246 handler.setFormatter(formatter)
247 logger.addHandler(handler)
251 parser = optparse.OptionParser()
252 parser.add_option('-p', '--port', dest='port', type='int',
253 default=handshake._DEFAULT_WEB_SOCKET_PORT,
254 help='port to listen to')
255 parser.add_option('-w', '--websock_handlers', dest='websock_handlers',
257 help='Web Socket handlers root directory.')
258 parser.add_option('-s', '--scan_dir', dest='scan_dir',
260 help=('Web Socket handlers scan directory. '
261 'Must be a directory under websock_handlers.'))
262 parser.add_option('-d', '--document_root', dest='document_root',
264 help='Document root directory.')
265 parser.add_option('-t', '--tls', dest='use_tls', action='store_true',
266 default=False, help='use TLS (wss://)')
267 parser.add_option('-k', '--private_key', dest='private_key',
268 default='', help='TLS private key file.')
269 parser.add_option('-c', '--certificate', dest='certificate',
270 default='', help='TLS certificate file.')
271 parser.add_option('-l', '--log_file', dest='log_file',
272 default='', help='Log file.')
273 parser.add_option('--log_level', type='choice', dest='log_level',
275 choices=['debug', 'info', 'warn', 'error', 'critical'],
277 parser.add_option('--log_max', dest='log_max', type='int',
278 default=_DEFAULT_LOG_MAX_BYTES,
279 help='Log maximum bytes')
280 parser.add_option('--log_count', dest='log_count', type='int',
281 default=_DEFAULT_LOG_BACKUP_COUNT,
282 help='Log backup count')
283 options = parser.parse_args()[0]
285 os.chdir(options.document_root)
287 _configure_logging(options)
290 if not _HAS_OPEN_SSL:
291 logging.critical('To use TLS, install pyOpenSSL.')
293 if not options.private_key or not options.certificate:
295 'To use TLS, specify private_key and certificate.')
298 if not options.scan_dir:
299 options.scan_dir = options.websock_handlers
302 # Share a Dispatcher among request handlers to save time for
303 # instantiation. Dispatcher can be shared because it is thread-safe.
304 options.dispatcher = dispatch.Dispatcher(options.websock_handlers,
306 _print_warnings_if_any(options.dispatcher)
308 WebSocketRequestHandler.options = options
309 WebSocketServer.options = options
311 server = WebSocketServer(('', options.port), WebSocketRequestHandler)
312 server.serve_forever()
314 logging.critical(str(e))
318 if __name__ == '__main__':