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)
156 req
= "%s %s %s\n" % (reqtype
, self
.hr
.path
, "HTTP/1.1")
158 yield p
.ss
.write(req
)
160 conntype
= self
.hr
.headers
.get('Connection', "")
161 keepalive
= conntype
.lower() == 'keep-alive'
163 self
.hr
.headers
['Connection'] = 'keep-alive'
164 self
.hr
.close_connection
= 0
167 hdrs
= str(self
.hr
.headers
)
169 yield p
.ss
.write(hdrs
)
170 yield p
.ss
.write('\r\n')
173 if self
.hr
.headers
.has_key('content-length'):
174 max_chunk_size
= 10*1024*1024
175 size_remaining
= int(self
.hr
.headers
["content-length"])
177 print "size_remaining", size_remaining
178 while size_remaining
:
179 chunk_size
= min(size_remaining
, max_chunk_size
)
180 data
= self
.hr
.rfile
.read(chunk_size
)
181 print "proxy rfile read", repr(data
)
182 yield multitask
.send(p
.sock
, data
)
183 size_remaining
-= len(data
)
185 # now read response and write back
186 # HTTP/1.0 200 OK status line etc.
187 line
= (yield p
.ss
.readline())
188 yield self
.client
.writeMessage(line
)
193 line
= (yield p
.ss
.readline())
194 print "reading from proxy", repr(line
)
196 if line
in ['\n', '\r\n']:
198 except StopIteration:
199 if httpd
._debug
: print "proxy read stopiter"
200 # TODO: close connection
203 print 'proxy read error', \
204 (traceback
and traceback
.print_exc() or None)
205 # TODO: close connection
209 # Examine the headers and look for a Connection directive
210 respheaders
= mimetools
.Message(f
, 0)
211 print "response headers", str(respheaders
)
212 remote
= self
.client
.remote
213 rcooks
= httpd
.process_cookies(respheaders
, remote
, "Set-Cookie", False)
214 rcooks
['session'] = self
.hr
.response_cookies
['session'].value
# nooo
215 rcooks
['session']['expires'] = \
216 self
.hr
.response_cookies
['session']['expires']
217 self
.hr
.response_cookies
= rcooks
218 print "rcooks", str(rcooks
)
220 # send all but Set-Cookie headers
221 del respheaders
['Set-Cookie'] # being replaced
222 yield self
.client
.writeMessage(str(respheaders
))
224 # now replacement cookies
225 for k
, v
in rcooks
.items():
227 yield self
.client
.writeMessage(val
+"\r\n")
229 # check connection for "closed" header
231 conntype
= respheaders
.get('Connection', "")
232 if conntype
.lower() == 'close':
233 self
.hr
.close_connection
= 1
234 elif (conntype
.lower() == 'keep-alive' and
235 self
.hr
.protocol_version
>= "HTTP/1.1"):
236 self
.hr
.close_connection
= 0
239 print "writing to client body"
240 yield self
.client
.writeMessage("\r\n")
242 if respheaders
.has_key('content-length'):
243 max_chunk_size
= 10*1024*1024
244 size_remaining
= int(respheaders
["content-length"])
245 while size_remaining
:
246 chunk_size
= min(size_remaining
, max_chunk_size
)
247 data
= (yield p
.ss
.read(chunk_size
))
248 print "reading from proxy expecting", size_remaining
, repr(data
)
249 yield self
.client
.writeMessage(data
)
250 size_remaining
-= len(data
)
255 data
= (yield p
.ss
.read(1024))
256 except httpd
.ConnectionClosed
:
258 print "reading from proxy", repr(data
)
261 yield self
.client
.writeMessage(data
)
263 if not keepalive
: #self.hr.close_connection:
264 print 'proxy wants client to close_connection'
266 yield self
.client
.connectionClosed()
267 raise httpd
.ConnectionClosed
268 except httpd
.ConnectionClosed
:
269 print 'close_connection done'