3 # Copyright (C) 2012 W. Trevor King <wking@drexel.edu>
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this program. If not, see
17 # <http://www.gnu.org/licenses/>.
19 """Simple testing for SCGI servers.
22 import socket as _socket
25 import urllib.parse as _urllib_parse
26 except ImportError: # Python 2
27 import urlparse as _urllib_parse
34 """Open a socket specified by the command line arguments.
36 This could be extended to support Unix sockets.
38 return _socket.create_connection((args.host, args.port))
40 def netstring(string):
41 """Convert a string into a netstring.
43 >>> netstring('hello world!')
46 return '{}:{},'.format(len(string), string)
48 def header(method, uri, data=None):
49 """Return the content string of an SCGI header."""
51 content_length = len(data)
54 # From RFC 2616, section 5.1.2:
55 # REQUEST_URI is hex-encoded absolute path
56 # From Nginx HttpCoreModule docs:
60 # This variable is equal to the *original* request URI as
61 # received from the client including the args. It cannot be
62 # modified. Look at $uri for the post-rewrite/altered
63 # URI. Does not include host name. Example:
64 # "/foo/bar.php?arg=baz"
66 # This variable is the current request URI, without any
67 # arguments (see $args for those). This variable will reflect
68 # any modifications done so far by internal redirects or the
69 # index module. Note this may be different from $request_uri,
70 # as $request_uri is what was originally sent by the browser
71 # before any such modifications. Does not include the protocol
72 # or host name. Example: /foo/bar.html
73 scheme,netloc,path,params,query,fragment = _urllib_parse.urlparse(uri)
74 # <scheme>://<netloc>/<path>;<params>?<query>#<fragment>
76 request_uri = _urllib_parse.urlunparse(
77 (scheme, netloc, path, params, query, fragment))
80 'CONTENT_LENGTH\x00{}\x00'.format(content_length),
81 'SCGI\x00{}\x00'.format(1),
82 'REQUEST_METHOD\x00{}\x00'.format(method),
83 'REQUEST_URI\x00{}\x00'.format(request_uri),
84 'DOCUMENT_URI\x00{}\x00'.format(document_uri),
86 return ''.join(strings)
89 """Recieve all data from a socket until it closes."""
98 def request(socket, data=None, **kwargs):
99 """Send a request and return the response string."""
100 ns = netstring(header(data=data, **kwargs))
101 if _sys.version_info >= (3, 0): # Python 3
102 ns = ns.encode('ascii')
106 return recvall(socket)
109 if __name__ == '__main__':
113 parser = argparse.ArgumentParser(description=__doc__, version=__version__)
115 '--host', default='localhost', help='hostname')
117 '-p', '--port', default=4000, type=int, help='port number')
119 '-m', '--method', default='GET', help='request method')
121 'uri', help='request URI')
123 args = parser.parse_args()
125 socket = get_socket(args)
128 response = request(socket=socket, method=args.method, uri=args.uri)
132 if _sys.version_info >= (3, 0): # Python 3
133 sys.stdout.buffer.write(response)
135 sys.stdout.write(response)