| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # | 2 # |
| 3 # Copyright 2012, Google Inc. | 3 # Copyright 2012, Google Inc. |
| 4 # All rights reserved. | 4 # All rights reserved. |
| 5 # | 5 # |
| 6 # Redistribution and use in source and binary forms, with or without | 6 # Redistribution and use in source and binary forms, with or without |
| 7 # modification, are permitted provided that the following conditions are | 7 # modification, are permitted provided that the following conditions are |
| 8 # met: | 8 # met: |
| 9 # | 9 # |
| 10 # * Redistributions of source code must retain the above copyright | 10 # * Redistributions of source code must retain the above copyright |
| (...skipping 18 matching lines...) Expand all Loading... |
| 29 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 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. | 30 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 31 | 31 |
| 32 | 32 |
| 33 """Standalone WebSocket server. | 33 """Standalone WebSocket server. |
| 34 | 34 |
| 35 Use this file to launch pywebsocket without Apache HTTP Server. | 35 Use this file to launch pywebsocket without Apache HTTP Server. |
| 36 | 36 |
| 37 | 37 |
| 38 BASIC USAGE | 38 BASIC USAGE |
| 39 =========== |
| 39 | 40 |
| 40 Go to the src directory and run | 41 Go to the src directory and run |
| 41 | 42 |
| 42 $ python mod_pywebsocket/standalone.py [-p <ws_port>] | 43 $ python mod_pywebsocket/standalone.py [-p <ws_port>] |
| 43 [-w <websock_handlers>] | 44 [-w <websock_handlers>] |
| 44 [-d <document_root>] | 45 [-d <document_root>] |
| 45 | 46 |
| 46 <ws_port> is the port number to use for ws:// connection. | 47 <ws_port> is the port number to use for ws:// connection. |
| 47 | 48 |
| 48 <document_root> is the path to the root directory of HTML files. | 49 <document_root> is the path to the root directory of HTML files. |
| 49 | 50 |
| 50 <websock_handlers> is the path to the root directory of WebSocket handlers. | 51 <websock_handlers> is the path to the root directory of WebSocket handlers. |
| 51 If not specified, <document_root> will be used. See __init__.py (or | 52 If not specified, <document_root> will be used. See __init__.py (or |
| 52 run $ pydoc mod_pywebsocket) for how to write WebSocket handlers. | 53 run $ pydoc mod_pywebsocket) for how to write WebSocket handlers. |
| 53 | 54 |
| 54 For more detail and other options, run | 55 For more detail and other options, run |
| 55 | 56 |
| 56 $ python mod_pywebsocket/standalone.py --help | 57 $ python mod_pywebsocket/standalone.py --help |
| 57 | 58 |
| 58 or see _build_option_parser method below. | 59 or see _build_option_parser method below. |
| 59 | 60 |
| 60 For trouble shooting, adding "--log_level debug" might help you. | 61 For trouble shooting, adding "--log_level debug" might help you. |
| 61 | 62 |
| 62 | 63 |
| 63 TRY DEMO | 64 TRY DEMO |
| 65 ======== |
| 64 | 66 |
| 65 Go to the src directory and run | 67 Go to the src directory and run standalone.py with -d option to set the |
| 68 document root to the directory containing example HTMLs and handlers like this: |
| 66 | 69 |
| 67 $ python standalone.py -d example | 70 $ cd src |
| 71 $ PYTHONPATH=. python mod_pywebsocket/standalone.py -d example |
| 68 | 72 |
| 69 to launch pywebsocket with the sample handler and html on port 80. Open | 73 to launch pywebsocket with the sample handler and html on port 80. Open |
| 70 http://localhost/console.html, click the connect button, type something into | 74 http://localhost/console.html, click the connect button, type something into |
| 71 the text box next to the send button and click the send button. If everything | 75 the text box next to the send button and click the send button. If everything |
| 72 is working, you'll see the message you typed echoed by the server. | 76 is working, you'll see the message you typed echoed by the server. |
| 73 | 77 |
| 74 | 78 |
| 75 SUPPORTING TLS | 79 USING TLS |
| 80 ========= |
| 76 | 81 |
| 77 To support TLS, run standalone.py with -t, -k, and -c options. | 82 To run the standalone server with TLS support, run it with -t, -k, and -c |
| 83 options. When TLS is enabled, the standalone server accepts only TLS connection. |
| 78 | 84 |
| 79 Note that when ssl module is used and the key/cert location is incorrect, | 85 Note that when ssl module is used and the key/cert location is incorrect, |
| 80 TLS connection silently fails while pyOpenSSL fails on startup. | 86 TLS connection silently fails while pyOpenSSL fails on startup. |
| 81 | 87 |
| 88 Example: |
| 82 | 89 |
| 83 SUPPORTING CLIENT AUTHENTICATION | 90 $ PYTHONPATH=. python mod_pywebsocket/standalone.py \ |
| 91 -d example \ |
| 92 -p 10443 \ |
| 93 -t \ |
| 94 -c ../test/cert/cert.pem \ |
| 95 -k ../test/cert/key.pem \ |
| 84 | 96 |
| 85 To support client authentication with TLS, run standalone.py with -t, -k, -c, | 97 Note that when passing a relative path to -c and -k option, it will be resolved |
| 86 and --tls-client-auth, and --tls-client-ca options. | 98 using the document root directory as the base. |
| 87 | 99 |
| 88 E.g., $./standalone.py -d ../example -p 10443 -t -c ../test/cert/cert.pem -k | 100 |
| 89 ../test/cert/key.pem --tls-client-auth --tls-client-ca=../test/cert/cacert.pem | 101 USING CLIENT AUTHENTICATION |
| 102 =========================== |
| 103 |
| 104 To run the standalone server with TLS client authentication support, run it with |
| 105 --tls-client-auth and --tls-client-ca options in addition to ones required for |
| 106 TLS support. |
| 107 |
| 108 Example: |
| 109 |
| 110 $ PYTHONPATH=. python mod_pywebsocket/standalone.py -d example -p 10443 -t \ |
| 111 -c ../test/cert/cert.pem -k ../test/cert/key.pem \ |
| 112 --tls-client-auth \ |
| 113 --tls-client-ca=../test/cert/cacert.pem |
| 114 |
| 115 Note that when passing a relative path to --tls-client-ca option, it will be |
| 116 resolved using the document root directory as the base. |
| 90 | 117 |
| 91 | 118 |
| 92 CONFIGURATION FILE | 119 CONFIGURATION FILE |
| 120 ================== |
| 93 | 121 |
| 94 You can also write a configuration file and use it by specifying the path to | 122 You can also write a configuration file and use it by specifying the path to |
| 95 the configuration file by --config option. Please write a configuration file | 123 the configuration file by --config option. Please write a configuration file |
| 96 following the documentation of the Python ConfigParser library. Name of each | 124 following the documentation of the Python ConfigParser library. Name of each |
| 97 entry must be the long version argument name. E.g. to set log level to debug, | 125 entry must be the long version argument name. E.g. to set log level to debug, |
| 98 add the following line: | 126 add the following line: |
| 99 | 127 |
| 100 log_level=debug | 128 log_level=debug |
| 101 | 129 |
| 102 For options which doesn't take value, please add some fake value. E.g. for | 130 For options which doesn't take value, please add some fake value. E.g. for |
| 103 --tls option, add the following line: | 131 --tls option, add the following line: |
| 104 | 132 |
| 105 tls=True | 133 tls=True |
| 106 | 134 |
| 107 Note that tls will be enabled even if you write tls=False as the value part is | 135 Note that tls will be enabled even if you write tls=False as the value part is |
| 108 fake. | 136 fake. |
| 109 | 137 |
| 110 When both a command line argument and a configuration file entry are set for | 138 When both a command line argument and a configuration file entry are set for |
| 111 the same configuration item, the command line value will override one in the | 139 the same configuration item, the command line value will override one in the |
| 112 configuration file. | 140 configuration file. |
| 113 | 141 |
| 114 | 142 |
| 115 THREADING | 143 THREADING |
| 144 ========= |
| 116 | 145 |
| 117 This server is derived from SocketServer.ThreadingMixIn. Hence a thread is | 146 This server is derived from SocketServer.ThreadingMixIn. Hence a thread is |
| 118 used for each request. | 147 used for each request. |
| 119 | 148 |
| 120 | 149 |
| 121 SECURITY WARNING | 150 SECURITY WARNING |
| 151 ================ |
| 122 | 152 |
| 123 This uses CGIHTTPServer and CGIHTTPServer is not secure. | 153 This uses CGIHTTPServer and CGIHTTPServer is not secure. |
| 124 It may execute arbitrary Python code or external programs. It should not be | 154 It may execute arbitrary Python code or external programs. It should not be |
| 125 used outside a firewall. | 155 used outside a firewall. |
| 126 """ | 156 """ |
| 127 | 157 |
| 128 import BaseHTTPServer | 158 import BaseHTTPServer |
| 129 import CGIHTTPServer | 159 import CGIHTTPServer |
| 130 import SimpleHTTPServer | 160 import SimpleHTTPServer |
| 131 import SocketServer | 161 import SocketServer |
| (...skipping 10 matching lines...) Expand all Loading... |
| 142 import sys | 172 import sys |
| 143 import threading | 173 import threading |
| 144 import time | 174 import time |
| 145 | 175 |
| 146 from mod_pywebsocket import common | 176 from mod_pywebsocket import common |
| 147 from mod_pywebsocket import dispatch | 177 from mod_pywebsocket import dispatch |
| 148 from mod_pywebsocket import handshake | 178 from mod_pywebsocket import handshake |
| 149 from mod_pywebsocket import http_header_util | 179 from mod_pywebsocket import http_header_util |
| 150 from mod_pywebsocket import memorizingfile | 180 from mod_pywebsocket import memorizingfile |
| 151 from mod_pywebsocket import util | 181 from mod_pywebsocket import util |
| 182 from mod_pywebsocket.xhr_benchmark_handler import XHRBenchmarkHandler |
| 152 | 183 |
| 153 | 184 |
| 154 _DEFAULT_LOG_MAX_BYTES = 1024 * 256 | 185 _DEFAULT_LOG_MAX_BYTES = 1024 * 256 |
| 155 _DEFAULT_LOG_BACKUP_COUNT = 5 | 186 _DEFAULT_LOG_BACKUP_COUNT = 5 |
| 156 | 187 |
| 157 _DEFAULT_REQUEST_QUEUE_SIZE = 128 | 188 _DEFAULT_REQUEST_QUEUE_SIZE = 128 |
| 158 | 189 |
| 159 # 1024 is practically large enough to contain WebSocket handshake lines. | 190 # 1024 is practically large enough to contain WebSocket handshake lines. |
| 160 _MAX_MEMORIZED_LINES = 1024 | 191 _MAX_MEMORIZED_LINES = 1024 |
| 161 | 192 |
| (...skipping 493 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 655 # Overrides CGIHTTPServerRequestHandler.cgi_directories. | 686 # Overrides CGIHTTPServerRequestHandler.cgi_directories. |
| 656 self.cgi_directories = self._options.cgi_directories | 687 self.cgi_directories = self._options.cgi_directories |
| 657 # Replace CGIHTTPRequestHandler.is_executable method. | 688 # Replace CGIHTTPRequestHandler.is_executable method. |
| 658 if self._options.is_executable_method is not None: | 689 if self._options.is_executable_method is not None: |
| 659 self.is_executable = self._options.is_executable_method | 690 self.is_executable = self._options.is_executable_method |
| 660 | 691 |
| 661 # This actually calls BaseRequestHandler.__init__. | 692 # This actually calls BaseRequestHandler.__init__. |
| 662 CGIHTTPServer.CGIHTTPRequestHandler.__init__( | 693 CGIHTTPServer.CGIHTTPRequestHandler.__init__( |
| 663 self, request, client_address, server) | 694 self, request, client_address, server) |
| 664 | 695 |
| 665 def _xhr_send_benchmark_helper(self): | |
| 666 content_length = int(self.headers.getheader('Content-Length')) | |
| 667 | |
| 668 self._logger.debug('Requested to receive %s bytes', content_length) | |
| 669 | |
| 670 RECEIVE_BLOCK_SIZE = 1024 * 1024 | |
| 671 | |
| 672 bytes_to_receive = content_length | |
| 673 while bytes_to_receive > 0: | |
| 674 bytes_to_receive_in_this_loop = bytes_to_receive | |
| 675 if bytes_to_receive_in_this_loop > RECEIVE_BLOCK_SIZE: | |
| 676 bytes_to_receive_in_this_loop = RECEIVE_BLOCK_SIZE | |
| 677 received_data = self.rfile.read(bytes_to_receive_in_this_loop) | |
| 678 for c in received_data: | |
| 679 if c != 'a': | |
| 680 self._logger.debug('Request body verification failed') | |
| 681 return | |
| 682 bytes_to_receive -= len(received_data) | |
| 683 if bytes_to_receive < 0: | |
| 684 self._logger.debug('Received %d more bytes than expected' % | |
| 685 (-bytes_to_receive)) | |
| 686 return | |
| 687 | |
| 688 # Return the number of received bytes back to the client. | |
| 689 response_body = '%d' % content_length | |
| 690 self.wfile.write( | |
| 691 'HTTP/1.1 200 OK\r\n' | |
| 692 'Content-Type: text/html\r\n' | |
| 693 'Content-Length: %d\r\n' | |
| 694 '\r\n%s' % (len(response_body), response_body)) | |
| 695 self.wfile.flush() | |
| 696 | |
| 697 def _xhr_receive_benchmark_helper(self): | |
| 698 content_length = self.headers.getheader('Content-Length') | |
| 699 request_body = self.rfile.read(int(content_length)) | |
| 700 | |
| 701 request_array = request_body.split(' ') | |
| 702 if len(request_array) < 2: | |
| 703 self._logger.debug('Malformed request body: %r', request_body) | |
| 704 return | |
| 705 | |
| 706 # Parse the size parameter. | |
| 707 bytes_to_send = request_array[0] | |
| 708 try: | |
| 709 bytes_to_send = int(bytes_to_send) | |
| 710 except ValueError, e: | |
| 711 self._logger.debug('Malformed size parameter: %r', bytes_to_send) | |
| 712 return | |
| 713 self._logger.debug('Requested to send %s bytes', bytes_to_send) | |
| 714 | |
| 715 # Parse the transfer encoding parameter. | |
| 716 chunked_mode = False | |
| 717 mode_parameter = request_array[1] | |
| 718 if mode_parameter == 'chunked': | |
| 719 self._logger.debug('Requested chunked transfer encoding') | |
| 720 chunked_mode = True | |
| 721 elif mode_parameter != 'none': | |
| 722 self._logger.debug('Invalid mode parameter: %r', mode_parameter) | |
| 723 return | |
| 724 | |
| 725 # Write a header | |
| 726 response_header = ( | |
| 727 'HTTP/1.1 200 OK\r\n' | |
| 728 'Content-Type: application/octet-stream\r\n') | |
| 729 if chunked_mode: | |
| 730 response_header += 'Transfer-Encoding: chunked\r\n\r\n' | |
| 731 else: | |
| 732 response_header += ( | |
| 733 'Content-Length: %d\r\n\r\n' % bytes_to_send) | |
| 734 self.wfile.write(response_header) | |
| 735 self.wfile.flush() | |
| 736 | |
| 737 # Write a body | |
| 738 SEND_BLOCK_SIZE = 1024 * 1024 | |
| 739 | |
| 740 while bytes_to_send > 0: | |
| 741 bytes_to_send_in_this_loop = bytes_to_send | |
| 742 if bytes_to_send_in_this_loop > SEND_BLOCK_SIZE: | |
| 743 bytes_to_send_in_this_loop = SEND_BLOCK_SIZE | |
| 744 | |
| 745 if chunked_mode: | |
| 746 self.wfile.write('%x\r\n' % bytes_to_send_in_this_loop) | |
| 747 self.wfile.write('a' * bytes_to_send_in_this_loop) | |
| 748 if chunked_mode: | |
| 749 self.wfile.write('\r\n') | |
| 750 self.wfile.flush() | |
| 751 | |
| 752 bytes_to_send -= bytes_to_send_in_this_loop | |
| 753 | |
| 754 if chunked_mode: | |
| 755 self.wfile.write('0\r\n\r\n') | |
| 756 self.wfile.flush() | |
| 757 | |
| 758 def parse_request(self): | 696 def parse_request(self): |
| 759 """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request. | 697 """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request. |
| 760 | 698 |
| 761 Return True to continue processing for HTTP(S), False otherwise. | 699 Return True to continue processing for HTTP(S), False otherwise. |
| 762 | 700 |
| 763 See BaseHTTPRequestHandler.handle_one_request method which calls | 701 See BaseHTTPRequestHandler.handle_one_request method which calls |
| 764 this method to understand how the return value will be handled. | 702 this method to understand how the return value will be handled. |
| 765 """ | 703 """ |
| 766 | 704 |
| 767 # We hook parse_request method, but also call the original | 705 # We hook parse_request method, but also call the original |
| (...skipping 16 matching lines...) Expand all Loading... |
| 784 'Basic realm="Pywebsocket"') | 722 'Basic realm="Pywebsocket"') |
| 785 self.end_headers() | 723 self.end_headers() |
| 786 self._logger.info('Request basic authentication') | 724 self._logger.info('Request basic authentication') |
| 787 return True | 725 return True |
| 788 | 726 |
| 789 host, port, resource = http_header_util.parse_uri(self.path) | 727 host, port, resource = http_header_util.parse_uri(self.path) |
| 790 | 728 |
| 791 # Special paths for XMLHttpRequest benchmark | 729 # Special paths for XMLHttpRequest benchmark |
| 792 xhr_benchmark_helper_prefix = '/073be001e10950692ccbf3a2ad21c245' | 730 xhr_benchmark_helper_prefix = '/073be001e10950692ccbf3a2ad21c245' |
| 793 if resource == (xhr_benchmark_helper_prefix + '_send'): | 731 if resource == (xhr_benchmark_helper_prefix + '_send'): |
| 794 self._xhr_send_benchmark_helper() | 732 xhr_benchmark_handler = XHRBenchmarkHandler( |
| 733 self.headers, self.rfile, self.wfile) |
| 734 xhr_benchmark_handler.do_send() |
| 795 return False | 735 return False |
| 796 if resource == (xhr_benchmark_helper_prefix + '_receive'): | 736 if resource == (xhr_benchmark_helper_prefix + '_receive'): |
| 797 self._xhr_receive_benchmark_helper() | 737 xhr_benchmark_handler = XHRBenchmarkHandler( |
| 738 self.headers, self.rfile, self.wfile) |
| 739 xhr_benchmark_handler.do_receive() |
| 798 return False | 740 return False |
| 799 | 741 |
| 800 if resource is None: | 742 if resource is None: |
| 801 self._logger.info('Invalid URI: %r', self.path) | 743 self._logger.info('Invalid URI: %r', self.path) |
| 802 self._logger.info('Fallback to CGIHTTPRequestHandler') | 744 self._logger.info('Fallback to CGIHTTPRequestHandler') |
| 803 return True | 745 return True |
| 804 server_options = self.server.websocket_server_options | 746 server_options = self.server.websocket_server_options |
| 805 if host is not None: | 747 if host is not None: |
| 806 validation_host = server_options.validation_host | 748 validation_host = server_options.validation_host |
| 807 if validation_host is not None and host != validation_host: | 749 if validation_host is not None and host != validation_host: |
| (...skipping 426 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1234 logging.critical('mod_pywebsocket: %s' % e) | 1176 logging.critical('mod_pywebsocket: %s' % e) |
| 1235 logging.critical('mod_pywebsocket: %s' % util.get_stack_trace()) | 1177 logging.critical('mod_pywebsocket: %s' % util.get_stack_trace()) |
| 1236 sys.exit(1) | 1178 sys.exit(1) |
| 1237 | 1179 |
| 1238 | 1180 |
| 1239 if __name__ == '__main__': | 1181 if __name__ == '__main__': |
| 1240 _main(sys.argv[1:]) | 1182 _main(sys.argv[1:]) |
| 1241 | 1183 |
| 1242 | 1184 |
| 1243 # vi:sts=4 sw=4 et | 1185 # vi:sts=4 sw=4 et |
| OLD | NEW |