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>]
42 <ws_port> is the port number to use for ws:// connection.
44 <document_root> is the path to the root directory of HTML files.
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.
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.
54 This server is derived from SocketServer.ThreadingMixIn. Hence a thread is
55 used for each request.
59 import SimpleHTTPServer
78 class _StandaloneConnection(object):
79 """Mimic mod_python mp_conn."""
81 def __init__(self, request_handler):
82 """Construct an instance.
85 request_handler: A WebSocketRequestHandler instance.
87 self._request_handler = request_handler
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)
95 def write(self, data):
96 """Mimic mp_conn.write()."""
97 return self._request_handler.wfile.write(data)
99 def read(self, length):
100 """Mimic mp_conn.read()."""
101 return self._request_handler.rfile.read(length)
104 class _StandaloneRequest(object):
105 """Mimic mod_python request."""
107 def __init__(self, request_handler, use_tls):
108 """Construct an instance.
111 request_handler: A WebSocketRequestHandler instance.
113 self._request_handler = request_handler
114 self.connection = _StandaloneConnection(request_handler)
115 self._use_tls = use_tls
118 """Getter to mimic request.uri."""
119 return self._request_handler.path
120 uri = property(get_uri)
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)
128 """Mimic request.is_https()."""
132 class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
133 """HTTPServer specialized for Web Socket."""
135 SocketServer.ThreadingMixIn.daemon_threads = True
137 def __init__(self, server_address, RequestHandlerClass):
138 """Override SocketServer.BaseServer.__init__."""
140 SocketServer.BaseServer.__init__(
141 self, server_address, RequestHandlerClass)
142 self.socket = self._create_socket()
144 self.server_activate()
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_)
155 class WebSocketRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
156 """SimpleHTTPRequestHandler specialized for Web Socket."""
159 """Override SocketServer.StreamRequestHandler.setup."""
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)
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,
174 SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(
175 self, *args, **keywords)
177 def _print_warnings_if_any(self):
178 warnings = self._dispatcher.source_warnings()
180 for warning in warnings:
181 logging.warning('mod_pywebsocket: %s' % warning)
183 def parse_request(self):
184 """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request.
186 Return True to continue processing for HTTP(S), False otherwise.
188 result = SimpleHTTPServer.SimpleHTTPRequestHandler.parse_request(self)
191 self._handshaker.do_handshake()
192 self._dispatcher.transfer_data(self._request)
194 except handshake.HandshakeError, e:
195 # Handshake for ws(s) failed. Assume http(s).
196 logging.info('mod_pywebsocket: %s' % e)
198 except dispatch.DispatchError, e:
199 logging.warning('mod_pywebsocket: %s' % e)
205 logging.basicConfig()
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',
213 help='Web Socket handlers root directory.')
214 parser.add_option('-s', '--scan_dir', dest='scan_dir',
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',
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]
230 if not _HAS_OPEN_SSL:
231 print >>sys.stderr, 'To use TLS, install pyOpenSSL.'
233 if not options.private_key or not options.certificate:
234 print >>sys.stderr, ('To use TLS, specify private_key and '
238 if not options.scan_dir:
239 options.scan_dir = options.websock_handlers
241 WebSocketRequestHandler.options = options
242 WebSocketServer.options = options
244 os.chdir(options.document_root)
246 server = WebSocketServer(('', options.port), WebSocketRequestHandler)
247 server.serve_forever()
250 if __name__ == '__main__':