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))
161 req
= "%s %s %s\n" % (reqtype
, self
.hr
.path
, "HTTP/1.1")
163 yield p
.ss
.write(req
)
165 conntype
= self
.hr
.headers
.get('Connection', "")
166 keepalive
= conntype
.lower() == 'keep-alive'
168 self
.hr
.headers
['Connection'] = 'keep-alive'
169 self
.hr
.close_connection
= 0
172 hdrs
= str(self
.hr
.headers
)
174 yield p
.ss
.write(hdrs
)
175 yield p
.ss
.write('\r\n')
178 if self
.hr
.headers
.has_key('content-length'):
179 max_chunk_size
= 10*1024*1024
180 size_remaining
= int(self
.hr
.headers
["content-length"])
182 print "size_remaining", size_remaining
183 while size_remaining
:
184 chunk_size
= min(size_remaining
, max_chunk_size
)
185 data
= self
.hr
.rfile
.read(chunk_size
)
186 print "proxy rfile read", repr(data
)
187 yield multitask
.send(p
.sock
, data
)
188 size_remaining
-= len(data
)
190 # now read response and write back
191 # HTTP/1.0 200 OK status line etc.
192 responseline
= (yield p
.ss
.readline())
193 yield self
.client
.writeMessage(responseline
)
198 line
= (yield p
.ss
.readline())
199 print "reading from proxy", repr(line
)
201 if line
in ['\n', '\r\n']:
203 except StopIteration:
204 if httpd
._debug
: print "proxy read stopiter"
205 # TODO: close connection
208 print 'proxy read error', \
209 (traceback
and traceback
.print_exc() or None)
210 # TODO: close connection
214 # Examine the headers and look for a Connection directive
215 respheaders
= mimetools
.Message(f
, 0)
216 print "response headers", str(respheaders
)
217 remote
= self
.client
.remote
218 rcooks
= httpd
.process_cookies(respheaders
, remote
, "Set-Cookie", False)
219 rcooks
['session'] = self
.hr
.response_cookies
['session'].value
# nooo
220 rcooks
['session']['expires'] = \
221 self
.hr
.response_cookies
['session']['expires']
222 self
.hr
.response_cookies
= rcooks
223 print "rcooks", str(rcooks
)
225 # override connection: keep-alive hack
226 #responseline = responseline.split(" ")
227 #print "responseline:", responseline
228 #if responseline[1] != "200":
229 # respheaders['Connection'] = 'close'
231 # send all but Set-Cookie headers
232 del respheaders
['Set-Cookie'] # being replaced
233 yield self
.client
.writeMessage(str(respheaders
))
235 # now replacement cookies
236 for k
, v
in rcooks
.items():
238 yield self
.client
.writeMessage(val
+"\r\n")
240 # check connection for "closed" header
242 conntype
= respheaders
.get('Connection', "")
243 if conntype
.lower() == 'close':
244 self
.hr
.close_connection
= 1
245 elif (conntype
.lower() == 'keep-alive' and
246 self
.hr
.protocol_version
>= "HTTP/1.1"):
247 self
.hr
.close_connection
= 0
250 print "writing to client body"
251 yield self
.client
.writeMessage("\r\n")
253 if respheaders
.has_key('content-length'):
254 max_chunk_size
= 10*1024*1024
255 size_remaining
= int(respheaders
["content-length"])
256 while size_remaining
:
257 chunk_size
= min(size_remaining
, max_chunk_size
)
258 data
= (yield p
.ss
.read(chunk_size
))
259 print "reading from proxy expecting", size_remaining
, repr(data
)
260 yield self
.client
.writeMessage(data
)
261 size_remaining
-= len(data
)
266 data
= (yield p
.ss
.read(1024))
267 except httpd
.ConnectionClosed
:
269 print "reading from proxy", repr(data
)
272 yield self
.client
.writeMessage(data
)
274 if not keepalive
: #self.hr.close_connection:
275 print 'proxy wants client to close_connection'
277 yield self
.client
.connectionClosed()
278 raise httpd
.ConnectionClosed
279 except httpd
.ConnectionClosed
:
280 print 'close_connection done'