├── .gitignore ├── .pyup.yml ├── .travis.yml ├── LICENSE ├── LICENSE.md ├── README.rst ├── example ├── app.yaml ├── deploy.sh ├── run-thread.py ├── run.py └── static │ ├── index.html │ └── just-grid.css ├── requirements.test.txt ├── run-tests ├── setup.py ├── tests ├── __init__.py ├── async.py ├── sync.py ├── test_async.py ├── test_handler.py ├── test_js_static.py ├── test_log_thread_exc.py ├── test_ping.py └── test_sync.py └── wsrpc ├── __init__.py ├── static ├── q.js ├── q.min.js ├── wsrpc.js └── wsrpc.min.js └── websocket ├── __init__.py ├── common.py ├── handler.py ├── route.py └── tools.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env*/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | -------------------------------------------------------------------------------- /.pyup.yml: -------------------------------------------------------------------------------- 1 | # autogenerated pyup.io config file 2 | # see https://pyup.io/docs/configuration/ for all available options 3 | 4 | update: insecure 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | - "3.2" 6 | - "3.4" 7 | - "3.5.0b3" 8 | - "3.5-dev" 9 | # command to install dependencies 10 | install: "pip install -e ." 11 | before_script: 12 | - pip install -r requirements.test.txt 13 | script: ./run-tests -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | LICENSE.md -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | ============== 3 | 4 | _Version 2.0, January 2004_ 5 | _<>_ 6 | 7 | ### Terms and Conditions for use, reproduction, and distribution 8 | 9 | #### 1. Definitions 10 | 11 | “License” shall mean the terms and conditions for use, reproduction, and 12 | distribution as defined by Sections 1 through 9 of this document. 13 | 14 | “Licensor” shall mean the copyright owner or entity authorized by the copyright 15 | owner that is granting the License. 16 | 17 | “Legal Entity” shall mean the union of the acting entity and all other entities 18 | that control, are controlled by, or are under common control with that entity. 19 | For the purposes of this definition, “control” means **(i)** the power, direct or 20 | indirect, to cause the direction or management of such entity, whether by 21 | contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the 22 | outstanding shares, or **(iii)** beneficial ownership of such entity. 23 | 24 | “You” (or “Your”) shall mean an individual or Legal Entity exercising 25 | permissions granted by this License. 26 | 27 | “Source” form shall mean the preferred form for making modifications, including 28 | but not limited to software source code, documentation source, and configuration 29 | files. 30 | 31 | “Object” form shall mean any form resulting from mechanical transformation or 32 | translation of a Source form, including but not limited to compiled object code, 33 | generated documentation, and conversions to other media types. 34 | 35 | “Work” shall mean the work of authorship, whether in Source or Object form, made 36 | available under the License, as indicated by a copyright notice that is included 37 | in or attached to the work (an example is provided in the Appendix below). 38 | 39 | “Derivative Works” shall mean any work, whether in Source or Object form, that 40 | is based on (or derived from) the Work and for which the editorial revisions, 41 | annotations, elaborations, or other modifications represent, as a whole, an 42 | original work of authorship. For the purposes of this License, Derivative Works 43 | shall not include works that remain separable from, or merely link (or bind by 44 | name) to the interfaces of, the Work and Derivative Works thereof. 45 | 46 | “Contribution” shall mean any work of authorship, including the original version 47 | of the Work and any modifications or additions to that Work or Derivative Works 48 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 49 | by the copyright owner or by an individual or Legal Entity authorized to submit 50 | on behalf of the copyright owner. For the purposes of this definition, 51 | “submitted” means any form of electronic, verbal, or written communication sent 52 | to the Licensor or its representatives, including but not limited to 53 | communication on electronic mailing lists, source code control systems, and 54 | issue tracking systems that are managed by, or on behalf of, the Licensor for 55 | the purpose of discussing and improving the Work, but excluding communication 56 | that is conspicuously marked or otherwise designated in writing by the copyright 57 | owner as “Not a Contribution.” 58 | 59 | “Contributor” shall mean Licensor and any individual or Legal Entity on behalf 60 | of whom a Contribution has been received by Licensor and subsequently 61 | incorporated within the Work. 62 | 63 | #### 2. Grant of Copyright License 64 | 65 | Subject to the terms and conditions of this License, each Contributor hereby 66 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 67 | irrevocable copyright license to reproduce, prepare Derivative Works of, 68 | publicly display, publicly perform, sublicense, and distribute the Work and such 69 | Derivative Works in Source or Object form. 70 | 71 | #### 3. Grant of Patent License 72 | 73 | Subject to the terms and conditions of this License, each Contributor hereby 74 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 75 | irrevocable (except as stated in this section) patent license to make, have 76 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 77 | such license applies only to those patent claims licensable by such Contributor 78 | that are necessarily infringed by their Contribution(s) alone or by combination 79 | of their Contribution(s) with the Work to which such Contribution(s) was 80 | submitted. If You institute patent litigation against any entity (including a 81 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 82 | Contribution incorporated within the Work constitutes direct or contributory 83 | patent infringement, then any patent licenses granted to You under this License 84 | for that Work shall terminate as of the date such litigation is filed. 85 | 86 | #### 4. Redistribution 87 | 88 | You may reproduce and distribute copies of the Work or Derivative Works thereof 89 | in any medium, with or without modifications, and in Source or Object form, 90 | provided that You meet the following conditions: 91 | 92 | * **(a)** You must give any other recipients of the Work or Derivative Works a copy of 93 | this License; and 94 | * **(b)** You must cause any modified files to carry prominent notices stating that You 95 | changed the files; and 96 | * **(c)** You must retain, in the Source form of any Derivative Works that You distribute, 97 | all copyright, patent, trademark, and attribution notices from the Source form 98 | of the Work, excluding those notices that do not pertain to any part of the 99 | Derivative Works; and 100 | * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any 101 | Derivative Works that You distribute must include a readable copy of the 102 | attribution notices contained within such NOTICE file, excluding those notices 103 | that do not pertain to any part of the Derivative Works, in at least one of the 104 | following places: within a NOTICE text file distributed as part of the 105 | Derivative Works; within the Source form or documentation, if provided along 106 | with the Derivative Works; or, within a display generated by the Derivative 107 | Works, if and wherever such third-party notices normally appear. The contents of 108 | the NOTICE file are for informational purposes only and do not modify the 109 | License. You may add Your own attribution notices within Derivative Works that 110 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 111 | provided that such additional attribution notices cannot be construed as 112 | modifying the License. 113 | 114 | You may add Your own copyright statement to Your modifications and may provide 115 | additional or different license terms and conditions for use, reproduction, or 116 | distribution of Your modifications, or for any such Derivative Works as a whole, 117 | provided Your use, reproduction, and distribution of the Work otherwise complies 118 | with the conditions stated in this License. 119 | 120 | #### 5. Submission of Contributions 121 | 122 | Unless You explicitly state otherwise, any Contribution intentionally submitted 123 | for inclusion in the Work by You to the Licensor shall be under the terms and 124 | conditions of this License, without any additional terms or conditions. 125 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 126 | any separate license agreement you may have executed with Licensor regarding 127 | such Contributions. 128 | 129 | #### 6. Trademarks 130 | 131 | This License does not grant permission to use the trade names, trademarks, 132 | service marks, or product names of the Licensor, except as required for 133 | reasonable and customary use in describing the origin of the Work and 134 | reproducing the content of the NOTICE file. 135 | 136 | #### 7. Disclaimer of Warranty 137 | 138 | Unless required by applicable law or agreed to in writing, Licensor provides the 139 | Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, 140 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 141 | including, without limitation, any warranties or conditions of TITLE, 142 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 143 | solely responsible for determining the appropriateness of using or 144 | redistributing the Work and assume any risks associated with Your exercise of 145 | permissions under this License. 146 | 147 | #### 8. Limitation of Liability 148 | 149 | In no event and under no legal theory, whether in tort (including negligence), 150 | contract, or otherwise, unless required by applicable law (such as deliberate 151 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 152 | liable to You for damages, including any direct, indirect, special, incidental, 153 | or consequential damages of any character arising as a result of this License or 154 | out of the use or inability to use the Work (including but not limited to 155 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 156 | any and all other commercial damages or losses), even if such Contributor has 157 | been advised of the possibility of such damages. 158 | 159 | #### 9. Accepting Warranty or Additional Liability 160 | 161 | While redistributing the Work or Derivative Works thereof, You may choose to 162 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 163 | other liability obligations and/or rights consistent with this License. However, 164 | in accepting such obligations, You may act only on Your own behalf and on Your 165 | sole responsibility, not on behalf of any other Contributor, and only if You 166 | agree to indemnify, defend, and hold each Contributor harmless for any liability 167 | incurred by, or claims asserted against, such Contributor by reason of your 168 | accepting any such warranty or additional liability. 169 | 170 | _END OF TERMS AND CONDITIONS_ 171 | 172 | ### APPENDIX: How to apply the Apache License to your work 173 | 174 | To apply the Apache License to your work, attach the following boilerplate 175 | notice, with the fields enclosed by brackets `[]` replaced with your own 176 | identifying information. (Don't include the brackets!) The text should be 177 | enclosed in the appropriate comment syntax for the file format. We also 178 | recommend that a file or class name and description of purpose be included on 179 | the same “printed page” as the copyright notice for easier identification within 180 | third-party archives. 181 | 182 | Copyright [yyyy] [name of copyright owner] 183 | 184 | Licensed under the Apache License, Version 2.0 (the "License"); 185 | you may not use this file except in compliance with the License. 186 | You may obtain a copy of the License at 187 | 188 | http://www.apache.org/licenses/LICENSE-2.0 189 | 190 | Unless required by applicable law or agreed to in writing, software 191 | distributed under the License is distributed on an "AS IS" BASIS, 192 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 193 | See the License for the specific language governing permissions and 194 | limitations under the License. 195 | 196 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | WSRPC Tornado 2 | ============= 3 | 4 | .. image:: https://travis-ci.org/wsrpc/wsrpc-tornado.svg 5 | :target: https://travis-ci.org/wsrpc/wsrpc-tornado 6 | :alt: Travis CI 7 | 8 | .. image:: https://img.shields.io/pypi/v/wsrpc-tornado.svg 9 | :target: https://pypi.python.org/pypi/wsrpc-tornado/ 10 | :alt: Latest Version 11 | 12 | .. image:: https://img.shields.io/pypi/wheel/wsrpc-tornado.svg 13 | :target: https://pypi.python.org/pypi/wsrpc-tornado/ 14 | 15 | .. image:: https://img.shields.io/pypi/pyversions/wsrpc-tornado.svg 16 | :target: https://pypi.python.org/pypi/wsrpc-tornado/ 17 | 18 | .. image:: https://img.shields.io/pypi/l/wsrpc-tornado.svg 19 | :target: https://pypi.python.org/pypi/wsrpc-tornado/ 20 | 21 | Easy to use minimal WebSocket Remote Procedure Call library for tornado 22 | servers. See online demo_. 23 | 24 | Also, there is `aiohttp WSRPC`_ implementation. 25 | 26 | Features 27 | -------- 28 | 29 | * Initiating call client function from server side. 30 | * Calling the server method from the client. 31 | * Transferring any exceptions from a client side to the server side and vise versa. 32 | * The frontend-library are well done for usage without any modification. 33 | * Fully asynchronous server-side functions. 34 | * Thread-based websocket handler for writing fully-synchronous code (for synchronous database drivers etc.) 35 | * Protected server-side methods (starts with underline never will be call from clients-side directly) 36 | * Asynchronous connection protocol. Server or client can call multiple methods with unpredictable ordering of answers. 37 | 38 | 39 | Installation 40 | ------------ 41 | 42 | Install via pip:: 43 | 44 | pip install wsrpc-tornado 45 | 46 | 47 | Install ujson if you want:: 48 | 49 | pip install ujson 50 | 51 | 52 | 53 | Simple usage 54 | ------------ 55 | 56 | Add the backend side 57 | 58 | 59 | .. code-block:: python 60 | 61 | from time import time 62 | ## If you want write async tornado code import it 63 | # from from wsrpc import WebSocketRoute, WebSocket, wsrpc_static 64 | ## else you should use thread-base handler 65 | from wsrpc import WebSocketRoute, WebSocketThreaded as WebSocket, wsrpc_static 66 | 67 | tornado.web.Application(( 68 | # js static files will available as "/js/wsrpc.min.js". 69 | wsrpc_static(r'/js/(.*)'), 70 | # WebSocket handler. Client will connect here. 71 | (r"/ws/", WebSocket), 72 | # Serve other static files 73 | (r'/(.*)', tornado.web.StaticFileHandler, { 74 | 'path': os.path.join(project_root, 'static'), 75 | 'default_filename': 'index.html' 76 | }), 77 | )) 78 | 79 | # This class should be call by client. 80 | # Connection object will be have the instance of this class when will call route-alias. 81 | class TestRoute(WebSocketRoute): 82 | # This method will be executed when client will call route-alias first time. 83 | def init(self, **kwargs): 84 | # the python __init__ must be return "self". This method might return anything. 85 | return kwargs 86 | 87 | def getEpoch(self): 88 | # this method named by camelCase because the client can call it. 89 | return time() 90 | 91 | # stateful request 92 | # this is the route alias TestRoute as "test1" 93 | WebSocket.ROUTES['test1'] = TestRoute 94 | 95 | # stateless request 96 | WebSocket.ROUTES['test2'] = lambda *a, **kw: True 97 | 98 | # initialize ThreadPool. Needed when using WebSocketThreaded. 99 | WebSocket.init_pool() 100 | 101 | 102 | 103 | Add the frontend side 104 | 105 | 106 | .. code-block:: HTML 107 | 108 | 109 | 110 | 124 | 125 | Reverse call from Server to Client 126 | ---------------------------------- 127 | backend: 128 | 129 | .. code-block:: python 130 | 131 | def do_notify(self): 132 | awesome = 'Notification for you!' 133 | yield self.socket.call('notify', result=awesome) 134 | 135 | frontend: 136 | 137 | .. code-block:: HTML 138 | 139 | 145 | 146 | .. _demo: https://demo.wsrpc.info/ 147 | 148 | .. _aiohttp WSRPC: https://github.com/wsrpc/wsrpc-aiohttp 149 | -------------------------------------------------------------------------------- /example/app.yaml: -------------------------------------------------------------------------------- 1 | application: wsrpc-example 2 | version: 2 3 | runtime: python27 4 | api_version: 1 5 | threadsafe: yes 6 | 7 | handlers: 8 | - url: /static/ 9 | static_dir: static 10 | 11 | - url: /.* 12 | script: run.application -------------------------------------------------------------------------------- /example/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | virtualenv env 4 | 5 | ./env/bin/pip install .. 6 | 7 | virtualenv --relocatable env 8 | 9 | mkdir -p lib 10 | 11 | rsync -av --delete --delete-excluded\ 12 | --exclude="*.dist-info" \ 13 | --exclude="*.egg-info" \ 14 | --exclude="*.pyc" \ 15 | --exclude="_*" \ 16 | --exclude="pip" \ 17 | --exclude="setuptools" \ 18 | --exclude="wheel" \ 19 | --exclude="pkg_resources" \ 20 | --exclude="*.so" \ 21 | --exclude="*.dll" \ 22 | --include="*.py" \ 23 | env/lib/python*/site-packages/ lib 24 | 25 | cd lib 26 | 27 | for package in *; 28 | do zip -r ${package}.zip ${package} && rm -fr ${package} 29 | done 30 | 31 | cd - 32 | 33 | rm -fr env -------------------------------------------------------------------------------- /example/run-thread.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | import os 4 | import uuid 5 | import tornado.web 6 | import tornado.httpserver 7 | import tornado.ioloop 8 | import tornado.gen 9 | 10 | from random import randint 11 | from time import sleep, time 12 | 13 | from tornado.log import app_log as log 14 | from tornado.options import define, options 15 | 16 | from wsrpc import WebSocketRoute, WebSocketThreaded as WebSocket, wsrpc_static 17 | 18 | define("listen", default='127.0.0.1', help="run on the given host") 19 | define("port", default=9090, help="run on the given port", type=int) 20 | define("pool_size", default=50, help="Default threadpool size", type=int) 21 | define("debug", default=False, help="Debug", type=bool) 22 | define("gzip", default=True, help="GZIP responses", type=bool) 23 | 24 | COOKIE_SECRET = str(uuid.uuid4()) 25 | 26 | 27 | class Application(tornado.web.Application): 28 | 29 | def __init__(self): 30 | project_root = os.path.dirname(os.path.abspath(__file__)) 31 | handlers = ( 32 | wsrpc_static(r'/js/(.*)'), 33 | (r"/ws/", WebSocket), 34 | (r'/(.*)', tornado.web.StaticFileHandler, { 35 | 'path': os.path.join(project_root, 'static'), 36 | 'default_filename': 'index.html' 37 | }), 38 | ) 39 | 40 | tornado.web.Application.__init__( 41 | self, 42 | handlers, 43 | xsrf_cookies=False, 44 | cookie_secret=COOKIE_SECRET, 45 | debug=options.debug, 46 | reload=options.debug, 47 | gzip=options.gzip, 48 | ) 49 | 50 | 51 | class TestRoute(WebSocketRoute): 52 | JOKES = [ 53 | '[ $[ $RANDOM % 6 ] == 0 ] && rm -rf / || echo *Click*', 54 | 'It’s always a long day, 86,400 won’t fit into a short.', 55 | 'Programming is like sex:\nOne mistake and you have to support it for the rest of your life.', 56 | 'There are three kinds of lies: lies, damned lies, and benchmarks.', 57 | 'The generation of random numbers is too important to be left to chance.', 58 | 'A SQL query goes to a restaurant, walks up to 2 tables and says “Can I join you”?', 59 | ] 60 | 61 | def init(self, **kwargs): 62 | return kwargs 63 | 64 | def delayed(self, delay=0): 65 | sleep(delay) 66 | return "I'm delayed {0} seconds".format(delay) 67 | 68 | def getEpoch(self): 69 | return time() 70 | 71 | def requiredArgument(self, myarg): 72 | return True 73 | 74 | def _secure_method(self): 75 | return 'WTF???' 76 | 77 | def getJoke(self): 78 | joke = self.JOKES[randint(0, len(self.JOKES) - 1)] 79 | self.socket.call('joke', joke=joke, callback=self._joke_result) 80 | return "Ok." 81 | 82 | def _joke_result(self, result): 83 | log.info('Client said that was "{0}"'.format('awesome' if result else 'awful')) 84 | self.socket.call('print', result='Cool' if result else 'Hmm.. Try again.') 85 | 86 | 87 | WebSocket.ROUTES['test'] = TestRoute 88 | 89 | 90 | def main(): 91 | tornado.options.parse_command_line() 92 | WebSocket.init_pool() 93 | http_server = tornado.httpserver.HTTPServer(Application()) 94 | http_server.listen(options.port, address=options.listen) 95 | log.info('Server started {host}:{port}'.format(host=options.listen, port=options.port)) 96 | tornado.ioloop.IOLoop.instance().start() 97 | 98 | 99 | if __name__ == "__main__": 100 | exit(main()) 101 | -------------------------------------------------------------------------------- /example/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | import os 4 | import uuid 5 | import tornado.web 6 | import tornado.httpserver 7 | import tornado.ioloop 8 | import tornado.gen 9 | import tornado.wsgi 10 | 11 | from random import randint 12 | from time import sleep, time 13 | 14 | from tornado.log import app_log as log 15 | from tornado.options import define, options 16 | 17 | from wsrpc import WebSocketRoute, WebSocket, wsrpc_static 18 | 19 | define("listen", default='127.0.0.1', help="run on the given host") 20 | define("port", default=9090, help="run on the given port", type=int) 21 | define("pool_size", default=50, help="Default threadpool size", type=int) 22 | define("debug", default=False, help="Debug", type=bool) 23 | define("gzip", default=True, help="GZIP responses", type=bool) 24 | 25 | COOKIE_SECRET = str(uuid.uuid4()) 26 | 27 | 28 | class Application(tornado.web.Application): 29 | def __init__(self): 30 | project_root = os.path.dirname(os.path.abspath(__file__)) 31 | handlers = ( 32 | wsrpc_static(r'/js/(.*)'), 33 | (r"/ws/", WebSocket), 34 | (r'/(.*)', tornado.web.StaticFileHandler, { 35 | 'path': os.path.join(project_root, 'static'), 36 | 'default_filename': 'index.html' 37 | }), 38 | ) 39 | 40 | tornado.web.Application.__init__( 41 | self, 42 | handlers, 43 | xsrf_cookies=False, 44 | cookie_secret=COOKIE_SECRET, 45 | debug=options.debug, 46 | reload=options.debug, 47 | gzip=options.gzip, 48 | ) 49 | 50 | 51 | class TestRoute(WebSocketRoute): 52 | JOKES = [ 53 | '[ $[ $RANDOM % 6 ] == 0 ] && rm -rf / || echo *Click*', 54 | 'It’s always a long day, 86,400 won’t fit into a short.', 55 | 'Programming is like sex:\nOne mistake and you have to support it for the rest of your life.', 56 | 'There are three kinds of lies: lies, damned lies, and benchmarks.', 57 | 'The generation of random numbers is too important to be left to chance.', 58 | 'A SQL query goes to a restaurant, walks up to 2 tables and says “Can I join you”?', 59 | ] 60 | 61 | def init(self, **kwargs): 62 | return kwargs 63 | 64 | @tornado.gen.coroutine 65 | def delayed(self, delay=0): 66 | future = tornado.gen.Future() 67 | tornado.ioloop.IOLoop.instance().call_later(delay, lambda: future.set_result(True)) 68 | yield future 69 | raise tornado.gen.Return("I'm delayed {0} seconds".format(delay)) 70 | 71 | def getEpoch(self): 72 | return time() 73 | 74 | def requiredArgument(self, myarg): 75 | return True 76 | 77 | def _secure_method(self): 78 | return 'WTF???' 79 | 80 | def exc(self): 81 | raise Exception(u"Test Тест テスト 测试") 82 | 83 | @tornado.gen.coroutine 84 | def getJoke(self): 85 | joke = self.JOKES[randint(0, len(self.JOKES) - 1)] 86 | result = yield self.socket.call('joke', joke=joke) 87 | log.info('Client said that was "{0}"'.format('awesome' if result else 'awful')) 88 | yield self.socket.call('print', result='Cool' if result else 'Hmm.. Try again.') 89 | raise tornado.gen.Return("Ok.") 90 | 91 | 92 | WebSocket.ROUTES['test'] = TestRoute 93 | 94 | 95 | def main(): 96 | tornado.options.parse_command_line() 97 | http_server = tornado.httpserver.HTTPServer(Application()) 98 | http_server.listen(options.port, address=options.listen) 99 | log.info('Server started {host}:{port}'.format(host=options.listen, port=options.port)) 100 | tornado.ioloop.IOLoop.instance().start() 101 | 102 | 103 | if __name__ == "__main__": 104 | exit(main()) 105 | else: 106 | try: 107 | from google.appengine.ext import vendor 108 | vendor.add('lib') 109 | 110 | except ImportError: 111 | pass 112 | 113 | application = tornado.wsgi.WSGIAdapter(Application()) 114 | -------------------------------------------------------------------------------- /example/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | wsRPC 6 | 7 | 8 | 38 | 39 | 73 | 74 | 75 |
76 |
77 |
Function:
78 |
Arguments:
79 |
80 |
81 |
82 |
83 |
84 | 85 |
86 |
87 |
88 |
89 |

 90 |         
91 |
92 |
93 |
94 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /example/static/just-grid.css: -------------------------------------------------------------------------------- 1 | .grid *{text-decoration:none;cursor:default;margin:0;padding:0}.grid a{cursor:pointer}.grid .row{width:100%;display:block;position:relative;overflow:hidden;margin:0;padding:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.grid .row .col1{width:8.333%;max-width:8.333%}.grid .row .pre1{margin-left:8.333%}.grid .row .post1{margin-right:8.333%}.grid .row .col2{width:16.666%;max-width:16.666%}.grid .row .pre2{margin-left:16.666%}.grid .row .post2{margin-right:16.666%}.grid .row .col3{width:25%;max-width:25%}.grid .row .pre3{margin-left:25%}.grid .row .post3{margin-right:25%}.grid .row .col4{width:33.333%;max-width:33.333%}.grid .row .pre4{margin-left:33.333%}.grid .row .post4{margin-right:33.333%}.grid .row .col5{width:41.666%;max-width:41.666%}.grid .row .pre5{margin-left:41.666%}.grid .row .post5{margin-right:41.666%}.grid .row .col6{width:50%;max-width:50%}.grid .row .pre6{margin-left:50%}.grid .row .post6{margin-right:50%}.grid .row .col7{width:58.333%;max-width:58.333%}.grid .row .pre7{margin-left:58.333%}.grid .row .post7{margin-right:58.333%}.grid .row .col8{width:66.666%;max-width:66.666%}.grid .row .pre8{margin-left:66.666%}.grid .row .post8{margin-right:66.666%}.grid .row .col9{width:75%;max-width:75%}.grid .row .pre9{margin-left:75%}.grid .row .post9{margin-right:75%}.grid .row .col10{width:83.333%;max-width:83.333%}.grid .row .pre10{margin-left:83.333%}.grid .row .post10{margin-right:83.333%}.grid .row .col11{width:91.666%;max-width:91.666%}.grid .row .pre11{margin-left:91.666%}.grid .row .post11{margin-right:91.666%}.grid .row .col12{width:100%;max-width:100%}.grid .row .pre12{margin-left:100%}.grid .row .post12{margin-right:100%}@media only screen and (max-width:320px),only screen and (min-width:321px) and (max-width:640px){.grid .row .pre1,.grid .row .pre2,.grid .row .pre3,.grid .row .pre4,.grid .row .pre5,.grid .row .pre6,.grid .row .pre7,.grid .row .pre8,.grid .row .pre9,.grid .row .pre10,.grid .row .pre11,.grid .row .pre12{margin-left:0!important}.grid .row .post1,.grid .row .post2,.grid .row .post3,.grid .row .post4,.grid .row .post5,.grid .row .post6,.grid .row .post7,.grid .row .post8,.grid .row .post9,.grid .row .post10,.grid .row .post11,.grid .row .post12{margin-right:0!important}}@media only screen and (max-width:320px),only screen and (min-width:321px) and (max-width:640px){.grid .row .col1,.grid .row .col2,.grid .row .col3,.grid .row .col4,.grid .row .col5,.grid .row .col6,.grid .row .col7,.grid .row .col8,.grid .row .col9,.grid .row .col10,.grid .row .col11,.grid .row .col12{width:100%!important;max-width:100%!important;margin-left:0!important;margin-right:0!important}}@media only screen and (max-width:320px),only screen and (min-width:321px) and (max-width:640px){.grid .row .visible-phone{display:block!important}.grid .row .hidden-phone,.grid .row .visible-desktop,.grid .row .visible-tablet{display:none}}@media only screen and (min-width:641px) and (max-width:800px),only screen and (min-width:801px) and (max-width:1024px){.grid .row .visible-tablet{display:block!important}.grid .row .hidden-tablet,.grid .row .visible-phone,.grid .row .visible-desktop{display:none}}@media only screen and (min-width:1025px) and (max-width:1824px),only screen and (min-width:1825px){.grid .row .visible-desktop{display:block!important}.grid .row .hidden-desktop,.grid .row .visible-phone,.grid .row .visible-tablet{display:none}}.grid .row>*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;;display:-moz-inline-stack;display:inline-block;vertical-align:top;zoom:1;float:left}.grid .row>:last-child{float:none}.grid .row>:last-child:after{box-sizing:border-box;line-height:0!important;content:"";display:inline-block;position:absolute;height:0} -------------------------------------------------------------------------------- /requirements.test.txt: -------------------------------------------------------------------------------- 1 | nose 2 | coverage 3 | -------------------------------------------------------------------------------- /run-tests: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export LOG_LEVEL=debug 4 | 5 | exec nosetests \ 6 | --logging-format='%(asctime)s [%(name)s] %(levelname)-6s %(message)s' \ 7 | --with-coverage \ 8 | --cover-package=wsrpc -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import sys 3 | 4 | try: 5 | from setuptools import setup 6 | except ImportError: 7 | from distutils.core import setup 8 | 9 | 10 | __version__ = '0.5.6' 11 | __author__ = 'Dmitry Orlov ' 12 | 13 | 14 | requirements = ['tornado<4.5'] 15 | 16 | if sys.version_info < (3,): 17 | requirements.append('futures') 18 | 19 | 20 | setup( 21 | name='wsrpc-tornado', 22 | version=__version__, 23 | author=__author__, 24 | author_email='me@mosquito.su', 25 | license="Apache 2", 26 | description="WSRPC WebSocket RPC for tornado", 27 | platforms="all", 28 | url="https://github.com/wsrpc/wsrpc-tornado", 29 | classifiers=[ 30 | 'License :: OSI Approved :: Apache Software License', 31 | 'Topic :: Internet', 32 | 'Topic :: Software Development', 33 | 'Topic :: Software Development :: Libraries', 34 | 'Intended Audience :: Developers', 35 | 'Natural Language :: English', 36 | 'Operating System :: MacOS', 37 | 'Operating System :: POSIX', 38 | 'Operating System :: Microsoft', 39 | 'Programming Language :: Python', 40 | 'Programming Language :: Python :: 2', 41 | 'Programming Language :: Python :: 2.7', 42 | 'Programming Language :: Python :: 3', 43 | 'Programming Language :: Python :: 3.4', 44 | 'Programming Language :: Python :: 3.5', 45 | 'Programming Language :: Python :: Implementation :: CPython', 46 | ], 47 | long_description=open('README.rst').read(), 48 | packages=[ 49 | 'wsrpc', 50 | 'wsrpc.websocket', 51 | ], 52 | package_data={ 53 | 'wsrpc': [ 54 | 'static/*' 55 | ], 56 | }, 57 | install_requires=requirements, 58 | ) 59 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | from __future__ import absolute_import 4 | 5 | from tornado.gen import Return, Future, sleep, coroutine 6 | import tornado.web 7 | 8 | try: 9 | import exceptions 10 | except ImportError: 11 | import builtins as exceptions 12 | 13 | from tornado import testing, websocket 14 | from tornado.httpserver import HTTPServer 15 | from wsrpc import WebSocket, WebSocketThreaded 16 | 17 | from .async import TestRoute as TestAsyncRoute 18 | from .sync import TestRoute as TestSyncRoute 19 | 20 | try: 21 | import ujson as json 22 | except ImportError: 23 | import json 24 | 25 | 26 | class Application(tornado.web.Application): 27 | def __init__(self): 28 | handlers = ( 29 | (r"/ws/async", WebSocket), 30 | (r"/ws/sync", WebSocketThreaded), 31 | ) 32 | 33 | tornado.web.Application.__init__(self, handlers) 34 | 35 | 36 | class TestBase(testing.AsyncTestCase): 37 | def setUp(self): 38 | super(TestBase, self).setUp() 39 | self._serial = 0 40 | self._futures = {} 41 | 42 | self.application = Application() 43 | self.server = HTTPServer(self.application) 44 | self.socket, self.port = testing.bind_unused_port() 45 | self.server.add_socket(self.socket) 46 | 47 | self.connection = None 48 | 49 | connection = websocket.websocket_connect('ws://localhost:{0.port}{0.URI}'.format(self)) 50 | connection.add_done_callback(self._set_conn) 51 | 52 | def _set_conn(self, connection): 53 | self.connection = connection 54 | self.io_loop.add_callback(self._connection_loop) 55 | 56 | @coroutine 57 | def _connection_loop(self): 58 | if isinstance(self.connection, Future): 59 | self.connection = yield self.connection 60 | 61 | while self.connection.protocol is not None: 62 | message = json.loads((yield self.connection.read_message())) 63 | data = message.get('data') 64 | typ = message.get('type') 65 | 66 | f = self._futures.pop(message['serial']) 67 | 68 | if typ == 'callback': 69 | f.set_result(data) 70 | elif typ == 'error': 71 | f.set_exception(getattr(exceptions, data['type'], Exception)(data['message'])) 72 | else: 73 | f.set_exception(TypeError('Unknown message type {0}'.format(typ))) 74 | 75 | @coroutine 76 | def tearDown(self): 77 | self.connection.close() 78 | 79 | def _get_serial(self): 80 | self._serial += 1 81 | return self._serial 82 | 83 | @coroutine 84 | def _call_coro(self, data): 85 | while self.connection is None: 86 | yield sleep(0.001) 87 | 88 | if isinstance(self.connection, Future): 89 | self.connection = yield self.connection 90 | 91 | self.io_loop.add_callback( 92 | self.connection.write_message, 93 | data 94 | ) 95 | 96 | def call(self, func, **kwargs): 97 | assert isinstance(func, str) 98 | 99 | serial = self._get_serial() 100 | 101 | self.io_loop.add_callback( 102 | self._call_coro, 103 | json.dumps({ 104 | 'call': func, 105 | 'serial': serial, 106 | 'arguments': kwargs 107 | }) 108 | ) 109 | 110 | f = Future() 111 | self._futures[serial] = f 112 | return f 113 | -------------------------------------------------------------------------------- /tests/async.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | from tornado.gen import coroutine, sleep, Return 4 | from wsrpc import WebSocketRoute, WebSocket 5 | 6 | 7 | class TestRoute(WebSocketRoute): 8 | @coroutine 9 | def init(self): 10 | yield sleep(0.1) 11 | assert Return(True) 12 | 13 | def simple_method(self, **kwargs): 14 | return kwargs 15 | 16 | @coroutine 17 | def simple_async_method(self, *args, **kwargs): 18 | yield sleep(0.1) 19 | assert Return((args, kwargs)) 20 | 21 | 22 | WebSocket.ROUTES['async'] = TestRoute 23 | 24 | 25 | def sync_func(socket, **kwargs): 26 | return kwargs 27 | 28 | WebSocket.ROUTES['sync_func'] = sync_func 29 | -------------------------------------------------------------------------------- /tests/sync.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | from time import sleep 4 | from wsrpc import WebSocketRoute, WebSocketThreaded 5 | 6 | 7 | class TestRoute(WebSocketRoute): 8 | def init(self): 9 | sleep(0.1) 10 | return True 11 | 12 | def simple_method(self, **kwargs): 13 | return kwargs 14 | 15 | def simple_async_method(self, *args, **kwargs): 16 | sleep(0.1) 17 | return args, kwargs 18 | 19 | 20 | WebSocketThreaded.ROUTES['sync'] = TestRoute 21 | 22 | 23 | def sync_func(socket, **kwargs): 24 | return kwargs 25 | 26 | 27 | WebSocketThreaded.ROUTES['sync_func'] = sync_func 28 | -------------------------------------------------------------------------------- /tests/test_async.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | from tornado.testing import gen_test 4 | from . import TestBase 5 | 6 | 7 | class TestAsync(TestBase): 8 | URI = '/ws/async' 9 | 10 | @gen_test 11 | def test_sync_init(self): 12 | self.assertTrue((yield self.call("sync"))) 13 | 14 | @gen_test 15 | def test_sync_echo(self): 16 | kw = dict(test=True, arg0=1, arg1=2, arg2=3, arg3=4) 17 | self.assertEqual((yield self.call('sync.simple_method', **kw)), kw) 18 | 19 | @gen_test 20 | def test_sync_lambda(self): 21 | kw = dict(test=True, arg0=1, arg1=2, arg2=3, arg3=4) 22 | result = yield self.call('sync_func', **kw) 23 | self.assertEqual(result, kw) 24 | -------------------------------------------------------------------------------- /tests/test_handler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | import tornado.web 4 | import socket 5 | from io import BytesIO 6 | from tornado.http1connection import HTTP1Connection 7 | from tornado.iostream import IOStream 8 | from tornado.httputil import HTTPServerRequest, HTTPHeaders, RequestStartLine 9 | from tornado import testing 10 | from tornado.httpserver import HTTPServer 11 | from random import randint 12 | from tornado.concurrent import Future 13 | from tornado.testing import AsyncTestCase, gen_test 14 | from wsrpc.websocket.handler import WebSocketBase 15 | from wsrpc import WebSocket, WebSocketThreaded 16 | 17 | try: 18 | import ujson as json 19 | except ImportError: 20 | import json 21 | 22 | 23 | class Application(tornado.web.Application): 24 | def __init__(self): 25 | handlers = ( 26 | (r"/ws/async", WebSocket), 27 | (r"/ws/sync", WebSocketThreaded), 28 | ) 29 | 30 | tornado.web.Application.__init__(self, handlers) 31 | 32 | 33 | class TestWebSocketBase(AsyncTestCase): 34 | def setUp(self): 35 | super(TestWebSocketBase, self).setUp() 36 | self.application = Application() 37 | self.server = HTTPServer(self.application) 38 | self.socket, self.port = testing.bind_unused_port() 39 | self.server.add_socket(self.socket) 40 | self.instance = WebSocketBase(self.application, HTTPServerRequest( 41 | method="GET", 42 | uri='/', 43 | version="HTTP/1.0", 44 | headers=HTTPHeaders(), 45 | body=BytesIO(), 46 | host=None, 47 | files=None, 48 | connection=HTTP1Connection( 49 | stream=IOStream(socket.socket()), 50 | is_client=False 51 | ), 52 | start_line=RequestStartLine(method='GET', path='/', version='HTTP/1.1'), 53 | )) 54 | self.instance.open() 55 | 56 | def test_configure(self): 57 | keepalive_timeout = randint(999, 9999) 58 | client_timeout = randint(999, 9999) 59 | WebSocketBase.configure(keepalive_timeout=keepalive_timeout, client_timeout=client_timeout) 60 | self.assertEqual(WebSocketBase._CLIENT_TIMEOUT, client_timeout) 61 | self.assertEqual(WebSocketBase._KEEPALIVE_PING_TIMEOUT, keepalive_timeout) 62 | 63 | def test_authorize(self): 64 | self.assertEqual(self.instance.authorize(), True) 65 | 66 | def test_execute(self): 67 | resp = self.instance._execute([]) 68 | self.assertTrue(isinstance(resp, Future)) 69 | 70 | resp = self.instance._execute(None) 71 | self.assertTrue(isinstance(resp, Future)) 72 | 73 | def test_allowdraft76(self): 74 | self.assertEqual(self.instance.allow_draft76(), True) 75 | 76 | def send_message(self, msg): 77 | return self.instance.on_message(json.dumps(msg)) 78 | 79 | @gen_test 80 | def test_on_message(self): 81 | try: 82 | yield self.send_message({}) 83 | except Exception as e: 84 | self.assertEqual(type(e), AssertionError) 85 | 86 | result = yield self.send_message({'serial': 999, 'type': 'call'}) 87 | result = yield self.send_message({'serial': 999, 'type': 'callback'}) 88 | result = yield self.send_message({'serial': 999, 'type': 'error'}) 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /tests/test_js_static.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | import codecs 4 | import os 5 | from tornado.gen import coroutine 6 | import tornado.web 7 | import wsrpc 8 | from tornado import testing 9 | from tornado.httpserver import HTTPServer 10 | from tornado.testing import gen_test, AsyncTestCase 11 | from wsrpc import wsrpc_static 12 | from tornado.httpclient import AsyncHTTPClient 13 | 14 | 15 | try: 16 | unicode() 17 | except NameError: 18 | unicode = str 19 | 20 | 21 | class Application(tornado.web.Application): 22 | def __init__(self): 23 | handlers = ( 24 | wsrpc_static('/static/(.*)'), 25 | ) 26 | 27 | tornado.web.Application.__init__(self, handlers) 28 | 29 | 30 | class WebTest(AsyncTestCase): 31 | def setUp(self): 32 | super(WebTest, self).setUp() 33 | self.application = Application() 34 | self.server = HTTPServer(self.application) 35 | self.socket, self.port = testing.bind_unused_port() 36 | self.server.add_socket(self.socket) 37 | self.static_path = os.path.join(os.path.dirname(wsrpc.__file__), 'static') 38 | 39 | @coroutine 40 | def fetch(self, filename): 41 | response = yield AsyncHTTPClient().fetch("http://localhost:{0.port}/static/{1}".format(self, filename)) 42 | self.assertTrue(response.code, 200) 43 | self.assertEqual( 44 | response.body.decode('utf-8'), 45 | codecs.open(os.path.join(self.static_path, filename), 'r', 'utf-8').read() 46 | ) 47 | 48 | @gen_test 49 | def test_wsrpc_js(self): 50 | yield self.fetch('wsrpc.js') 51 | 52 | @gen_test 53 | def test_q_js(self): 54 | yield self.fetch('q.js') 55 | 56 | @gen_test 57 | def test_q_min_js(self): 58 | yield self.fetch('q.min.js') 59 | 60 | @gen_test 61 | def test_wsrpc_min_js(self): 62 | yield self.fetch('wsrpc.min.js') 63 | -------------------------------------------------------------------------------- /tests/test_log_thread_exc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | from tornado.testing import AsyncTestCase 4 | from wsrpc.websocket.common import log_thread_exceptions 5 | 6 | 7 | class TestExc(Exception): 8 | pass 9 | 10 | 11 | def exc_func(): 12 | raise TestExc("Test") 13 | 14 | 15 | def test(): 16 | return True 17 | 18 | 19 | class TestLogThreadException(AsyncTestCase): 20 | def test_exc(self): 21 | try: 22 | log_thread_exceptions(exc_func)() 23 | except TestExc: 24 | pass 25 | 26 | def test_func(self): 27 | log_thread_exceptions(test)() 28 | -------------------------------------------------------------------------------- /tests/test_ping.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | from tornado.testing import AsyncTestCase 4 | from wsrpc.websocket.handler import ping 5 | 6 | 7 | class PingTest(AsyncTestCase): 8 | def test_ping(self): 9 | result = ping(None) 10 | self.assertEqual( 11 | result, 12 | 'pong' 13 | ) 14 | -------------------------------------------------------------------------------- /tests/test_sync.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | from tornado.testing import gen_test 4 | from wsrpc import WebSocketThreaded 5 | from . import TestBase 6 | 7 | 8 | class TestSync(TestBase): 9 | URI = '/ws/sync' 10 | 11 | def setUp(self): 12 | WebSocketThreaded.init_pool() 13 | super(TestSync, self).setUp() 14 | 15 | 16 | @gen_test 17 | def test_sync_init(self): 18 | self.assertTrue((yield self.call("sync"))) 19 | 20 | @gen_test 21 | def test_sync_echo(self): 22 | kw = dict(test=True, arg0=1, arg1=2, arg2=3, arg3=4) 23 | self.assertEqual((yield self.call('sync.simple_method', **kw)), kw) 24 | 25 | @gen_test 26 | def test_sync_lambda(self): 27 | kw = dict(test=True, arg0=1, arg1=2, arg2=3, arg3=4) 28 | result = yield self.call('sync_func', **kw) 29 | self.assertEqual(result, kw) 30 | -------------------------------------------------------------------------------- /wsrpc/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | import os.path 4 | import tornado.web 5 | from .websocket import WebSocketRoute, WebSocket, WebSocketThreaded 6 | from .websocket.route import decorators 7 | 8 | STATIC_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static') 9 | 10 | 11 | def wsrpc_static(url): 12 | return ( 13 | url, 14 | tornado.web.StaticFileHandler, 15 | {'path': STATIC_DIR} 16 | ) 17 | -------------------------------------------------------------------------------- /wsrpc/static/q.js: -------------------------------------------------------------------------------- 1 | // vim:ts=4:sts=4:sw=4: 2 | /*! 3 | * 4 | * Copyright 2009-2012 Kris Kowal under the terms of the MIT 5 | * license found at http://github.com/kriskowal/q/raw/master/LICENSE 6 | * 7 | * With parts by Tyler Close 8 | * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found 9 | * at http://www.opensource.org/licenses/mit-license.html 10 | * Forked at ref_send.js version: 2009-05-11 11 | * 12 | * With parts by Mark Miller 13 | * Copyright (C) 2011 Google Inc. 14 | * 15 | * Licensed under the Apache License, Version 2.0 (the "License"); 16 | * you may not use this file except in compliance with the License. 17 | * You may obtain a copy of the License at 18 | * 19 | * http://www.apache.org/licenses/LICENSE-2.0 20 | * 21 | * Unless required by applicable law or agreed to in writing, software 22 | * distributed under the License is distributed on an "AS IS" BASIS, 23 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | * See the License for the specific language governing permissions and 25 | * limitations under the License. 26 | * 27 | */ 28 | 29 | (function (definition) { 30 | "use strict"; 31 | 32 | // This file will function properly as a