18a22689e8a27fa7494ee46d7b7d8cacdc482bee
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"):
131 self
.client
.proxy
= ProxyConnection()
132 self
.client
.proxy
.connect()
134 multitask
.add(self
.proxy_relay(reqtype
))
138 def onPOST(self
, client
, *args
):
139 """Serve a POST request."""
140 return self
.on_query(client
, "POST", *args
)
142 def onGET(self
, client
, *args
):
143 """Serve a GET request."""
144 return self
.on_query(client
, "GET", *args
)
146 def proxy_relay(self
, reqtype
):
148 p
= self
.client
.proxy
151 req
= "%s %s %s\n" % (reqtype
, self
.hr
.path
, self
.hr
.request_version
)
153 yield p
.ss
.write(req
)
156 hdrs
= str(self
.hr
.headers
)
158 yield p
.ss
.write(hdrs
)
159 yield p
.ss
.write('\r\n')
161 conntype
= self
.hr
.headers
.get('Connection', "")
162 keepalive
= conntype
.lower() == 'keep-alive'
165 if self
.hr
.headers
.has_key('content-length'):
166 max_chunk_size
= 10*1024*1024
167 size_remaining
= int(self
.hr
.headers
["content-length"])
169 print "size_remaining", size_remaining
170 while size_remaining
:
171 chunk_size
= min(size_remaining
, max_chunk_size
)
172 data
= self
.hr
.rfile
.read(chunk_size
)
173 print "proxy rfile read", repr(data
)
174 yield multitask
.send(p
.sock
, data
)
175 size_remaining
-= len(data
)
177 # now read response and write back
178 # HTTP/1.0 200 OK status line etc.
179 line
= (yield p
.ss
.readline())
180 yield self
.client
.writeMessage(line
)
185 line
= (yield p
.ss
.readline())
186 print "reading from proxy", repr(line
)
188 if line
in ['\n', '\r\n']:
190 except StopIteration:
191 if httpd
._debug
: print "proxy read stopiter"
192 # TODO: close connection
195 print 'proxy read error', \
196 (traceback
and traceback
.print_exc() or None)
197 # TODO: close connection
201 # Examine the headers and look for a Connection directive
202 respheaders
= mimetools
.Message(f
, 0)
203 print "response headers", str(respheaders
)
204 remote
= self
.client
.remote
205 rcooks
= httpd
.process_cookies(respheaders
, remote
, "Set-Cookie", False)
206 rcooks
['session'] = self
.hr
.response_cookies
['session'].value
# nooo
207 rcooks
['session']['expires'] = \
208 self
.hr
.response_cookies
['session']['expires']
209 self
.hr
.response_cookies
= rcooks
210 print "rcooks", str(rcooks
)
212 # send all but Set-Cookie headers
213 del respheaders
['Set-Cookie'] # being replaced
214 yield self
.client
.writeMessage(str(respheaders
))
216 # now replacement cookies
217 for k
, v
in rcooks
.items():
219 yield self
.client
.writeMessage(val
+"\r\n")
221 # check connection for "closed" header
223 conntype
= respheaders
.get('Connection', "")
224 if conntype
.lower() == 'close':
225 self
.hr
.close_connection
= 1
226 elif (conntype
.lower() == 'keep-alive' and
227 self
.hr
.protocol_version
>= "HTTP/1.1"):
228 self
.hr
.close_connection
= 0
231 print "writing to client body"
232 yield self
.client
.writeMessage("\r\n")
234 if respheaders
.has_key('content-length'):
235 max_chunk_size
= 10*1024*1024
236 size_remaining
= int(respheaders
["content-length"])
237 while size_remaining
:
238 chunk_size
= min(size_remaining
, max_chunk_size
)
239 data
= (yield p
.ss
.read(chunk_size
))
240 print "reading from proxy expecting", size_remaining
, repr(data
)
241 yield self
.client
.writeMessage(data
)
242 size_remaining
-= len(data
)
247 data
= (yield p
.ss
.read(1024))
248 except httpd
.ConnectionClosed
:
250 print "reading from proxy", repr(data
)
253 yield self
.client
.writeMessage(data
)
256 if self
.hr
.close_connection
:
257 print 'proxy wants client to close_connection'
259 yield self
.client
.connectionClosed()
261 except httpd
.ConnectionClosed
:
262 print 'close_connection done'