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
:
51 self
.host
= "127.0.0.1"
56 """Connect to the host and port specified in __init__."""
57 msg
= "getaddrinfo returns an empty list"
58 for res
in socket
.getaddrinfo(self
.host
, self
.port
, 0,
60 af
, socktype
, proto
, canonname
, sa
= res
62 self
.sock
= socket
.socket(af
, socktype
, proto
)
63 if self
.debuglevel
> 0:
64 print "connect: (%s, %s)" % (self
.host
, self
.port
)
66 except socket
.error
, msg
:
67 if self
.debuglevel
> 0:
68 print 'connect fail:', (self
.host
, self
.port
)
75 raise socket
.error
, msg
77 self
.ss
= httpd
.SockStream(self
.sock
)
80 """Close the connection to the HTTP server."""
82 self
.sock
.close() # close it manually... there may be other refs
86 """Send `str' to the server."""
93 # send the data to the server. if we get a broken pipe, then close
94 # the socket. we want to reconnect when somebody tries to send again.
96 # NOTE: we DO propagate the error, though, because we cannot simply
97 # ignore the error... the caller will know if they can retry.
98 if self
.debuglevel
> 0:
99 print "send:", repr(str)
101 yield multitask
.send(self
.sock
, str)
102 except socket
.error
, v
:
103 if v
[0] == 32: # Broken pipe
107 def read(self
, num_bytes
=1024):
108 data
= (yield multitask
.recv(self
.sock
, num_bytes
))
111 class ProxyServerRequestHandler(object):
113 """Simple HTTP request handler with GET and HEAD commands.
115 This serves files from the current directory and any of its
116 subdirectories. The MIME type for files is determined by
117 calling the .guess_type() method.
119 The GET and HEAD requests are identical except that the HEAD
120 request omits the actual contents of the file.
124 server_version
= "SimpleHTTP/" + __version__
126 def on_query(self
, client
, reqtype
, *args
):
127 """Serve a request."""
131 print "on_query", reqtype
, repr(self
.hr
.headers
), str(self
.hr
.headers
)
132 session
= self
.client
.session
133 p
= self
.proxies
.get(session
, None)
135 proxy
= ProxyConnection()
137 self
.proxies
[session
] = proxy
139 multitask
.add(self
.proxy_relay(reqtype
))
143 def onPOST(self
, client
, *args
):
144 """Serve a POST request."""
145 return self
.on_query(client
, "POST", *args
)
147 def onGET(self
, client
, *args
):
148 """Serve a GET request."""
149 return self
.on_query(client
, "GET", *args
)
151 def proxy_relay(self
, reqtype
):
153 session
= self
.client
.session
154 p
= self
.proxies
[session
]
157 # (yield multitask.sleep(0.01))
162 req
= "%s %s %s\n" % (reqtype
, self
.hr
.path
, "HTTP/1.1")
164 yield p
.ss
.write(req
)
166 conntype
= self
.hr
.headers
.get('Connection', "")
167 keepalive
= conntype
.lower() == 'keep-alive'
169 self
.hr
.headers
['Connection'] = 'keep-alive'
170 self
.hr
.close_connection
= 0
173 hdrs
= str(self
.hr
.headers
)
175 yield p
.ss
.write(hdrs
)
176 yield p
.ss
.write('\r\n')
179 if self
.hr
.headers
.has_key('content-length'):
180 max_chunk_size
= 10*1024*1024
181 size_remaining
= int(self
.hr
.headers
["content-length"])
183 print "size_remaining", size_remaining
184 while size_remaining
:
185 chunk_size
= min(size_remaining
, max_chunk_size
)
186 data
= self
.hr
.rfile
.read(chunk_size
)
187 print "proxy rfile read", repr(data
)
188 yield multitask
.send(p
.sock
, data
)
189 size_remaining
-= len(data
)
191 # now read response and write back
192 # HTTP/1.0 200 OK status line etc.
193 responseline
= (yield p
.ss
.readline())
194 yield self
.client
.writeMessage(responseline
)
199 line
= (yield p
.ss
.readline())
200 print "reading from proxy", repr(line
)
202 if line
in ['\n', '\r\n']:
204 except StopIteration:
205 if httpd
._debug
: print "proxy read stopiter"
206 # TODO: close connection
209 print 'proxy read error', \
210 (traceback
and traceback
.print_exc() or None)
211 # TODO: close connection
215 # Examine the headers and look for a Connection directive
216 respheaders
= mimetools
.Message(f
, 0)
217 print "response headers", str(respheaders
)
218 remote
= self
.client
.remote
219 rcooks
= httpd
.process_cookies(respheaders
, remote
, "Set-Cookie", False)
220 rcooks
['session'] = self
.hr
.response_cookies
['session'].value
# nooo
221 rcooks
['session']['expires'] = \
222 self
.hr
.response_cookies
['session']['expires']
223 self
.hr
.response_cookies
= rcooks
224 print "rcooks", str(rcooks
)
226 # override connection: keep-alive hack
227 #responseline = responseline.split(" ")
228 #print "responseline:", responseline
229 #if responseline[1] != "200":
230 # respheaders['Connection'] = 'close'
232 # send all but Set-Cookie headers
233 del respheaders
['Set-Cookie'] # being replaced
234 yield self
.client
.writeMessage(str(respheaders
))
236 # now replacement cookies
237 for k
, v
in rcooks
.items():
239 yield self
.client
.writeMessage(val
+"\r\n")
241 # check connection for "closed" header
243 conntype
= respheaders
.get('Connection', "")
244 if conntype
.lower() == 'close':
245 self
.hr
.close_connection
= 1
246 elif (conntype
.lower() == 'keep-alive' and
247 self
.hr
.protocol_version
>= "HTTP/1.1"):
248 self
.hr
.close_connection
= 0
251 print "writing to client body"
252 yield self
.client
.writeMessage("\r\n")
254 if respheaders
.has_key('content-length'):
255 max_chunk_size
= 10*1024*1024
256 size_remaining
= int(respheaders
["content-length"])
257 while size_remaining
:
258 chunk_size
= min(size_remaining
, max_chunk_size
)
259 data
= (yield p
.ss
.read(chunk_size
))
260 print "reading from proxy expecting", size_remaining
, repr(data
)
261 yield self
.client
.writeMessage(data
)
262 size_remaining
-= len(data
)
267 data
= (yield p
.ss
.read(1024))
268 except httpd
.ConnectionClosed
:
270 print "reading from proxy", repr(data
)
273 yield self
.client
.writeMessage(data
)
275 if not keepalive
: #self.hr.close_connection:
276 print 'proxy wants client to close_connection'
278 yield self
.client
.connectionClosed()
279 raise httpd
.ConnectionClosed
280 except httpd
.ConnectionClosed
:
281 print 'close_connection done'
285 except httpd
.ConnectionClosed
:
287 raise httpd
.ConnectionClosed
289 print traceback
.print_exc()