2009-11-25 Yuzo Fujishima <yuzo@google.com>
[WebKit-https.git] / WebKitTools / pywebsocket / mod_pywebsocket / standalone.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2009, Google Inc.
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions are
8 # met:
9 #
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
15 # distribution.
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.
19 #
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.
31
32
33 """Standalone Web Socket server.
34
35 Use this server to run mod_pywebsocket without Apache HTTP Server.
36
37 Usage:
38     python standalone.py [-p <ws_port>] [-w <websock_handlers>]
39                          [-s <scan_dir>]
40                          [-d <document_root>]
41                          ... for other options, see _main below ...
42
43 <ws_port> is the port number to use for ws:// connection.
44
45 <document_root> is the path to the root directory of HTML files.
46
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.
50
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.
53
54 Note:
55 This server is derived from SocketServer.ThreadingMixIn. Hence a thread is
56 used for each request.
57 """
58
59 import BaseHTTPServer
60 import SimpleHTTPServer
61 import SocketServer
62 import logging
63 import logging.handlers
64 import optparse
65 import os
66 import socket
67 import sys
68
69 _HAS_OPEN_SSL = False
70 try:
71     import OpenSSL.SSL
72     _HAS_OPEN_SSL = True
73 except ImportError:
74     pass
75
76 import dispatch
77 import handshake
78
79
80 _LOG_LEVELS = {
81     'debug': logging.DEBUG,
82     'info': logging.INFO,
83     'warn': logging.WARN,
84     'error': logging.ERROR,
85     'critical': logging.CRITICAL};
86
87 _DEFAULT_LOG_MAX_BYTES = 1024 * 256
88 _DEFAULT_LOG_BACKUP_COUNT = 5
89
90
91 def _print_warnings_if_any(dispatcher):
92     warnings = dispatcher.source_warnings()
93     if warnings:
94         for warning in warnings:
95             logging.warning('mod_pywebsocket: %s' % warning)
96
97
98 class _StandaloneConnection(object):
99     """Mimic mod_python mp_conn."""
100
101     def __init__(self, request_handler):
102         """Construct an instance.
103
104         Args:
105             request_handler: A WebSocketRequestHandler instance.
106         """
107         self._request_handler = request_handler
108
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)
114
115     def write(self, data):
116         """Mimic mp_conn.write()."""
117         return self._request_handler.wfile.write(data)
118
119     def read(self, length):
120         """Mimic mp_conn.read()."""
121         return self._request_handler.rfile.read(length)
122
123
124 class _StandaloneRequest(object):
125     """Mimic mod_python request."""
126
127     def __init__(self, request_handler, use_tls):
128         """Construct an instance.
129
130         Args:
131             request_handler: A WebSocketRequestHandler instance.
132         """
133         self._request_handler = request_handler
134         self.connection = _StandaloneConnection(request_handler)
135         self._use_tls = use_tls
136
137     def get_uri(self):
138         """Getter to mimic request.uri."""
139         return self._request_handler.path
140     uri = property(get_uri)
141
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)
146
147     def is_https(self):
148         """Mimic request.is_https()."""
149         return self._use_tls
150
151
152 class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
153     """HTTPServer specialized for Web Socket."""
154
155     SocketServer.ThreadingMixIn.daemon_threads = True
156
157     def __init__(self, server_address, RequestHandlerClass):
158         """Override SocketServer.BaseServer.__init__."""
159
160         SocketServer.BaseServer.__init__(
161                 self, server_address, RequestHandlerClass)
162         self.socket = self._create_socket()
163         self.server_bind()
164         self.server_activate()
165
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_)
173         return socket_
174
175
176 class WebSocketRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
177     """SimpleHTTPRequestHandler specialized for Web Socket."""
178
179     def setup(self):
180         """Override SocketServer.StreamRequestHandler.setup."""
181
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)
185
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,
192                                                 self._dispatcher)
193         SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(
194                 self, *args, **keywords)
195
196     def _print_warnings_if_any(self):
197         warnings = self._dispatcher.source_warnings()
198         if warnings:
199             for warning in warnings:
200                 logging.warning('mod_pywebsocket: %s' % warning)
201
202     def parse_request(self):
203         """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request.
204
205         Return True to continue processing for HTTP(S), False otherwise.
206         """
207         result = SimpleHTTPServer.SimpleHTTPRequestHandler.parse_request(self)
208         if result:
209             try:
210                 self._handshaker.do_handshake()
211                 self._dispatcher.transfer_data(self._request)
212                 return False
213             except handshake.HandshakeError, e:
214                 # Handshake for ws(s) failed. Assume http(s).
215                 logging.info('mod_pywebsocket: %s' % e)
216                 return True
217             except dispatch.DispatchError, e:
218                 logging.warning('mod_pywebsocket: %s' % e)
219                 return False
220         return result
221
222     def log_request(self, code='-', size='-'):
223         """Override BaseHTTPServer.log_request."""
224
225         logging.info('"%s" %s %s',
226                      self.requestline, str(code), str(size))
227
228     def log_error(self, *args):
229         """Override BaseHTTPServer.log_error."""
230
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:])))
234
235
236 def _configure_logging(options):
237     logger = logging.getLogger()
238     logger.setLevel(_LOG_LEVELS[options.log_level])
239     if options.log_file:
240         handler = logging.handlers.RotatingFileHandler(
241                 options.log_file, 'a', options.log_max, options.log_count)
242     else:
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)
248
249
250 def _main():
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',
256                       default='.',
257                       help='Web Socket handlers root directory.')
258     parser.add_option('-s', '--scan_dir', dest='scan_dir',
259                       default=None,
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',
263                       default='.',
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',
274                       default='warn',
275                       choices=['debug', 'info', 'warn', 'error', 'critical'],
276                       help='Log level.')
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]
284
285     os.chdir(options.document_root)
286
287     _configure_logging(options)
288
289     if options.use_tls:
290         if not _HAS_OPEN_SSL:
291             logging.critical('To use TLS, install pyOpenSSL.')
292             sys.exit(1)
293         if not options.private_key or not options.certificate:
294             logging.critical(
295                     'To use TLS, specify private_key and certificate.')
296             sys.exit(1)
297
298     if not options.scan_dir:
299         options.scan_dir = options.websock_handlers
300
301     try:
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,
305                                                  options.scan_dir)
306         _print_warnings_if_any(options.dispatcher)
307
308         WebSocketRequestHandler.options = options
309         WebSocketServer.options = options
310
311         server = WebSocketServer(('', options.port), WebSocketRequestHandler)
312         server.serve_forever()
313     except Exception, e:
314         logging.critical(str(e))
315         sys.exit(1)
316
317
318 if __name__ == '__main__':
319     _main()
320
321
322 # vi:sts=4 sw=4 et