Commits

Anonymous committed 860f02c

add basic support for proxy

  • Participants
  • Parent commits da67e8d

Comments (0)

Files changed (7)

-# -*- coding: utf-8 -
-#
-# Copyright (c) 2008,2009 Benoit Chesneau <benoitc@e-engura.com> 
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
+-*- coding: utf-8 -
+
+Copyright (c) 2008,2009 Benoit Chesneau <benoitc@e-engura.com> 
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
-- remove httplib2 dependancy and replace it by a tiny http client allowing streaming & such (also less bugged)
-- add support for different auth schema (oauth, basic auth come to my mind first)
+- add support for different auth schema (oauth, digest come to my mind first)

File restkit/httpc.py

 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 #
+# 
+# ProxiedHttpClient code from Google GData Python client under Apache License 2
+# Copyright (C) 2006-2009 Google Inc.
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
 
 import base64
 import copy
 import httplib
+import os
 import re
 import socket
 import StringIO
         return path + "?" + uri.query
     return path
 
+
 class Auth(object):
     """ Interface for Auth classes """
-    def __init__(self, credentials, headers=None, **kwargs):
+    def __init__(self, credentials, **kwargs):
         self.credentials = credentials
-        self.headers = headers or {}
         
     def depth(self, uri):
         return uri.path.count("/")
         http client depending on hostname or uri"""
         return True
         
-    def request(self, url, method, body, headers):
+    def request(self, uri, method, body, headers):
         pass
         
     def response(self, response, content):
 
 class BasicAuth(Auth):
     """ basic authentification """
-    def request(self, url, method, body, headers):
+    def request(self, uri, method, body, headers):
         headers['authorization'] = 'Basic ' + base64.b64encode("%s:%s" % self.credentials).strip()
         
     def add_credentials(self, username, password=None):
         password = password or ""
         self.credentials = (username, password)
+        
 
+#TODO : manage authentification detection
 class HttpClient(object):
     MAX_REDIRECTIONS = 5
     
         response = self._make_request(uri, method, body, headers)
             
         if auth and auth.response(response, body):
-            auth.request(method, uri, headers, body)
-            response = self._make_request(conn, uri, method, body, headers)
-            
+            auth.request(uri, method, headers, body)
+            response = self._make_request(uri, method, body, headers)
+
         if self.follow_redirect:
             if nb_redirections < self.MAX_REDIRECTIONS: 
                 if response.status in [301, 302, 307]:
                         response = self._request(url_parser(new_url), method, body, 
                             headers, nb_redirections + 1)
                         self.final_url = new_url
-                elif response.status == 303: # only get request on this status
+                elif response.status == 303: 
+                    # only 'GET' is possible with this status
+                    # according the rfc
                     new_url = response.getheader('location')
                     if not new_uri.netloc: # we got a relative url
                         absolute_uri = "%s://%s" % (uri.scheme, uri.netloc)
                 return resp, ResponseStream(response, stream_size)
             return resp, response.read()
         
-        
+class ProxiedHttpClient(HttpClient):
+    """ HTTP Client with simple proxy management """
+
+    def _get_connection(self, uri, headers=None):
+        headers = headers or {}
+        proxy = None
+        if uri.scheme == 'https':
+            proxy = os.environ.get('https_proxy')
+        elif uri.scheme == 'http':
+            proxy = os.environ.get('http_proxy')
+
+        if not proxy:
+            return HttpClient._get_connection(self, uri, headers=headers)
+
+        proxy_auth = _get_proxy_auth()
+        if uri.scheme == 'https':
+            if proxy_auth:
+                proxy_auth = 'Proxy-authorization: %s' % proxy_auth
+            port = uri.port
+            if not port:
+                port = 443
+            proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (uri.hostname, port)
+            user_agent = 'User-Agent: %s\r\n' % (headers.get('User-Agent', restkit.USER_AGENT))
+            proxy_pieces = '%s%s%s\r\n' % (proxy_connect, proxy_auth, user_agent)
+            proxy_uri = url_parser(proxy)
+            if not proxy_uri.port:
+                proxy_uri.port = '80'
+            # Connect to the proxy server, very simple recv and error checking
+            p_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
+            p_sock.connect((proxy_uri.host, int(proxy_uri.port)))
+            p_sock.sendall(proxy_pieces)
+            response = ''
+            # Wait for the full response.
+            while response.find("\r\n\r\n") == -1:
+                response += p_sock.recv(8192)
+            p_status = response.split()[1]
+            if p_status != str(200):
+                raise ProxyError('Error status=%s' % str(p_status))
+            # Trivial setup for ssl socket.
+            ssl = socket.ssl(p_sock, None, None)
+            fake_sock = httplib.FakeSocket(p_sock, ssl)
+            # Initalize httplib and replace with the proxy socket.
+            connection = httplib.HTTPConnection(proxy_uri.host)
+            connection.sock=fake_sock
+            return connection
+        else:
+            proxy_uri = url_parser(proxy)
+            if not proxy_uri.port:
+                proxy_uri.port = '80'
+            if proxy_auth:
+                headers['Proxy-Authorization'] = proxy_auth.strip()
+            return httplib.HTTPConnection(proxy_uri.hostname, proxy_uri.port)
+        return None
+            
 def _decompress_content(resp, response, stream=False, stream_size=16384):
     try:
         encoding = resp.get('content-encoding', None)
         if binarydata == '': break
         connection.send(binarydata)
         
+def _get_proxy_auth():
+  import base64
+  proxy_username = os.environ.get('proxy-username')
+  if not proxy_username:
+    proxy_username = os.environ.get('proxy_username')
+  proxy_password = os.environ.get('proxy-password')
+  if not proxy_password:
+    proxy_password = os.environ.get('proxy_password')
+  if proxy_username:
+    user_auth = base64.b64encode('%s:%s' % (proxy_username,
+                                            proxy_password))
+    return 'Basic %s\r\n' % (user_auth.strip())
+  else:
+    return ''
+        
 class ResponseStream(object):
     
     def __init__(self, response, amnt=16384):
     def __repr__(self):
         return "<%s status %s for %s>" % (self.__class__.__name__,
                                           self.status,
-                                          self.final_url)
+                                          self.final_url)

File restkit/rest.py

     chardet = False
 
 from restkit.errors import *
-from restkit.httpc import HttpClient, ResponseStream
+from restkit.httpc import ProxiedHttpClient, ResponseStream
 from restkit.utils import to_bytestring
 
 __all__ = ['Resource', 'RestClient', 'url_quote', 'url_encode']
         """ 
 
         if transport is None:
-            transport = HttpClient()
+            transport = ProxiedHttpClient()
 
         self.transport = transport
 
 
 setup(
     name = 'restkit',
-    version = '1.0',
+    version = '0.8',
     description = 'Python REST kit',
     long_description = \
 """An HTTP resource kit for Python""",

File tests/httpc_test.py

 
         self.assert_(self.res.response.status == 200 )
 
-    def testAuth(self):
+    def testBasicAuth(self):
         httptransport = httpc.HttpClient()
         httptransport.add_authorization(httpc.BasicAuth(("test", "test")))
         

File tests/resource_test.py

 
         self.assertRaises(RequestError, bad_post)
 
-    def testAuth(self):
+    def testBasicAuth(self):
         transport = httpc.HttpClient()
         transport.add_authorization(httpc.BasicAuth(("test", "test")))