1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 import urllib
19 import socket
20 import sys
21 from types import ListType, TupleType
22
23
24 from rhn import connections
25 from rhn.SSL import TimeoutException
26 from rhn.SmartIO import SmartIO
27
28
29 from rhn.UserDictCase import UserDictCase
30 from spacewalk.common.rhnTB import Traceback
31 from spacewalk.common.rhnConfig import CFG
32 from spacewalk.common.rhnException import rhnFault, rhnException
33 from spacewalk.common.rhnLog import log_debug, log_error
34 from spacewalk.common import rhnFlags, rhnLib, apache
35 from spacewalk.common.rhnTranslate import _
36
37
38 import rhnConstants
39 from responseContext import ResponseContext
43
44 """ Shared handler class (between rhnBroker and rhnRedirect.
45 *** only inherited ***
46 """
47
48
50 """ init with http request object """
51
52
53
54
55
56 self.req = req
57
58
59 if 'wsgi.input' in self.req.headers_in:
60 smartFd = SmartIO(max_mem_size=CFG.MAX_MEM_FILE_SIZE)
61 smartFd.write(self.req.headers_in['wsgi.input'].read())
62 self.req.headers_in['wsgi.input'] = smartFd
63
64 self.responseContext = ResponseContext()
65 self.uri = None
66
67
68
69 self.caChain = CFG.CA_CHAIN
70 self.httpProxy = CFG.HTTP_PROXY
71 self.httpProxyUsername = CFG.HTTP_PROXY_USERNAME
72 self.httpProxyPassword = CFG.HTTP_PROXY_PASSWORD
73 if not self.httpProxyUsername:
74 self.httpProxyPassword = ''
75 self.rhnParent = CFG.RHN_PARENT or ''
76 self.rhnParent = rhnLib.parseUrl(self.rhnParent)[1].split(':')[0]
77 CFG.set('RHN_PARENT', self.rhnParent)
78
79
80
81
82
83
84
85
86
87
88
89
90
108
110 """ Handler part 1
111 Should not return an error code -- simply connects.
112 """
113
114 scheme, host, port, self.uri = self._parse_url(self.rhnParent)
115 self.responseContext.setConnection(self._create_connection())
116
117 if not self.uri:
118 self.uri = '/'
119
120 log_debug(3, 'Scheme:', scheme)
121 log_debug(3, 'Host:', host)
122 log_debug(3, 'Port:', port)
123 log_debug(3, 'URI:', self.uri)
124 log_debug(3, 'HTTP proxy:', self.httpProxy)
125 log_debug(3, 'HTTP proxy username:', self.httpProxyUsername)
126 log_debug(3, 'HTTP proxy password:', "<password>")
127 log_debug(3, 'CA cert:', self.caChain)
128
129 try:
130 self.responseContext.getConnection().connect()
131 except socket.error, e:
132 log_error("Error opening connection", self.rhnParent, e)
133 Traceback(mail=0)
134 raise rhnFault(1000,
135 _("Spacewalk Proxy could not successfully connect its RHN parent. "
136 "Please contact your system administrator.")), None, sys.exc_info()[2]
137
138
139 log_debug(3, "Connected to parent: %s " % self.rhnParent)
140 if self.httpProxy:
141 if self.httpProxyUsername:
142 log_debug(3, "HTTP proxy info: %s %s/<password>" % (
143 self.httpProxy, self.httpProxyUsername))
144 else:
145 log_debug(3, "HTTP proxy info: %s" % self.httpProxy)
146 else:
147 log_debug(3, "HTTP proxy info: not using an HTTP proxy")
148 peer = self.responseContext.getConnection().sock.getpeername()
149 log_debug(4, "Other connection info: %s:%s%s" %
150 (peer[0], peer[1], self.uri))
151
153 """ Returns a Connection object """
154 scheme, host, port, _uri = self._parse_url(self.rhnParent)
155
156 params = {
157 'host': host,
158 'port': port,
159 }
160 if CFG.has_key('timeout'):
161 params['timeout'] = CFG.TIMEOUT
162 if self.httpProxy:
163 params['proxy'] = self.httpProxy
164 params['username'] = self.httpProxyUsername
165 params['password'] = self.httpProxyPassword
166 if scheme == 'https' and self.caChain:
167 params['trusted_certs'] = [self.caChain, ]
168
169
170 if self.httpProxy:
171 if scheme == 'https':
172 conn_class = connections.HTTPSProxyConnection
173 else:
174 conn_class = connections.HTTPProxyConnection
175 else:
176 if scheme == 'https':
177 conn_class = connections.HTTPSConnection
178 else:
179 conn_class = connections.HTTPConnection
180
181 log_debug(5, "Using connection class", conn_class, 'Params:', params)
182 return conn_class(**params)
183
184 @staticmethod
186 """ Returns scheme, host, port, path. """
187 scheme, netloc, path, _params, _query, _frag = rhnLib.parseUrl(url)
188 host, port = urllib.splitnport(netloc)
189 if (port <= 0):
190 port = None
191 return scheme, host, port, path
192
194 """ Handler part 2
195
196 Server (or next proxy) communication.
197 """
198
199 log_debug(1)
200
201
202
203
204 log_debug(2, self.req.method, self.uri)
205 self.responseContext.getConnection().putrequest(self.req.method,
206 self.uri)
207
208
209 try:
210 status, headers, bodyFd = self._proxy2server()
211 self.responseContext.setHeaders(headers)
212 self.responseContext.setBodyFd(bodyFd)
213 except IOError:
214
215
216
217 Traceback("SharedHandler._serverCommo", self.req, mail=0)
218 raise rhnFault(1000, _(
219 "Spacewalk Proxy error: connection with the Spacewalk server failed")), None, sys.exc_info()[2]
220 except socket.error:
221
222 Traceback("SharedHandler._serverCommo", self.req)
223 raise rhnFault(1000, _(
224 "Spacewalk Proxy error: connection with the Spacewalk server failed")), None, sys.exc_info()[2]
225
226 log_debug(2, "HTTP status code (200 means all is well): %s" % status)
227
228
229
230
231
232 return self._handleServerResponse(status)
233
259
261 if headerObj is None:
262 headerObj = self.responseContext.getHeaders()
263
264 if hasattr(headerObj, 'getheaders'):
265 return headerObj.getheaders(k)
266
267 headers = headerObj.getallmatchingheaders(k)
268 hname = str(k).lower() + ':'
269 hlen = len(hname)
270 ret = []
271 for header in headers:
272 hn = header[:hlen].lower()
273 if hn != hname:
274 log_debug(1, "Invalid header", header)
275 continue
276 ret.append(header[hlen:].strip())
277 return ret
278
297
298
299
300 @staticmethod
302 """ Copy the incoming headers. """
303
304 hdrs = UserDictCase()
305 for k in req.headers_in.keys():
306
307
308 hdrs[k] = req.headers_in[k]
309 return hdrs
310
334
336 hdrs = rhnFlags.get('outputTransportOptions')
337 log_debug(3, hdrs)
338 size = -1
339
340
341 http_connection = self.responseContext.getConnection()
342 for (k, vals) in hdrs.items():
343 if k.lower() in ['content_length', 'content-length']:
344 try:
345 size = int(vals)
346 except ValueError:
347 pass
348 if k.lower() in ['content_length', 'content_type']:
349
350 k = k.replace('_', '-')
351 if not (k.lower()[:2] == 'x-' or
352 k.lower() in [
353 'accept', 'accept-charset', 'accept-encoding', 'accept-language',
354 'accept-ranges', 'age', 'allow', 'authorization', 'cache-control',
355 'connection', 'content-encoding', 'content-language', 'content-length',
356 'content-location', 'content-md5', 'content-range', 'content-type',
357 'date', 'etag', 'expect', 'expires', 'from', 'if-match',
358 'if-modified-since', 'if-none-match', 'if-range', 'if-unmodified-since',
359 'last-modified', 'location', 'max-forwards', 'pragma', 'proxy-authenticate',
360 'proxy-authorization', 'range', 'referer', 'retry-after', 'server',
361 'te', 'trailer', 'transfer-encoding', 'upgrade', 'user-agent', 'vary',
362 'warning', 'www-authenticate']):
363
364 continue
365 if not isinstance(vals, (ListType, TupleType)):
366 vals = [vals]
367 for v in vals:
368 log_debug(5, "Outgoing header", k, v)
369 http_connection.putheader(k, v)
370 http_connection.endheaders()
371
372
373 if size > 0:
374
375 self.req.headers_in['wsgi.input'].seek(0, 0)
376 if sys.version_info < (2, 6):
377 data = self.req.headers_in['wsgi.input'].read(size)
378 else:
379 data = self.req.headers_in['wsgi.input']
380 http_connection.send(data)
381
382
383
384 try:
385 response = http_connection.getresponse()
386 except TimeoutException:
387 log_error("Connection timed out")
388 return apache.HTTP_GATEWAY_TIME_OUT, None, None
389 headers = response.msg
390 status = response.status
391
392
393 bodyFd = response
394 return status, headers, bodyFd
395
401
402 @staticmethod
404 """ This routine attempts to determine the size of an HTTP body by searching
405 the headers for a "Content-Length" field. The size is returned, if
406 found, otherwise -1 is returned.
407 """
408
409
410 size = 0
411 if headers.has_key(rhnConstants.HEADER_CONTENT_LENGTH):
412 try:
413 size = int(headers[rhnConstants.HEADER_CONTENT_LENGTH])
414 except ValueError:
415 size = -1
416 else:
417 size = -1
418
419 return size
420
422 """ This routine will transfer the header contents of an HTTP response to
423 the output headers of an HTTP request for reply to the original
424 requesting client. This function does NOT call the request's
425 send_http_header routine; that is the responsibility of the caller.
426 """
427
428 if fromResponse is None or toRequest is None:
429 return
430
431
432
433
434 for k in fromResponse.msg.keys():
435
436 v = self._get_header(k, fromResponse.msg)
437
438 if (k == 'transfer-encoding') and ('chunked' in v):
439 log_debug(5, "Filtering header", k, v)
440 continue
441
442
443
444 rhnLib.setHeaderValue(toRequest.headers_out, k, v)
445
446 - def _forwardHTTPBody(self, fromResponse, toRequest):
447 """ This routine will transfer the body of an HTTP response to the output
448 area of an HTTP request for response to the original requesting client.
449 The request's send_http_header function must be called before this
450 function is called.
451 """
452 if fromResponse is None or toRequest is None:
453 return
454
455
456
457 size = self._determineHTTPBodySize(fromResponse.msg)
458 log_debug(4, "Response body size: ", size)
459
460
461
462
463 if (size > 0 or size == -1) and (toRequest.method != 'HEAD'):
464 tfile = SmartIO(max_mem_size=CFG.MAX_MEM_FILE_SIZE)
465 buf = fromResponse.read(CFG.BUFFER_SIZE)
466 while buf:
467 try:
468 tfile.write(buf)
469 buf = fromResponse.read(CFG.BUFFER_SIZE)
470 except IOError:
471 buf = 0
472 tfile.seek(0)
473 if 'wsgi.file_wrapper' in toRequest.headers_in:
474 toRequest.output = toRequest.headers_in['wsgi.file_wrapper'](tfile, CFG.BUFFER_SIZE)
475 else:
476 toRequest.output = iter(lambda: tfile.read(CFG.BUFFER_SIZE), '')
477