4d3e20f9765834e8565c286f7286cf303593a6e2
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
77 """Close the connection to the HTTP server."""
79 self
.sock
.close() # close it manually... there may be other refs
83 """Send `str' to the server."""
90 # send the data to the server. if we get a broken pipe, then close
91 # the socket. we want to reconnect when somebody tries to send again.
93 # NOTE: we DO propagate the error, though, because we cannot simply
94 # ignore the error... the caller will know if they can retry.
95 if self
.debuglevel
> 0:
96 print "send:", repr(str)
98 yield multitask
.send(self
.sock
, str)
99 except socket
.error
, v
:
100 if v
[0] == 32: # Broken pipe
104 def read(self
, num_bytes
=1024):
105 data
= (yield multitask
.recv(self
.sock
, num_bytes
))
108 class ProxyServerRequestHandler(object):
110 """Simple HTTP request handler with GET and HEAD commands.
112 This serves files from the current directory and any of its
113 subdirectories. The MIME type for files is determined by
114 calling the .guess_type() method.
116 The GET and HEAD requests are identical except that the HEAD
117 request omits the actual contents of the file.
121 server_version
= "SimpleHTTP/" + __version__
123 def on_query(self
, client
, reqtype
, *args
):
124 """Serve a request."""
127 if not hasattr(self
.client
, "proxy"):
128 self
.client
.proxy
= ProxyConnection()
129 self
.client
.proxy
.connect()
131 multitask
.add(self
.proxy_relay(reqtype
))
135 def onPOST(self
, client
, *args
):
136 """Serve a POST request."""
137 return self
.on_query(client
, "POST", *args
)
139 def onGET(self
, client
, *args
):
140 """Serve a GET request."""
141 return self
.on_query(client
, "GET", *args
)
143 def proxy_relay(self
, reqtype
):
145 p
= self
.client
.proxy
148 req
= "%s %s %s\n" % (reqtype
, self
.hr
.path
, self
.hr
.request_version
)
150 yield multitask
.send(p
.sock
, req
)
153 hdrs
= str(self
.hr
.headers
)
155 yield multitask
.send(p
.sock
, hdrs
)
156 yield multitask
.send(p
.sock
, "\n")
159 if self
.hr
.headers
.has_key('content-length'):
160 max_chunk_size
= 10*1024*1024
161 size_remaining
= int(self
.hr
.headers
["content-length"])
163 print "size_remaining", size_remaining
164 while size_remaining
:
165 chunk_size
= min(size_remaining
, max_chunk_size
)
166 data
= self
.hr
.rfile
.read(chunk_size
)
167 yield multitask
.send(p
.sock
, data
)
168 size_remaining
-= len(L
[-1])
170 # now read response and write back
174 data
= (yield multitask
.recv(p
.sock
, 1024))
175 print "reading from proxy", repr(data
)
181 requestline
= f
.readline()
182 yield self
.client
.writeMessage(requestline
)
184 # Examine the headers and look for a Connection directive
185 respheaders
= mimetools
.Message(f
, 0)
186 print "response headers", str(respheaders
)
187 remote
= self
.client
.remote
188 rcooks
= httpd
.process_cookies(respheaders
, remote
, "Set-Cookie", False)
189 rcooks
['session'] = self
.hr
.response_cookies
['session'].value
# nooo
190 rcooks
['session']['expires'] = \
191 self
.hr
.response_cookies
['session']['expires']
192 self
.hr
.response_cookies
= rcooks
193 print "rcooks", str(rcooks
)
195 # send all but Set-Cookie headers
196 del respheaders
['Set-Cookie'] # being replaced
197 yield self
.client
.writeMessage(str(respheaders
))
199 # now replacement cookies
200 for k
, v
in rcooks
.items():
202 yield self
.client
.writeMessage(val
+"\r\n")
204 # check connection for "closed" header
205 conntype
= respheaders
.get('Connection', "")
206 if conntype
.lower() == 'close':
207 self
.hr
.close_connection
= 1
208 elif (conntype
.lower() == 'keep-alive' and
209 self
.hr
.protocol_version
>= "HTTP/1.1"):
210 self
.hr
.close_connection
= 0
213 print "writing to client body"
214 yield self
.client
.writeMessage("\r\n")
215 yield self
.client
.writeMessage(f
.read())
216 if self
.hr
.close_connection
:
218 yield self
.client
.connectionClosed()
219 except httpd
.ConnectionClosed
:
220 print 'close_connection done'