36e5b178afd8c714a7d677d14888afb162c04f2a
[mw2txt.git] / posts / SCGI / scgi-test.py
1 #!/usr/bin/env python
2
3 # Copyright (C) 2012 W. Trevor King <wking@drexel.edu>
4 #
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.
9 #
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.
14 #
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/>.
18
19 """Simple testing for SCGI servers.
20 """
21
22 import socket as _socket
23 import sys as _sys
24 try:  # Python 3
25     import urllib.parse as _urllib_parse
26 except ImportError:  # Python 2
27     import urlparse as _urllib_parse
28
29
30 __version__ = '0.1'
31
32
33 def get_socket(args):
34     """Open a socket specified by the command line arguments.
35
36     This could be extended to support Unix sockets.
37     """
38     return _socket.create_connection((args.host, args.port))
39
40 def netstring(string):
41     """Convert a string into a netstring.
42
43     >>> netstring('hello world!')
44     '12:hello world!,'
45     """
46     return '{}:{},'.format(len(string), string)
47
48 def header(method, uri, data=None):
49     """Return the content string of an SCGI header."""
50     try:
51         content_length = len(data)
52     except TypeError:
53         content_length = 0
54     # From RFC 2616, section 5.1.2:
55     #   REQUEST_URI is hex-encoded absolute path
56     # From Nginx HttpCoreModule docs:
57     #   $document_uri
58     #     The same as $uri.
59     #   $request_uri
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"
65     #  $uri
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>
75     scheme = netloc = ''
76     request_uri = _urllib_parse.urlunparse(
77         (scheme, netloc, path, params, query, fragment))
78     document_uri = path
79     strings = [
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),
85         ]
86     return ''.join(strings)
87
88 def recvall(socket):
89     """Recieve all data from a socket until it closes."""
90     ret = []
91     while True:
92         r = socket.recv(1024)
93         if not r:
94             break
95         ret.append(r)
96     return b''.join(ret)
97
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')
103     socket.sendall(ns)
104     if data:
105         socket.sendall(data)    
106     return recvall(socket)
107
108
109 if __name__ == '__main__':
110     import argparse
111     import sys
112
113     parser = argparse.ArgumentParser(description=__doc__, version=__version__)
114     parser.add_argument(
115         '--host', default='localhost', help='hostname')
116     parser.add_argument(
117         '-p', '--port', default=4000, type=int, help='port number')
118     parser.add_argument(
119         '-m', '--method', default='GET', help='request method')
120     parser.add_argument(
121         'uri', help='request URI')
122
123     args = parser.parse_args()
124
125     socket = get_socket(args)
126     response = None
127     try:
128         response = request(socket=socket, method=args.method, uri=args.uri)
129     finally:
130         socket.close()
131     if response:
132         if _sys.version_info >= (3, 0):  # Python 3
133             sys.stdout.buffer.write(response)
134         else:
135             sys.stdout.write(response)