forked from jacksonj/CouchProxy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCouchProxyHandler.py
More file actions
217 lines (188 loc) · 7.89 KB
/
CouchProxyHandler.py
File metadata and controls
217 lines (188 loc) · 7.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
import BaseHTTPServer
class CouchProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""
The HTTP handler for incoming proxy requests
"""
# The list of headers from the CouchDB client which will be
# forwaded to the onward host
FWD_HEADERS = ("Accept", "Accept-Charset", "Accept-Encoding",
"Content-Type", "User-Agent", "Content-Length",
"X-Couch-Full-Commit", "Cookie", "Set-Cookie")
def send_response(self, code, message=None):
"""Send the response header and log the response code.
Also send two standard headers with the server software
version and the current date.
"""
if message is None:
if code in self.responses:
message = self.responses[code][0]
else:
message = ''
if self.request_version != 'HTTP/0.9':
self.wfile.write("%s %d %s\r\n" %
(self.protocol_version, code, message))
# print (self.protocol_version, code, message)
self.send_header('Server', self.version_string())
self.send_header('Date', self.date_time_string())
def log_message(self, format, *args):
"""
Logs a message, appending useful info"
"""
self.logger.log_info(self.address_string(), format, *args)
def log_debug(self, format, *args):
"""
Logs a message, appending useful info"
"""
self.logger.log_debug(self.address_string(), format, *args)
def get_request_headers(self):
"""
Parses the request headers and returns a dictionary
of all to be forwarded to the remote host
"""
ret_headers = {}
for k in CouchProxyHandler.FWD_HEADERS:
v = self.headers.get(k, None)
if v:
ret_headers[k] = v
return ret_headers
def get_response_headers(self, response):
"""
Parses the request headers and returns a dictionary
of all the to be forwaded back to the calling client.
Strips off the affinity session SetCookie request, if
present
"""
ret_headers = {}
for h in response:
# Ignore httplib2 stuff
if h not in ('fromcache', 'version', 'status',
'reason', 'previous', 'content-location'):
# Do not pass back the cmsweb front-end cookie
if h == 'set-cookie':
if response[h].find("cms-node=") < 0:
# This Set-Cookie header can be passed back
ret_headers[h] = response[h]
else:
# Return this header
ret_headers[h] = response[h]
return ret_headers
def add_cookie(self, headers, cookie):
"""
Adds a cookie to the request headers
"""
newCookies = []
# Remove the cookie if it already exists in the request
# All others will be forwaded
k = cookie.split('=')
if headers.has_key('Cookie'):
curCookies = headers['Cookie'].strip().split(';')
curCookies = [C.strip() for C in curCookies]
for c in curCookies:
k2 = c.split('=')
if k2[0] != k[0]:
newCookies.append(c)
# Create the new cookie header
newCookies.append(cookie)
headers['Cookie'] = "; ".join(newCookies)
def generic_request(self, method):
"""
All methods should be treated the same...
"""
try:
# Read the request
host, port = self.client_address
content_length = int(self.headers.getheader("Content-Length", 0))
fwdHeaders = self.get_request_headers()
body = self.rfile.read(content_length)
# Debug logging
self.log_debug(" Request headers:")
for k in self.headers:
self.log_debug(" %s: %s", k, self.headers[k])
self.log_debug(" Forwarded headers:")
for k in fwdHeaders:
self.log_debug(" %s: %s", k, fwdHeaders[k])
# Get affinity header if required
affinity = self.server.affinity.get_session(host, self)
if affinity:
self.add_cookie(fwdHeaders, affinity)
# Forward on the request
self.path = self.path.replace('"', '%22')
self.path = self.path.replace('%2F', '/') # may get fixed in couch 1.1
result, response = self.client.makeRequest(self.path, method, fwdHeaders, body)
# Start an affinity session if required
self.server.affinity.start_session(host, response, self)
# Return the result
self.send_response(response.status)
# Send / log headers
# TODO: Strip affinity cookie SetCookie header
self.log_debug(" Response headers:")
retHeaders = self.get_response_headers(response)
# Handle a chunked response - the client has unfolded this
# so we need to add a content-length header
if retHeaders.has_key('transfer-encoding') and retHeaders['transfer-encoding'] == 'chunked':
del retHeaders['transfer-encoding']
retHeaders['content-length'] = len(result)
# Send all headers
for k in retHeaders:
self.send_header(k, retHeaders[k])
self.log_debug(" %s: %s", k, retHeaders[k])
self.end_headers()
# Write the response data
self.wfile.write(result)
# All done!
self.log_request(response.status, len(result))
except:
self.send_response(500)
self.end_headers()
message = "Error handling request"
self.wfile.write(message)
self.log_request(500, len(message))
def do_PUT(self):
self.log_request()
self.generic_request('PUT')
def do_GET(self):
self.log_request()
self.generic_request('GET')
def do_POST(self):
self.log_request()
# POST can be asking for a new affinity session to start...
if self.path == "/ProxyAffinity/Session":
try:
# Queue the session
host, port = self.client_address
self.server.affinity.queue_session(host, self)
self.send_response(200)
self.end_headers()
self.log_request(200, 0)
except:
self.send_response(500)
self.end_headers()
message = "Error starting affinity session"
self.wfile.write(message)
senf.log_request(500, len(message))
else:
# Just a normal POST request
self.generic_request('POST')
def do_DELETE(self):
self.log_request()
# DELETE can be asking for an affinity session to end...
if self.path == "/ProxyAffinity/Session":
try:
# Remove the session
host, port = self.client_address
self.server.affinity.end_session(host, self)
self.send_response(200)
self.end_headers()
self.log_request(200, 0)
except:
self.send_response(500)
self.end_headers()
message = "Error ending affinity session"
self.wfile.write(message)
senf.log_request(500, len(message))
else:
# Just a normal DELETE request
self.generic_request('DELETE')
def do_HEAD(self):
self.log_request()
self.generic_request('HEAD')