changeset 1061:0f18462344f8

refactored http header emitting code, CGI works, others untested
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Wed, 26 Jul 2006 12:32:29 +0200
parents 4b02826f6408
children 9d13bc34ac0a
files MoinMoin/request/CGI.py MoinMoin/request/CLI.py MoinMoin/request/FCGI.py MoinMoin/request/MODPYTHON.py MoinMoin/request/STANDALONE.py MoinMoin/request/TWISTED.py MoinMoin/request/WSGI.py MoinMoin/request/__init__.py docs/CHANGES
diffstat 9 files changed, 130 insertions(+), 206 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/request/CGI.py	Wed Jul 26 00:10:42 2006 +0200
+++ b/MoinMoin/request/CGI.py	Wed Jul 26 12:32:29 2006 +0200
@@ -63,33 +63,9 @@
             import errno
             if ex.errno != errno.EPIPE: raise
 
-    # Headers ----------------------------------------------------------
-
-    def http_headers(self, more_headers=[]):
-        # Send only once
-        if getattr(self, 'sent_headers', None):
-            return
-
-        self.sent_headers = 1
-        have_ct = 0
+    def _emit_http_headers(self, headers):
+        """ private method to send out preprocessed list of HTTP headers """
+        for header in headers:
+            self.write("%s\r\n" % header)
+        self.write("\r\n")
 
-        # send http headers
-        for header in more_headers + getattr(self, 'user_headers', []):
-            if header.lower().startswith("content-type:"):
-                # don't send content-type multiple times!
-                if have_ct: continue
-                have_ct = 1
-            if type(header) is unicode:
-                header = header.encode('ascii')
-            self.write("%s\r\n" % header)
-
-        if not have_ct:
-            self.write("Content-type: text/html;charset=%s\r\n" % config.charset)
-
-        self.write('\r\n')
-
-        #from pprint import pformat
-        #sys.stderr.write(pformat(more_headers))
-        #sys.stderr.write(pformat(self.user_headers))
-
-
--- a/MoinMoin/request/CLI.py	Wed Jul 26 00:10:42 2006 +0200
+++ b/MoinMoin/request/CLI.py	Wed Jul 26 12:32:29 2006 +0200
@@ -78,7 +78,8 @@
     def setHttpHeader(self, header):
         pass
 
-    def http_headers(self, more_headers=[]):
+    def _emit_http_headers(self, headers):
+        """ private method to send out preprocessed list of HTTP headers """
         pass
 
     def http_redirect(self, url):
--- a/MoinMoin/request/FCGI.py	Wed Jul 26 00:10:42 2006 +0200
+++ b/MoinMoin/request/FCGI.py	Wed Jul 26 12:32:29 2006 +0200
@@ -56,31 +56,9 @@
         RequestBase.finish(self)
         self.fcgreq.finish()
 
-    # Headers ----------------------------------------------------------
-
-    def http_headers(self, more_headers=[]):
-        """ Send out HTTP headers. Possibly set a default content-type. """
-        if getattr(self, 'sent_headers', None):
-            return
-        self.sent_headers = 1
-        have_ct = 0
+    def _emit_http_headers(self, headers):
+        """ private method to send out preprocessed list of HTTP headers """
+        for header in headers:
+            self.write("%s\r\n" % header)
+        self.write("\r\n")
 
-        # send http headers
-        for header in more_headers + getattr(self, 'user_headers', []):
-            if type(header) is unicode:
-                header = header.encode('ascii')
-            if header.lower().startswith("content-type:"):
-                # don't send content-type multiple times!
-                if have_ct: continue
-                have_ct = 1
-            self.write("%s\r\n" % header)
-
-        if not have_ct:
-            self.write("Content-type: text/html;charset=%s\r\n" % config.charset)
-
-        self.write('\r\n')
-
-        #from pprint import pformat
-        #sys.stderr.write(pformat(more_headers))
-        #sys.stderr.write(pformat(self.user_headers))
-
--- a/MoinMoin/request/MODPYTHON.py	Wed Jul 26 00:10:42 2006 +0200
+++ b/MoinMoin/request/MODPYTHON.py	Wed Jul 26 12:32:29 2006 +0200
@@ -138,51 +138,14 @@
         from mod_python import apache
         return apache.OK
 
-    # Headers ----------------------------------------------------------
-
-    def setHttpHeader(self, header):
-        """ Filters out content-type and status to set them directly
-            in the mod_python request. Rest is put into the headers_out
-            member of the mod_python request.
-
-            @param header: string, containing valid HTTP header.
-        """
-        if type(header) is unicode:
-            header = header.encode('ascii')
-        key, value = header.split(':', 1)
-        value = value.lstrip()
-        if key.lower() == 'content-type':
-            # save content-type for http_headers
-            if not self._have_ct:
-                # we only use the first content-type!
-                self.mpyreq.content_type = value
-                self._have_ct = 1
-        elif key.lower() == 'status':
-            # save status for finish
-            try:
-                self.mpyreq.status = int(value.split(' ', 1)[0])
-            except:
-                pass
-            else:
-                self._have_status = 1
-        else:
-            # this is a header we sent out
+    def _emit_http_headers(self, headers):
+        """ private method to send out preprocessed list of HTTP headers """
+        st_header, ct_header, other_headers = headers[0], headers[1], headers[2:]
+        status = st_header.split(':', 1)[1].lstrip()
+        self.mpyreq.status = int(status.split(' ', 1)[0])
+        self.mpyreq.content_type = ct_header.split(':', 1)[1].lstrip()
+        for header in other_headers:
             self.mpyreq.headers_out[key] = value
-
-    def http_headers(self, more_headers=[]):
-        """ Sends out headers and possibly sets default content-type
-            and status.
-
-            @param more_headers: list of strings, defaults to []
-        """
-        for header in more_headers + getattr(self, 'user_headers', []):
-            self.setHttpHeader(header)
-        # if we don't had an content-type header, set text/html
-        if self._have_ct == 0:
-            self.mpyreq.content_type = "text/html;charset=%s" % config.charset
-        # if we don't had a status header, set 200
-        if self._have_status == 0:
-            self.mpyreq.status = 200
         # this is for mod_python 2.7.X, for 3.X it's a NOP
         self.mpyreq.send_http_header()
 
--- a/MoinMoin/request/STANDALONE.py	Wed Jul 26 00:10:42 2006 +0200
+++ b/MoinMoin/request/STANDALONE.py	Wed Jul 26 12:32:29 2006 +0200
@@ -90,44 +90,14 @@
 
     # Headers ----------------------------------------------------------
 
-    def http_headers(self, more_headers=[]):
-        if getattr(self, 'sent_headers', None):
-            return
-
-        self.sent_headers = 1
-        user_headers = getattr(self, 'user_headers', [])
-
-        # check for status header and send it
-        our_status = 200
-        for header in more_headers + user_headers:
-            if header.lower().startswith("status:"):
-                try:
-                    our_status = int(header.split(':', 1)[1].strip().split(" ", 1)[0])
-                except:
-                    pass
-                # there should be only one!
-                break
-        # send response
-        self.sareq.send_response(our_status)
+    def _emit_http_headers(self, headers):
+        """ private method to send out preprocessed list of HTTP headers """
+        st_header, other_headers = headers[0], headers[1:]
+        status = st_header.split(':', 1)[1].lstrip()
+        status_code, status_msg = status.split(' ', 1)
+        status_code = int(status_code)
+        self.sareq.send_response(status_code, status_msg)
+        for header in other_headers:
+            self.write("%s\r\n" % header)
+        self.write("\r\n")
 
-        # send http headers
-        have_ct = 0
-        for header in more_headers + user_headers:
-            if type(header) is unicode:
-                header = header.encode('ascii')
-            if header.lower().startswith("content-type:"):
-                # don't send content-type multiple times!
-                if have_ct: continue
-                have_ct = 1
-
-            self.write("%s\r\n" % header)
-
-        if not have_ct:
-            self.write("Content-type: text/html;charset=%s\r\n" % config.charset)
-
-        self.write('\r\n')
-
-        #from pprint import pformat
-        #sys.stderr.write(pformat(more_headers))
-        #sys.stderr.write(pformat(self.user_headers))
-
--- a/MoinMoin/request/TWISTED.py	Wed Jul 26 00:10:42 2006 +0200
+++ b/MoinMoin/request/TWISTED.py	Wed Jul 26 12:32:29 2006 +0200
@@ -46,7 +46,7 @@
             RequestBase.__init__(self, properties)
 
         except MoinMoinFinish: # might be triggered by http_redirect
-            self.http_headers() # send headers (important for sending MOIN_ID cookie)
+            self.emit_http_headers() # send headers (important for sending MOIN_ID cookie)
             self.finish()
 
         except Exception, err:
@@ -110,34 +110,16 @@
 
     # Headers ----------------------------------------------------------
 
-    def __setHttpHeader(self, header):
-        if type(header) is unicode:
-            header = header.encode('ascii')
-        key, value = header.split(':', 1)
-        value = value.lstrip()
-        if key.lower() == 'set-cookie':
-            key, value = value.split('=', 1)
-            self.twistd.addCookie(key, value)
-        else:
-            self.twistd.setHeader(key, value)
-        #print "request.RequestTwisted.setHttpHeader: %s" % header
-
-    def http_headers(self, more_headers=[]):
-        if getattr(self, 'sent_headers', None):
-            return
-        self.sent_headers = 1
-        have_ct = 0
-
-        # set http headers
-        for header in more_headers + getattr(self, 'user_headers', []):
-            if header.lower().startswith("content-type:"):
-                # don't send content-type multiple times!
-                if have_ct: continue
-                have_ct = 1
-            self.__setHttpHeader(header)
-
-        if not have_ct:
-            self.__setHttpHeader("Content-type: text/html;charset=%s" % config.charset)
+    def _emit_http_headers(self, headers):
+        """ private method to send out preprocessed list of HTTP headers """
+        for header in headers:
+            key, value = header.split(':', 1)
+            value = value.lstrip()
+            if key.lower() == 'set-cookie':
+                key, value = value.split('=', 1)
+                self.twistd.addCookie(key, value)
+            else:
+                self.twistd.setHeader(key, value)
 
     def http_redirect(self, url):
         """ Redirect to a fully qualified, or server-rooted URL 
--- a/MoinMoin/request/WSGI.py	Wed Jul 26 00:10:42 2006 +0200
+++ b/MoinMoin/request/WSGI.py	Wed Jul 26 12:32:29 2006 +0200
@@ -21,6 +21,7 @@
             self.stdin = env['wsgi.input']
             self.stdout = StringIO.StringIO()
 
+            # used by MoinMoin.server.wsgi:
             self.status = '200 OK'
             self.headers = []
 
@@ -48,33 +49,14 @@
     def reset_output(self):
         self.stdout = StringIO.StringIO()
 
-    def setHttpHeader(self, header):
-        if type(header) is unicode:
-            header = header.encode('ascii')
-
-        key, value = header.split(':', 1)
-        value = value.lstrip()
-        if key.lower() == 'content-type':
-            # save content-type for http_headers
-            if self.hasContentType:
-                # we only use the first content-type!
-                return
-            else:
-                self.hasContentType = True
-
-        elif key.lower() == 'status':
-            # save status for finish
-            self.status = value
-            return
-
-        self.headers.append((key, value))
-
-    def http_headers(self, more_headers=[]):
-        for header in more_headers:
-            self.setHttpHeader(header)
-
-        if not self.hasContentType:
-            self.headers.insert(0, ('Content-Type', 'text/html;charset=%s' % config.charset))
+    def _emit_http_headers(self, headers):
+        """ private method to send out preprocessed list of HTTP headers """
+        st_header, other_headers = headers[0], headers[1:]
+        self.status = st_header.split(':', 1)[1].lstrip()
+        for header in other_headers:
+            key, value = header.split(':', 1)
+            value = value.lstrip()
+            self.headers.append((key, value))
 
     def flush(self):
         pass
@@ -83,6 +65,7 @@
         pass
 
     def output(self):
+        # called by MoinMoin.server.wsgi
         return self.stdout.getvalue()
 
 
--- a/MoinMoin/request/__init__.py	Wed Jul 26 00:10:42 2006 +0200
+++ b/MoinMoin/request/__init__.py	Wed Jul 26 12:32:29 2006 +0200
@@ -93,7 +93,6 @@
         # Pages meta data that we collect in one request
         self.pages = {}
 
-        self.sent_headers = 0
         self.user_headers = []
         self.cacheable = 0 # may this output get cached by http proxies/caches?
         self.page = None
@@ -974,7 +973,7 @@
         # when surge protection triggered, tell bots to come back later...
         if resultcode == 503:
             headers.append('Retry-After: %d' % self.cfg.surge_lockout_time)
-        self.http_headers(headers)
+        self.emit_http_headers(headers)
         self.setResponseCode(resultcode)
         self.write(msg)
         self.forbidden = True
@@ -1115,7 +1114,64 @@
         @param url: relative or absolute url, ascii using url encoding.
         """
         url = self.getQualifiedURL(url)
-        self.http_headers(["Status: 302 Found", "Location: %s" % url])
+        self.emit_http_headers(["Status: 302 Found", "Location: %s" % url])
+
+    def http_headers(self, more_headers=[]):
+        """ wrapper for old, deprecated http_headers call,
+            new code only calls emit_http_headers.
+            Remove in moin 1.7.
+        """
+        self.emit_http_headers(more_headers)
+
+    def emit_http_headers(self, more_headers=[]):
+        """ emit http headers after some preprocessing,
+            for emitting, it calls the server specific _emit_http_headers
+            method with a list of headers in this FIXED order:
+            1. status header (always present and valid, e.g. "200 OK")
+            2. content type header (always present)
+            3. other headers (optional)
+        """
+        # Send headers only once
+        if getattr(self, 'sent_headers', False):
+            return
+        self.sent_headers = True
+
+        content_type = None
+        status = None
+        headers = []
+        # assemble complete list of http headers
+        for header in more_headers + getattr(self, 'user_headers', []):
+            if isinstance(header, unicode):
+                header = header.encode('ascii')
+            key, value = header.split(':', 1)
+            lkey = key.lower()
+            value = value.lstrip()
+            if content_type is None and lkey == "content-type":
+                content_type = value
+            elif status is None and lkey == "status":
+                status = value
+            else:
+                headers.append(header)
+
+        if content_type is None:
+            content_type = "text/html; charset=%s" % config.charset
+        ct_header = "Content-type: %s" % content_type
+
+        if status is None:
+            status = "200 OK"
+        try:
+            int(status.split(" ", 1)[0])
+        except:
+            self.log("emit_http_headers called with invalid header Status: %s" % status)
+            status = "500 Server Error - invalid status header"
+        st_header = "Status: %s" % status
+
+        headers = [st_header, ct_header] + headers # do NOT change order!
+        self._emit_http_headers(headers)
+
+        #from pprint import pformat
+        #sys.stderr.write(pformat(headers))
+
 
     def setHttpHeader(self, header):
         """ Save header for later send.
@@ -1139,7 +1195,7 @@
         @param err: Exception instance or subclass.
         """
         self.failed = 1 # save state for self.run()            
-        self.http_headers(['Status: 500 MoinMoin Internal Error'])
+        self.emit_http_headers(['Status: 500 MoinMoin Internal Error'])
         self.setResponseCode(500)
         self.log('%s: %s' % (err.__class__.__name__, str(err)))
         from MoinMoin import failure
--- a/docs/CHANGES	Wed Jul 26 00:10:42 2006 +0200
+++ b/docs/CHANGES	Wed Jul 26 12:32:29 2006 +0200
@@ -81,11 +81,11 @@
       will be missing and the adaptor script will need a change maybe):
       CGI works
       CLI works
-      STANDALONE not
-      MODPY not
-      WSGI not
-      FCGI not
-      TWISTED not
+      STANDALONE ?
+      MODPY ?
+      WSGI ?
+      FCGI ?
+      TWISTED ?
     * moved util/antispam.py to security/antispam.py,
       moved util/autoadmin.py to security/autoadmin.py,
       moved security.py to security/__init__.py,
@@ -119,6 +119,21 @@
       TODO: write mig script for data_dir
       TODO: make blanks in interwiki pagelinks possible
     * request.action now has the action requested, default: 'show'.
+    * Cleaned up duplicated http_headers code and DEPRECATED this function
+      call (it was sometimes confused with setHttpHeaders call) - it will
+      vanish with moin 1.7, so please fix your custom plugins!
+      The replacement is:
+          request.emit_http_headers(more_headers=[])
+      This call pre-processes the headers list (encoding from unicode, making
+      sure that there is exactly ONE content-type header, etc.) and then
+      calls a server specific helper _emit_http_headers to emit it.
+      CGI works
+      CLI ?
+      STANDALONE ?
+      MODPY ?
+      WSGI ?
+      FCGI ?
+      TWISTED ?
 
   New Features:
     * Removed "underscore in URL" == "blank in pagename magic" - it made more