[Inteproxy-commits] r143 - in trunk: . inteproxy test

scm-commit@wald.intevation.org scm-commit at wald.intevation.org
Thu Jun 12 19:17:43 CEST 2008


Author: bh
Date: 2008-06-12 19:17:43 +0200 (Thu, 12 Jun 2008)
New Revision: 143

Added:
   trunk/inteproxy/httpmessage.py
Modified:
   trunk/ChangeLog
   trunk/inteproxy/feesdialog.py
   trunk/inteproxy/proxycore.py
   trunk/inteproxy/transcoder.py
   trunk/test/test_owsproxy_post_transcoder.py
Log:
* inteproxy/httpmessage.py: New module with abstractions of http
messages.

* inteproxy/transcoder.py (TranscoderRequest): Removed. superseded
by HTTPRequestMessage

* inteproxy/proxycore.py
(InteProxyHTTPRequestHandler.handle_proxy_request): Adapt to http
message objects
(InteProxyHTTPRequestHandler.read_client_request): Return a
HTTPRequestMessage instead of a TranscoderRequest
(InteProxyHTTPRequestHandler.open_http_connection): Return the
response as a HTTPResponseMessage instead of a httplib response
object
(InteProxyHTTPRequestHandler.handle_response): Adapt to the http
message objects.  No longer accepts the response_read parameter

* inteproxy/feesdialog.py (handle_fees_and_access_constraints):
Adapt to the http message objects.  No need to return the read
method anymore.

* test/test_owsproxy_post_transcoder.py
(TestOWSProxyPOSTTranscoder.create_transcoder_request)
(TestOWSProxyPOSTTranscoder.create_request): Rename
create_transcoder_request to create_request and use a
HTTPRequestMessage instead of a TranscoderRequest.  Update callers


Modified: trunk/ChangeLog
===================================================================
--- trunk/ChangeLog	2008-06-12 16:09:45 UTC (rev 142)
+++ trunk/ChangeLog	2008-06-12 17:17:43 UTC (rev 143)
@@ -1,6 +1,35 @@
 2008-06-12  Bernhard Herzog  <bh at intevation.de>
 
+	* inteproxy/httpmessage.py: New module with abstractions of http
+	messages.
+
+	* inteproxy/transcoder.py (TranscoderRequest): Removed. superseded
+	by HTTPRequestMessage
+
 	* inteproxy/proxycore.py
+	(InteProxyHTTPRequestHandler.handle_proxy_request): Adapt to http
+	message objects
+	(InteProxyHTTPRequestHandler.read_client_request): Return a
+	HTTPRequestMessage instead of a TranscoderRequest
+	(InteProxyHTTPRequestHandler.open_http_connection): Return the
+	response as a HTTPResponseMessage instead of a httplib response
+	object
+	(InteProxyHTTPRequestHandler.handle_response): Adapt to the http
+	message objects.  No longer accepts the response_read parameter
+
+	* inteproxy/feesdialog.py (handle_fees_and_access_constraints):
+	Adapt to the http message objects.  No need to return the read
+	method anymore.
+
+	* test/test_owsproxy_post_transcoder.py
+	(TestOWSProxyPOSTTranscoder.create_transcoder_request)
+	(TestOWSProxyPOSTTranscoder.create_request): Rename
+	create_transcoder_request to create_request and use a
+	HTTPRequestMessage instead of a TranscoderRequest.  Update callers
+
+2008-06-12  Bernhard Herzog  <bh at intevation.de>
+
+	* inteproxy/proxycore.py
 	(InteProxyHTTPRequestHandler.open_http_connection): Handle URLs
 	with query and fragment parts correctly
 

Modified: trunk/inteproxy/feesdialog.py
===================================================================
--- trunk/inteproxy/feesdialog.py	2008-06-12 16:09:45 UTC (rev 142)
+++ trunk/inteproxy/feesdialog.py	2008-06-12 17:17:43 UTC (rev 143)
@@ -53,35 +53,23 @@
     then parses the response as XML and extract Fees and
     AccessConstraints information from it and shows them in a dialog.
 
-    The response parameter should be the urllib2 response object for the
-    remote url.  This function has to read the entire response in order
-    to parse it.  A side effect of this is that the response's read
-    method can no longer be used by the caller to read the response.
-    Therefore this function always returns a callable object that
-    behaves like the original response's read method that can be used
-    instead.
+    The response parameter should be the HTTPRequestMessage object with
+    the response from the remote url.
 
     The function keeps track of which URLs were already processed, so
     that the dialog is never shown twice for the same URL.
     """
-    response_read = response.read
-
     # only try to handle anything for successful requests.
     # Should we allow any other success response codes?
     if response.status != 200:
-        return response_read
+        return
 
     if remote_url in dialog_shown_for:
-        return response_read
+        return
 
     if is_get_capabilities_url(remote_url):
-        data = response_read()
-        title, fees, access_constraints = parse_capabilities(data)
+        title, fees, access_constraints = parse_capabilities(response.body)
         inteproxy.main.the_application.run_fees_dialog(title, fees,
                                                        access_constraints)
 
         dialog_shown_for[remote_url] = True
-
-        response_read = StringIO(data).read
-
-    return response_read

Added: trunk/inteproxy/httpmessage.py
===================================================================
--- trunk/inteproxy/httpmessage.py	2008-06-12 16:09:45 UTC (rev 142)
+++ trunk/inteproxy/httpmessage.py	2008-06-12 17:17:43 UTC (rev 143)
@@ -0,0 +1,148 @@
+# Copyright (C) 2008 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Abstractions for http request and response messages"""
+
+from StringIO import StringIO
+
+class HTTPMessage(object):
+
+    """Base class for HTTP Messages in InteProxy.
+
+    Parameters
+      headers -- The headers of the message as a mimetools.Message
+                 instance.
+
+      infile -- File-object like object from which the body of the
+                message can be read.
+
+    The headers object can be accessed as the headers attribute.  The
+    body should be accessed through the body property or the message's
+    read method.  If the body is read via the body property, the body is
+    read into memory from the infile once.  The body can also be set by
+    assignment ot the body attribute or the set_body method.  The latter
+    can also set the content type.
+
+    If the body is read or set, the headers are adjusted a little.  The
+    Content-length is set to the length of the body and any
+    Transfer-encoding headers are removed.
+    """
+
+    def __init__(self, headers, infile):
+        self.headers = headers
+        self.infile = infile
+        self._body = None
+        self._body_stream = None
+
+    def debug_log_message(self, log_function):
+        for header, value in self.headers.items():
+            log_function("header: %s:%r", header, value)
+        if self.body_has_been_read():
+            if self._body:
+                log_function("body: %r", self._body)
+            else:
+                log_function("no body")
+        else:
+            log_function("body may be present but has not been read yet")
+
+    def body_has_been_read(self):
+        return self._body != None
+
+    def set_body(self, body, content_type=None):
+        self.headers["Content-length"] = str(len(body))
+        del self.headers["Transfer-encoding"]
+        if content_type is not None:
+            self.headers["Content-type"] = content_type
+        self._body = body
+
+    def get_body(self):
+        self.read_entire_message()
+        return self._body
+
+    body = property(get_body, set_body)
+
+    def read_entire_message(self):
+        raise NotImplementedError
+
+    def read(self, amount):
+        if self._body_stream is None and self.body_has_been_read():
+            self._body_stream = StringIO(self.body)
+        if self._body_stream is not None:
+            return self._body_stream.read(amount)
+        else:
+            return self.infile.read(amount)
+
+
+class HTTPRequestMessage(HTTPMessage):
+
+    """Represents a HTTP Message for a request.
+
+    Additional parameters:
+       method -- The HTTP method of the request (usually 'GET' or 'POST'()
+
+       uri -- The uri of the request
+
+       version -- The http protocol version given in the request
+
+    All of these parameters are available as attributes of the same
+    name.
+    """
+
+    def __init__(self, method, uri, version, headers, infile):
+        super(HTTPRequestMessage, self).__init__(headers, infile)
+        self.method = method
+        self.uri = uri
+        self.version = version
+
+    def debug_log_message(self, log_function):
+        log_function("HTTPRequestMessage: %s %s %s",
+                     self.method, self.uri, self.version)
+        super(HTTPRequestMessage, self).debug_log_message(log_function)
+
+    def read_entire_message(self):
+        if self.body_has_been_read():
+            return
+        body = None
+        length = int(self.headers.get("Content-Length", "0"))
+        if length:
+            # FIXME: check whether length bytes were read
+            body = self.infile.read(length)
+
+        if body is not None:
+            self.set_body(body)
+
+
+class HTTPResponseMessage(HTTPMessage):
+
+    """Represents a HTTP Message for a response.
+
+    Additional parameters:
+       version -- The http protocol version given in the response
+
+       status -- The HTTP status of the response
+
+       reason -- The reason given in the response
+
+    All of these parameters are available as attributes of the same
+    name.
+    """
+
+    def __init__(self, version, status, reason, headers, infile):
+        super(HTTPResponseMessage, self).__init__(headers, infile)
+        self.version = version
+        self.status = status
+        self.reason = reason
+
+    def debug_log_message(self, log_function):
+        log_function("HTTPResponseMessage: %s %s %s",
+                     self.version, self.status, self.reason)
+        super(HTTPResponseMessage, self).debug_log_message(log_function)
+
+    def read_entire_message(self):
+        if self.body_has_been_read():
+            return
+        self.set_body(self.infile.read())


Property changes on: trunk/inteproxy/httpmessage.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Modified: trunk/inteproxy/proxycore.py
===================================================================
--- trunk/inteproxy/proxycore.py	2008-06-12 16:09:45 UTC (rev 142)
+++ trunk/inteproxy/proxycore.py	2008-06-12 17:17:43 UTC (rev 143)
@@ -20,6 +20,7 @@
 from inteproxy.threadpool import ThreadPool
 from inteproxy.feesdialog import handle_fees_and_access_constraints
 from inteproxy.httpserver import HTTPServer
+from inteproxy.httpmessage import HTTPRequestMessage, HTTPResponseMessage
 from inteproxy.proxyconnection import HTTPSProxyConnection
 
 
@@ -95,7 +96,7 @@
         #
         transcoder.convert_request(client_request)
         self.log_debug("modified client_request:")
-        client_request.debug_log_request(self.log_debug)
+        client_request.debug_log_message(self.log_debug)
 
         #
         # Create and send new request to the real remote host.
@@ -112,31 +113,19 @@
             self.send_error(502)
 
         if response is not None:
-            self.log_debug("received response: %s: %r", response.status,
-                           response.reason)
+            response.debug_log_message(self.log_debug)
             # check for fees and access constraints and run a dialog
-            response_read = handle_fees_and_access_constraints(remote_url,
-                                                               response)
-            self.handle_response(response, response_read)
+            handle_fees_and_access_constraints(remote_url, response)
+            self.handle_response(response)
 
         self.log_debug("request finished")
 
     def read_client_request(self):
         """Read the client request including the body, if any.
         The request is returned as a TranscoderRequest instance."""
-        body = None
-        length = int(self.headers.getheader("Content-Length", "0"))
-        if length:
-            # FIXME: check whether length bytes were read
-            body = self.rfile.read(length)
-            self.log_debug("body of client request (%d bytes):\n%r",
-                           length, body)
-        else:
-            self.log_debug("client request has no body")
+        return HTTPRequestMessage(self.command, self.path, self.request_version,
+                                  self.headers, self.rfile)
 
-        return inteproxy.transcoder.TranscoderRequest(self.command, self.path,
-                                                      self.headers, body)
-
     def open_http_connection(self, remote_url, client_request):
         """Open a HTTP connection to remote_url and send client_request
 
@@ -179,14 +168,33 @@
         connection.endheaders()
 
         if client_request.body is not None:
+            self.log_debug("Sending body to server: %r", client_request.body)
             connection.send(client_request.body)
 
         self.log_debug("request sent")
 
-        return connection.getresponse()
+        response = connection.getresponse()
+        for version_int, version in [(11, "HTTP/1.1"),
+                                     (10, "HTTP/1.0"),
+                                     ( 9, "HTTP/0.9")]:
+            if response.version == version_int:
+                break
+        else:
+            version = "UNKNOWN"
+            self.log_debug("Unknown http version from server: %s",
+                           response.version)
+        if version != self.request_version:
+            self.log_debug("Response version %r does not match"
+                           " request version %r",
+                           version, self.request_version)
+        response_message = HTTPResponseMessage(version, response.status,
+                                               response.reason,
+                                               response.msg, response)
 
+        return response_message
 
-    def handle_response(self, response, response_read):
+
+    def handle_response(self, response):
         # Ideally, the HTTP version in our reply to the client should be
         # what the remote server used in its reply.  Unfortunately,
         # there doesn't seem to be a way to get that information from
@@ -205,13 +213,13 @@
         self.protocol_version = self.request_version
         self.send_response(response.status, response.reason)
 
-        for header, value in response.msg.items():
-            self.log_debug("received header: %s:%r", header, value)
+        for header, value in response.headers.items():
+            self.log_debug("header to client: %s:%r", header, value)
             self.send_header(header, value)
         self.end_headers()
 
-        transfer_encoding = response.msg.get("Transfer-encoding")
-        self.transfer_data(response_read, self.wfile.write,
+        transfer_encoding = response.headers.get("Transfer-encoding")
+        self.transfer_data(response.read, self.wfile.write,
                            chunked = (transfer_encoding == "chunked"))
 
     def transfer_data(self, read, write, length=None, chunked=False):

Modified: trunk/inteproxy/transcoder.py
===================================================================
--- trunk/inteproxy/transcoder.py	2008-06-12 16:09:45 UTC (rev 142)
+++ trunk/inteproxy/transcoder.py	2008-06-12 17:17:43 UTC (rev 143)
@@ -18,50 +18,6 @@
 import getpassword
 
 
-class TranscoderRequest(object):
-
-    """Represents a HTTP request for the transcoders
-
-    Parameters
-      method -- The HTTP method of the request ('GET' or 'POST')
-
-      path   -- The path of the request
-
-      headers -- The headers of the request as a mimetools.Message
-                 instance (the headers attribute of
-                 BaseHTTPRequestHandler instances is of the correct form
-                 already)
-
-      body    -- The body of the request.  Should be either None if the
-                 request has no body or a string.
-
-    All of the parameters are accessible as instance variables with the
-    same name.  The transcoders will change them and the headers in
-    place.
-    """
-
-    def __init__(self, method, path, headers, body):
-        self.method = method
-        self.path = path
-        self.headers = headers
-        self.body = body
-
-    def debug_log_request(self, log_function):
-        log_function("TranscoderRequest for %s %s", self.method, self.path)
-        for header, value in self.headers.items():
-            log_function("header: %s:%r", header, value)
-        if self.body:
-            log_function("body: %r", self.body)
-        else:
-            log_function("no body")
-
-    def set_body(self, body, content_type=None):
-        self.headers["Content-length"] = str(len(body))
-        if content_type is not None:
-            self.headers["Content-type"] = content_type
-        self.body = body
-
-
 class IdentityTranscoder(object):
 
     """A transcoder that does not change anything

Modified: trunk/test/test_owsproxy_post_transcoder.py
===================================================================
--- trunk/test/test_owsproxy_post_transcoder.py	2008-06-12 16:09:45 UTC (rev 142)
+++ trunk/test/test_owsproxy_post_transcoder.py	2008-06-12 17:17:43 UTC (rev 143)
@@ -11,7 +11,8 @@
 import mimetools
 
 import unittest
-from inteproxy.transcoder import OWSProxyPOSTTranscoder, TranscoderRequest
+from inteproxy.transcoder import OWSProxyPOSTTranscoder
+from inteproxy.httpmessage import HTTPRequestMessage
 
 
 class TestOWSProxyPOSTTranscoder(unittest.TestCase):
@@ -47,10 +48,12 @@
         '</wfs:Query>'
         '</wfs:GetFeature>')
 
-    def create_transcoder_request(self, body):
-        return TranscoderRequest("POST", "/foo",
-                                 mimetools.Message(StringIO("\r\n\r\n")),
-                                 body)
+    def create_request(self, body):
+        headers = ["Content-length: %d" % len(body)]
+        headers_object = mimetools.Message(StringIO("\r\n".join(headers)
+                                                    + "\r\n\r\n"))
+        return HTTPRequestMessage("POST", "/foo", "HTTP/1.0", headers_object,
+                                  StringIO(body))
 
     def test_username_password_substitution(self):
 
@@ -70,7 +73,7 @@
         for user, password, userattr, passwordattr in test_users:
             transcoder = Transcoder("POST",
                                     ("https", "example.com", "wfs", "", ""))
-            request = self.create_transcoder_request(self.get_feature_orig)
+            request = self.create_request(self.get_feature_orig)
             transcoder.convert_request(request)
             self.assertEquals(request.body,
                               self.get_feature_modified_template % locals())



More information about the Inteproxy-commits mailing list