a4c142b4151d497f576c2e35d73239636f8514cb
[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
42 <ws_port> is the port number to use for ws:// connection.
43
44 <document_root> is the path to the root directory of HTML files.
45
46 <websock_handlers> is the path to the root directory of Web Socket handlers.
47 See __init__.py for details of <websock_handlers> and how to write Web Socket
48 handlers. If this path is relative, <document_root> is used as the base.
49
50 <scan_dir> is a path under the root directory. If specified, only the handlers
51 under scan_dir are scanned. This is useful in saving scan time.
52
53 Note:
54 This server is derived from SocketServer.ThreadingMixIn. Hence a thread is
55 used for each request.
56 """
57
58 import BaseHTTPServer
59 import SimpleHTTPServer
60 import SocketServer
61 import logging
62 import optparse
63 import os
64 import socket
65 import sys
66
67 _HAS_OPEN_SSL = False
68 try:
69     import OpenSSL.SSL
70     _HAS_OPEN_SSL = True
71 except ImportError:
72     pass
73
74 import dispatch
75 import handshake
76
77
78 class _StandaloneConnection(object):
79     """Mimic mod_python mp_conn."""
80
81     def __init__(self, request_handler):
82         """Construct an instance.
83
84         Args:
85             request_handler: A WebSocketRequestHandler instance.
86         """
87         self._request_handler = request_handler
88
89     def get_local_addr(self):
90         """Getter to mimic mp_conn.local_addr."""
91         return (self._request_handler.server.server_name,
92                 self._request_handler.server.server_port)
93     local_addr = property(get_local_addr)
94
95     def write(self, data):
96         """Mimic mp_conn.write()."""
97         return self._request_handler.wfile.write(data)
98
99     def read(self, length):
100         """Mimic mp_conn.read()."""
101         return self._request_handler.rfile.read(length)
102
103
104 class _StandaloneRequest(object):
105     """Mimic mod_python request."""
106
107     def __init__(self, request_handler, use_tls):
108         """Construct an instance.
109
110         Args:
111             request_handler: A WebSocketRequestHandler instance.
112         """
113         self._request_handler = request_handler
114         self.connection = _StandaloneConnection(request_handler)
115         self._use_tls = use_tls
116
117     def get_uri(self):
118         """Getter to mimic request.uri."""
119         return self._request_handler.path
120     uri = property(get_uri)
121
122     def get_headers_in(self):
123         """Getter to mimic request.headers_in."""
124         return self._request_handler.headers
125     headers_in = property(get_headers_in)
126
127     def is_https(self):
128         """Mimic request.is_https()."""
129         return self._use_tls
130
131
132 class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
133     """HTTPServer specialized for Web Socket."""
134
135     SocketServer.ThreadingMixIn.daemon_threads = True
136
137     def __init__(self, server_address, RequestHandlerClass):
138         """Override SocketServer.BaseServer.__init__."""
139
140         SocketServer.BaseServer.__init__(
141                 self, server_address, RequestHandlerClass)
142         self.socket = self._create_socket()
143         self.server_bind()
144         self.server_activate()
145
146     def _create_socket(self):
147         socket_ = socket.socket(self.address_family, self.socket_type)
148         if WebSocketServer.options.use_tls:
149             ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
150             ctx.use_privatekey_file(WebSocketServer.options.private_key)
151             ctx.use_certificate_file(WebSocketServer.options.certificate)
152             socket_ = OpenSSL.SSL.Connection(ctx, socket_)
153         return socket_
154
155 class WebSocketRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
156     """SimpleHTTPRequestHandler specialized for Web Socket."""
157
158     def setup(self):
159         """Override SocketServer.StreamRequestHandler.setup."""
160
161         self.connection = self.request
162         self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
163         self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
164
165     def __init__(self, *args, **keywords):
166         self._request = _StandaloneRequest(
167                 self, WebSocketRequestHandler.options.use_tls)
168         self._dispatcher = dispatch.Dispatcher(
169                 WebSocketRequestHandler.options.websock_handlers,
170                 WebSocketRequestHandler.options.scan_dir)
171         self._print_warnings_if_any()
172         self._handshaker = handshake.Handshaker(self._request,
173                                                 self._dispatcher)
174         SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(
175                 self, *args, **keywords)
176
177     def _print_warnings_if_any(self):
178         warnings = self._dispatcher.source_warnings()
179         if warnings:
180             for warning in warnings:
181                 logging.warning('mod_pywebsocket: %s' % warning)
182
183     def parse_request(self):
184         """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request.
185
186         Return True to continue processing for HTTP(S), False otherwise.
187         """
188         result = SimpleHTTPServer.SimpleHTTPRequestHandler.parse_request(self)
189         if result:
190             try:
191                 self._handshaker.do_handshake()
192                 self._dispatcher.transfer_data(self._request)
193                 return False
194             except handshake.HandshakeError, e:
195                 # Handshake for ws(s) failed. Assume http(s).
196                 logging.info('mod_pywebsocket: %s' % e)
197                 return True
198             except dispatch.DispatchError, e:
199                 logging.warning('mod_pywebsocket: %s' % e)
200                 return False
201         return result
202
203
204 def _main():
205     logging.basicConfig()
206
207     parser = optparse.OptionParser()
208     parser.add_option('-p', '--port', dest='port', type='int',
209                       default=handshake._DEFAULT_WEB_SOCKET_PORT,
210                       help='port to listen to')
211     parser.add_option('-w', '--websock_handlers', dest='websock_handlers',
212                       default='.',
213                       help='Web Socket handlers root directory.')
214     parser.add_option('-s', '--scan_dir', dest='scan_dir',
215                       default=None,
216                       help=('Web Socket handlers scan directory. '
217                             'Must be a directory under websock_handlers.'))
218     parser.add_option('-d', '--document_root', dest='document_root',
219                       default='.',
220                       help='Document root directory.')
221     parser.add_option('-t', '--tls', dest='use_tls', action='store_true',
222                       default=False, help='use TLS (wss://)')
223     parser.add_option('-k', '--private_key', dest='private_key',
224                       default='', help='TLS private key file.')
225     parser.add_option('-c', '--certificate', dest='certificate',
226                       default='', help='TLS certificate file.')
227     options = parser.parse_args()[0]
228
229     if options.use_tls:
230         if not _HAS_OPEN_SSL:
231             print >>sys.stderr, 'To use TLS, install pyOpenSSL.'
232             sys.exit(1)
233         if not options.private_key or not options.certificate:
234             print >>sys.stderr, ('To use TLS, specify private_key and '
235                                  'certificate.')
236             sys.exit(1)
237
238     if not options.scan_dir:
239         options.scan_dir = options.websock_handlers
240
241     WebSocketRequestHandler.options = options
242     WebSocketServer.options = options
243
244     os.chdir(options.document_root)
245
246     server = WebSocketServer(('', options.port), WebSocketRequestHandler)
247     server.serve_forever()
248
249
250 if __name__ == '__main__':
251     _main()
252
253
254 # vi:sts=4 sw=4 et