cc22f4476f9a165db2988ab0ab91c883281aac14
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 session
= self
.client
.session
131 p
= self
.proxies
.get(session
, None)
133 proxy
= ProxyConnection()
135 self
.proxies
[session
] = proxy
137 multitask
.add(self
.proxy_relay(reqtype
))
141 def onPOST(self
, client
, *args
):
142 """Serve a POST request."""
143 return self
.on_query(client
, "POST", *args
)
145 def onGET(self
, client
, *args
):
146 """Serve a GET request."""
147 return self
.on_query(client
, "GET", *args
)
149 def proxy_relay(self
, reqtype
):
151 session
= self
.client
.session
152 p
= self
.proxies
[session
]
155 req
= "%s %s %s\n" % (reqtype
, self
.hr
.path
, self
.hr
.request_version
)
157 yield p
.ss
.write(req
)
160 hdrs
= str(self
.hr
.headers
)
162 yield p
.ss
.write(hdrs
)
163 yield p
.ss
.write('\r\n')
165 conntype
= self
.hr
.headers
.get('Connection', "")
166 keepalive
= conntype
.lower() == 'keep-alive'
169 if self
.hr
.headers
.has_key('content-length'):
170 max_chunk_size
= 10*1024*1024
171 size_remaining
= int(self
.hr
.headers
["content-length"])
173 print "size_remaining", size_remaining
174 while size_remaining
:
175 chunk_size
= min(size_remaining
, max_chunk_size
)
176 data
= self
.hr
.rfile
.read(chunk_size
)
177 print "proxy rfile read", repr(data
)
178 yield multitask
.send(p
.sock
, data
)
179 size_remaining
-= len(data
)
181 # now read response and write back
182 # HTTP/1.0 200 OK status line etc.
183 line
= (yield p
.ss
.readline())
184 yield self
.client
.writeMessage(line
)
189 line
= (yield p
.ss
.readline())
190 print "reading from proxy", repr(line
)
192 if line
in ['\n', '\r\n']:
194 except StopIteration:
195 if httpd
._debug
: print "proxy read stopiter"
196 # TODO: close connection
199 print 'proxy read error', \
200 (traceback
and traceback
.print_exc() or None)
201 # TODO: close connection
205 # Examine the headers and look for a Connection directive
206 respheaders
= mimetools
.Message(f
, 0)
207 print "response headers", str(respheaders
)
208 remote
= self
.client
.remote
209 rcooks
= httpd
.process_cookies(respheaders
, remote
, "Set-Cookie", False)
210 rcooks
['session'] = self
.hr
.response_cookies
['session'].value
# nooo
211 rcooks
['session']['expires'] = \
212 self
.hr
.response_cookies
['session']['expires']
213 self
.hr
.response_cookies
= rcooks
214 print "rcooks", str(rcooks
)
216 # send all but Set-Cookie headers
217 del respheaders
['Set-Cookie'] # being replaced
218 yield self
.client
.writeMessage(str(respheaders
))
220 # now replacement cookies
221 for k
, v
in rcooks
.items():
223 yield self
.client
.writeMessage(val
+"\r\n")
225 # check connection for "closed" header
227 conntype
= respheaders
.get('Connection', "")
228 if conntype
.lower() == 'close':
229 self
.hr
.close_connection
= 1
230 elif (conntype
.lower() == 'keep-alive' and
231 self
.hr
.protocol_version
>= "HTTP/1.1"):
232 self
.hr
.close_connection
= 0
235 print "writing to client body"
236 yield self
.client
.writeMessage("\r\n")
238 if respheaders
.has_key('content-length'):
239 max_chunk_size
= 10*1024*1024
240 size_remaining
= int(respheaders
["content-length"])
241 while size_remaining
:
242 chunk_size
= min(size_remaining
, max_chunk_size
)
243 data
= (yield p
.ss
.read(chunk_size
))
244 print "reading from proxy expecting", size_remaining
, repr(data
)
245 yield self
.client
.writeMessage(data
)
246 size_remaining
-= len(data
)
251 data
= (yield p
.ss
.read(1024))
252 except httpd
.ConnectionClosed
:
254 print "reading from proxy", repr(data
)
257 yield self
.client
.writeMessage(data
)
261 if self
.hr
.close_connection
:
262 print 'proxy wants client to close_connection'
264 raise httpd
.ConnectionClosed
265 yield self
.client
.connectionClosed()
266 except httpd
.ConnectionClosed
:
267 print 'close_connection done'