source: publico/il.spdo/trunk/Paste-1.7.5.1-py2.6.egg/paste/proxy.py @ 5327

Última Alteração nesse arquivo desde 5327 foi 5327, incluída por fabianosantos, 8 anos atrás

Import inicial.

File size: 9.9 KB
Linha 
1# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
2# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
3"""
4An application that proxies WSGI requests to a remote server.
5
6TODO:
7
8* Send ``Via`` header?  It's not clear to me this is a Via in the
9  style of a typical proxy.
10
11* Other headers or metadata?  I put in X-Forwarded-For, but that's it.
12
13* Signed data of non-HTTP keys?  This would be for things like
14  REMOTE_USER.
15
16* Something to indicate what the original URL was?  The original host,
17  scheme, and base path.
18
19* Rewriting ``Location`` headers?  mod_proxy does this.
20
21* Rewriting body?  (Probably not on this one -- that can be done with
22  a different middleware that wraps this middleware)
23
24* Example:: 
25   
26    use = egg:Paste#proxy
27    address = http://server3:8680/exist/rest/db/orgs/sch/config/
28    allowed_request_methods = GET
29 
30"""
31
32import httplib
33import urlparse
34import urllib
35
36from paste import httpexceptions
37from paste.util.converters import aslist
38
39# Remove these headers from response (specify lower case header
40# names):
41filtered_headers = (     
42    'transfer-encoding',
43    'connection',
44    'keep-alive',
45    'proxy-authenticate',
46    'proxy-authorization',
47    'te',
48    'trailers',
49    'upgrade',
50)
51
52class Proxy(object):
53
54    def __init__(self, address, allowed_request_methods=(),
55                 suppress_http_headers=()):
56        self.address = address
57        self.parsed = urlparse.urlsplit(address)
58        self.scheme = self.parsed[0].lower()
59        self.host = self.parsed[1]
60        self.path = self.parsed[2]
61        self.allowed_request_methods = [
62            x.lower() for x in allowed_request_methods if x]
63       
64        self.suppress_http_headers = [
65            x.lower() for x in suppress_http_headers if x]
66
67    def __call__(self, environ, start_response):
68        if (self.allowed_request_methods and
69            environ['REQUEST_METHOD'].lower() not in self.allowed_request_methods):
70            return httpexceptions.HTTPBadRequest("Disallowed")(environ, start_response)
71
72        if self.scheme == 'http':
73            ConnClass = httplib.HTTPConnection
74        elif self.scheme == 'https':
75            ConnClass = httplib.HTTPSConnection
76        else:
77            raise ValueError(
78                "Unknown scheme for %r: %r" % (self.address, self.scheme))
79        conn = ConnClass(self.host)
80        headers = {}
81        for key, value in environ.items():
82            if key.startswith('HTTP_'):
83                key = key[5:].lower().replace('_', '-')
84                if key == 'host' or key in self.suppress_http_headers:
85                    continue
86                headers[key] = value
87        headers['host'] = self.host
88        if 'REMOTE_ADDR' in environ:
89            headers['x-forwarded-for'] = environ['REMOTE_ADDR']
90        if environ.get('CONTENT_TYPE'):
91            headers['content-type'] = environ['CONTENT_TYPE']
92        if environ.get('CONTENT_LENGTH'):
93            if environ['CONTENT_LENGTH'] == '-1':
94                # This is a special case, where the content length is basically undetermined
95                body = environ['wsgi.input'].read(-1)
96                headers['content-length'] = str(len(body))
97            else:
98                headers['content-length'] = environ['CONTENT_LENGTH']
99                length = int(environ['CONTENT_LENGTH'])
100                body = environ['wsgi.input'].read(length)
101        else:
102            body = ''
103           
104        path_info = urllib.quote(environ['PATH_INFO'])
105        if self.path:           
106            request_path = path_info
107            if request_path and request_path[0] == '/':
108                request_path = request_path[1:]
109               
110            path = urlparse.urljoin(self.path, request_path)
111        else:
112            path = path_info
113        if environ.get('QUERY_STRING'):
114            path += '?' + environ['QUERY_STRING']
115           
116        conn.request(environ['REQUEST_METHOD'],
117                     path,
118                     body, headers)
119        res = conn.getresponse()
120        headers_out = parse_headers(res.msg)
121       
122        status = '%s %s' % (res.status, res.reason)
123        start_response(status, headers_out)
124        # @@: Default?
125        length = res.getheader('content-length')
126        if length is not None:
127            body = res.read(int(length))
128        else:
129            body = res.read()
130        conn.close()
131        return [body]
132
133def make_proxy(global_conf, address, allowed_request_methods="",
134               suppress_http_headers=""):
135    """
136    Make a WSGI application that proxies to another address:
137   
138    ``address``
139        the full URL ending with a trailing ``/``
140       
141    ``allowed_request_methods``:
142        a space seperated list of request methods (e.g., ``GET POST``)
143       
144    ``suppress_http_headers``
145        a space seperated list of http headers (lower case, without
146        the leading ``http_``) that should not be passed on to target
147        host
148    """
149    allowed_request_methods = aslist(allowed_request_methods)
150    suppress_http_headers = aslist(suppress_http_headers)
151    return Proxy(
152        address,
153        allowed_request_methods=allowed_request_methods,
154        suppress_http_headers=suppress_http_headers)
155
156
157class TransparentProxy(object):
158
159    """
160    A proxy that sends the request just as it was given, including
161    respecting HTTP_HOST, wsgi.url_scheme, etc.
162
163    This is a way of translating WSGI requests directly to real HTTP
164    requests.  All information goes in the environment; modify it to
165    modify the way the request is made.
166
167    If you specify ``force_host`` (and optionally ``force_scheme``)
168    then HTTP_HOST won't be used to determine where to connect to;
169    instead a specific host will be connected to, but the ``Host``
170    header in the request will remain intact.
171    """
172
173    def __init__(self, force_host=None,
174                 force_scheme='http'):
175        self.force_host = force_host
176        self.force_scheme = force_scheme
177
178    def __repr__(self):
179        return '<%s %s force_host=%r force_scheme=%r>' % (
180            self.__class__.__name__,
181            hex(id(self)),
182            self.force_host, self.force_scheme)
183
184    def __call__(self, environ, start_response):
185        scheme = environ['wsgi.url_scheme']
186        if self.force_host is None:
187            conn_scheme = scheme
188        else:
189            conn_scheme = self.force_scheme
190        if conn_scheme == 'http':
191            ConnClass = httplib.HTTPConnection
192        elif conn_scheme == 'https':
193            ConnClass = httplib.HTTPSConnection
194        else:
195            raise ValueError(
196                "Unknown scheme %r" % scheme)
197        if 'HTTP_HOST' not in environ:
198            raise ValueError(
199                "WSGI environ must contain an HTTP_HOST key")
200        host = environ['HTTP_HOST']
201        if self.force_host is None:
202            conn_host = host
203        else:
204            conn_host = self.force_host
205        conn = ConnClass(conn_host)
206        headers = {}
207        for key, value in environ.items():
208            if key.startswith('HTTP_'):
209                key = key[5:].lower().replace('_', '-')
210                headers[key] = value
211        headers['host'] = host
212        if 'REMOTE_ADDR' in environ and 'HTTP_X_FORWARDED_FOR' not in environ:
213            headers['x-forwarded-for'] = environ['REMOTE_ADDR']
214        if environ.get('CONTENT_TYPE'):
215            headers['content-type'] = environ['CONTENT_TYPE']
216        if environ.get('CONTENT_LENGTH'):
217            length = int(environ['CONTENT_LENGTH'])
218            body = environ['wsgi.input'].read(length)
219            if length == -1:
220                environ['CONTENT_LENGTH'] = str(len(body))
221        elif 'CONTENT_LENGTH' not in environ:
222            body = ''
223            length = 0
224        else:
225            body = ''
226            length = 0
227       
228        path = (environ.get('SCRIPT_NAME', '')
229                + environ.get('PATH_INFO', ''))
230        path = urllib.quote(path)
231        if 'QUERY_STRING' in environ:
232            path += '?' + environ['QUERY_STRING']
233        conn.request(environ['REQUEST_METHOD'],
234                     path, body, headers)
235        res = conn.getresponse()
236        headers_out = parse_headers(res.msg)
237               
238        status = '%s %s' % (res.status, res.reason)
239        start_response(status, headers_out)
240        # @@: Default?
241        length = res.getheader('content-length')
242        if length is not None:
243            body = res.read(int(length))
244        else:
245            body = res.read()
246        conn.close()
247        return [body]
248
249def parse_headers(message):
250    """
251    Turn a Message object into a list of WSGI-style headers.
252    """
253    headers_out = []       
254    for full_header in message.headers:
255        if not full_header:           
256            # Shouldn't happen, but we'll just ignore
257            continue                     
258        if full_header[0].isspace():
259            # Continuation line, add to the last header
260            if not headers_out:                       
261                raise ValueError(
262                    "First header starts with a space (%r)" % full_header)
263            last_header, last_value = headers_out.pop()                   
264            value = last_value + ' ' + full_header.strip()
265            headers_out.append((last_header, value))     
266            continue                               
267        try:       
268            header, value = full_header.split(':', 1)
269        except:                                     
270            raise ValueError("Invalid header: %r" % full_header)
271        value = value.strip()                                   
272        if header.lower() not in filtered_headers:
273            headers_out.append((header, value))   
274    return headers_out
275
276def make_transparent_proxy(
277    global_conf, force_host=None, force_scheme='http'):
278    """
279    Create a proxy that connects to a specific host, but does
280    absolutely no other filtering, including the Host header.
281    """
282    return TransparentProxy(force_host=force_host,
283                            force_scheme=force_scheme)
Note: Veja TracBrowser para ajuda no uso do navegador do trac.
 

The contents and data of this website are published under license:
Creative Commons 4.0 Brasil - Atribuir Fonte - Compartilhar Igual.