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 |