875c913c83333c577d4aa8fe5e4b29e72171bb93
3 This module acts as an HTTP Proxy Server. However it adds the
4 multitaskhttpd auto-generated session cookie to the headers.
6 It also changes the connection type to use keep-alive, so that
7 the connection stays open to the server, resulting in an
8 "apparent" persistent connection.
10 Thus, a service on the receiving end of this proxy server may reliably
11 keep persistent state, even though the browsers connecting to it
12 may be doing HTTP 1.0 or have an unreliable internet connection.
19 __all__
= ["SimpleHTTPRequestHandler"]
33 from cStringIO
import StringIO
35 from StringIO
import StringIO
39 class NotConnected(Exception):
42 class ProxyConnection
:
50 self
.host
= "127.0.0.1"
55 """Connect to the host and port specified in __init__."""
56 msg
= "getaddrinfo returns an empty list"
57 for res
in socket
.getaddrinfo(self
.host
, self
.port
, 0,
59 af
, socktype
, proto
, canonname
, sa
= res
61 self
.sock
= socket
.socket(af
, socktype
, proto
)
62 if self
.debuglevel
> 0:
63 print "connect: (%s, %s)" % (self
.host
, self
.port
)
65 except socket
.error
, msg
:
66 if self
.debuglevel
> 0:
67 print 'connect fail:', (self
.host
, self
.port
)
74 raise socket
.error
, msg
76 self
.ss
= httpd
.SockStream(self
.sock
)
79 """Close the connection to the HTTP server."""
81 self
.sock
.close() # close it manually... there may be other refs
85 """Send `str' to the server."""
92 # send the data to the server. if we get a broken pipe, then close
93 # the socket. we want to reconnect when somebody tries to send again.
95 # NOTE: we DO propagate the error, though, because we cannot simply
96 # ignore the error... the caller will know if they can retry.
97 if self
.debuglevel
> 0:
98 print "send:", repr(str)
100 yield multitask
.send(self
.sock
, str)
101 except socket
.error
, v
:
102 if v
[0] == 32: # Broken pipe
106 def read(self
, num_bytes
=1024):
107 data
= (yield multitask
.recv(self
.sock
, num_bytes
))
110 class ProxyServerRequestHandler(object):
112 """Simple HTTP request handler with GET and HEAD commands.
114 This serves files from the current directory and any of its
115 subdirectories. The MIME type for files is determined by
116 calling the .guess_type() method.
118 The GET and HEAD requests are identical except that the HEAD
119 request omits the actual contents of the file.
123 server_version
= "SimpleHTTP/" + __version__
125 def on_query(self
, client
, reqtype
, *args
):
126 """Serve a request."""
129 print "on_query", reqtype
, repr(self
.hr
.headers
), str(self
.hr
.headers
)
130 if not hasattr(self
.client
, "proxy"):
132 self
.client
.proxy
= ProxyConnection()
133 self
.client
.proxy
.connect()
135 multitask
.add(self
.proxy_relay(reqtype
))
139 def onPOST(self
, client
, *args
):
140 """Serve a POST request."""
141 return self
.on_query(client
, "POST", *args
)
143 def onGET(self
, client
, *args
):
144 """Serve a GET request."""
145 return self
.on_query(client
, "GET", *args
)
147 def proxy_relay(self
, reqtype
):
149 p
= self
.client
.proxy
152 req
= "%s %s %s\n" % (reqtype
, self
.hr
.path
, self
.hr
.request_version
)
154 yield p
.ss
.write(req
)
157 hdrs
= str(self
.hr
.headers
)
159 yield p
.ss
.write(hdrs
)
160 yield p
.ss
.write('\r\n')
162 conntype
= self
.hr
.headers
.get('Connection', "")
163 keepalive
= conntype
.lower() == 'keep-alive'
166 if self
.hr
.headers
.has_key('content-length'):
167 max_chunk_size
= 10*1024*1024
168 size_remaining
= int(self
.hr
.headers
["content-length"])
170 print "size_remaining", size_remaining
171 while size_remaining
:
172 chunk_size
= min(size_remaining
, max_chunk_size
)
173 data
= self
.hr
.rfile
.read(chunk_size
)
174 print "proxy rfile read", repr(data
)
175 yield multitask
.send(p
.sock
, data
)
176 size_remaining
-= len(data
)
178 # now read response and write back
179 # HTTP/1.0 200 OK status line etc.
180 line
= (yield p
.ss
.readline())
181 yield self
.client
.writeMessage(line
)
186 line
= (yield p
.ss
.readline())
187 print "reading from proxy", repr(line
)
189 if line
in ['\n', '\r\n']:
191 except StopIteration:
192 if httpd
._debug
: print "proxy read stopiter"
193 # TODO: close connection
196 print 'proxy read error', \
197 (traceback
and traceback
.print_exc() or None)
198 # TODO: close connection
202 # Examine the headers and look for a Connection directive
203 respheaders
= mimetools
.Message(f
, 0)
204 print "response headers", str(respheaders
)
205 remote
= self
.client
.remote
206 rcooks
= httpd
.process_cookies(respheaders
, remote
, "Set-Cookie", False)
207 rcooks
['session'] = self
.hr
.response_cookies
['session'].value
# nooo
208 rcooks
['session']['expires'] = \
209 self
.hr
.response_cookies
['session']['expires']
210 self
.hr
.response_cookies
= rcooks
211 print "rcooks", str(rcooks
)
213 # send all but Set-Cookie headers
214 del respheaders
['Set-Cookie'] # being replaced
215 yield self
.client
.writeMessage(str(respheaders
))
217 # now replacement cookies
218 for k
, v
in rcooks
.items():
220 yield self
.client
.writeMessage(val
+"\r\n")
222 # check connection for "closed" header
224 conntype
= respheaders
.get('Connection', "")
225 if conntype
.lower() == 'close':
226 self
.hr
.close_connection
= 1
227 elif (conntype
.lower() == 'keep-alive' and
228 self
.hr
.protocol_version
>= "HTTP/1.1"):
229 self
.hr
.close_connection
= 0
232 print "writing to client body"
233 yield self
.client
.writeMessage("\r\n")
235 if respheaders
.has_key('content-length'):
236 max_chunk_size
= 10*1024*1024
237 size_remaining
= int(respheaders
["content-length"])
238 while size_remaining
:
239 chunk_size
= min(size_remaining
, max_chunk_size
)
240 data
= (yield p
.ss
.read(chunk_size
))
241 print "reading from proxy expecting", size_remaining
, repr(data
)
242 yield self
.client
.writeMessage(data
)
243 size_remaining
-= len(data
)
248 data
= (yield p
.ss
.read(1024))
249 except httpd
.ConnectionClosed
:
251 print "reading from proxy", repr(data
)
254 yield self
.client
.writeMessage(data
)
258 if self
.hr
.close_connection
:
259 print 'proxy wants client to close_connection'
261 yield self
.client
.connectionClosed()
262 except httpd
.ConnectionClosed
:
263 print 'close_connection done'