6d500cbe1bb84b10fe577c0accac2ce0e1d1ab06
[WebKit-https.git] / WebKitTools / pywebsocket / mod_pywebsocket / dispatch.py
1 # Copyright 2009, Google Inc.
2 # All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 #     * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 #     * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 #     * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30
31 """Dispatch Web Socket request.
32 """
33
34
35 import os
36 import re
37
38 import util
39
40
41 _SOURCE_PATH_PATTERN = re.compile(r'(?i)_wsh\.py$')
42 _SOURCE_SUFFIX = '_wsh.py'
43 _DO_EXTRA_HANDSHAKE_HANDLER_NAME = 'web_socket_do_extra_handshake'
44 _TRANSFER_DATA_HANDLER_NAME = 'web_socket_transfer_data'
45
46
47 class DispatchError(Exception):
48     """Exception in dispatching Web Socket request."""
49
50     pass
51
52
53 def _normalize_path(path):
54     """Normalize path.
55
56     Args:
57         path: the path to normalize.
58
59     Path is converted to the absolute path.
60     The input path can use either '\\' or '/' as the separator.
61     The normalized path always uses '/' regardless of the platform.
62     """
63
64     path = path.replace('\\', os.path.sep)
65     path = os.path.abspath(path)
66     path = path.replace('\\', '/')
67     return path
68
69
70 def _path_to_resource_converter(base_dir):
71     base_dir = _normalize_path(base_dir)
72     base_len = len(base_dir)
73     suffix_len = len(_SOURCE_SUFFIX)
74     def converter(path):
75         if not path.endswith(_SOURCE_SUFFIX):
76             return None
77         path = _normalize_path(path)
78         if not path.startswith(base_dir):
79             return None
80         return path[base_len:-suffix_len]
81     return converter
82
83
84 def _source_file_paths(directory):
85     """Yield Web Socket Handler source file names in the given directory."""
86
87     for root, unused_dirs, files in os.walk(directory):
88         for base in files:
89             path = os.path.join(root, base)
90             if _SOURCE_PATH_PATTERN.search(path):
91                 yield path
92
93
94 def _source(source_str):
95     """Source a handler definition string."""
96
97     global_dic = {}
98     try:
99         exec source_str in global_dic
100     except Exception:
101         raise DispatchError('Error in sourcing handler:' +
102                             util.get_stack_trace())
103     return (_extract_handler(global_dic, _DO_EXTRA_HANDSHAKE_HANDLER_NAME),
104             _extract_handler(global_dic, _TRANSFER_DATA_HANDLER_NAME))
105
106
107 def _extract_handler(dic, name):
108     if name not in dic:
109         raise DispatchError('%s is not defined.' % name)
110     handler = dic[name]
111     if not callable(handler):
112         raise DispatchError('%s is not callable.' % name)
113     return handler
114
115
116 class Dispatcher(object):
117     """Dispatches Web Socket requests.
118
119     This class maintains a map from resource name to handlers.
120     """
121
122     def __init__(self, root_dir, scan_dir=None):
123         """Construct an instance.
124
125         Args:
126             root_dir: The directory where handler definition files are
127                       placed.
128             scan_dir: The directory where handler definition files are
129                       searched. scan_dir must be a directory under root_dir,
130                       including root_dir itself.  If scan_dir is None, root_dir
131                       is used as scan_dir. scan_dir can be useful in saving
132                       scan time when root_dir contains many subdirectories.
133         """
134
135         self._handlers = {}
136         self._source_warnings = []
137         if scan_dir is None:
138             scan_dir = root_dir
139         if not os.path.realpath(scan_dir).startswith(os.path.realpath(root_dir)):
140             raise DispatchError('scan_dir:%s must be a directory under '
141                                 'root_dir:%s.' % (scan_dir, root_dir))
142         self._source_files_in_dir(root_dir, scan_dir)
143
144     def source_warnings(self):
145         """Return warnings in sourcing handlers."""
146
147         return self._source_warnings
148
149     def do_extra_handshake(self, request):
150         """Do extra checking in Web Socket handshake.
151
152         Select a handler based on request.uri and call its
153         web_socket_do_extra_handshake function.
154
155         Args:
156             request: mod_python request.
157         """
158
159         do_extra_handshake_, unused_transfer_data = self._handler(request)
160         try:
161             do_extra_handshake_(request)
162         except Exception:
163             raise DispatchError('%s raised exception: %s' %
164                     (_DO_EXTRA_HANDSHAKE_HANDLER_NAME, util.get_stack_trace()))
165
166     def transfer_data(self, request):
167         """Let a handler transfer_data with a Web Socket client.
168
169         Select a handler based on request.ws_resource and call its
170         web_socket_transfer_data function.
171
172         Args:
173             request: mod_python request.
174         """
175
176         unused_do_extra_handshake, transfer_data_ = self._handler(request)
177         try:
178             transfer_data_(request)
179         except Exception:
180             raise DispatchError('%s raised exception: %s' %
181                     (_TRANSFER_DATA_HANDLER_NAME, util.get_stack_trace()))
182
183     def _handler(self, request):
184         try:
185             return self._handlers[request.ws_resource]
186         except KeyError:
187             raise DispatchError('No handler for: %r' % request.ws_resource)
188
189     def _source_files_in_dir(self, root_dir, scan_dir):
190         """Source all the handler source files in the scan_dir directory.
191         
192         The resource path is determined relative to root_dir.
193         """
194
195         to_resource = _path_to_resource_converter(root_dir)
196         for path in _source_file_paths(scan_dir):
197             try:
198                 handlers = _source(open(path).read())
199             except DispatchError, e:
200                 self._source_warnings.append('%s: %s' % (path, e))
201                 continue
202             self._handlers[to_resource(path)] = handlers
203
204
205 # vi:sts=4 sw=4 et