gallery.py: Rework headers to use an OrderedDict
authorW. Trevor King <wking@tremily.us>
Mon, 9 Dec 2013 05:17:06 +0000 (21:17 -0800)
committerW. Trevor King <wking@tremily.us>
Mon, 9 Dec 2013 05:17:06 +0000 (21:17 -0800)
This makes it easier to manipulate and access headers (e.g. to extract
the charset).  Also:

* Use CRLF line endings for headers [1], instead of our old LF.
* Encode text/ content to bytes (other content should already be in
  bytes) [2].  The encoding used for text/ content is configurable.

[1]: http://tools.ietf.org/html/rfc2616#section-6.1
[2]: http://www.python.org/dev/peps/pep-0333/#unicode-issues

posts/gallery/gallery.py

index 5799641f09ec449737aebb17409fd0829166badc..ac62c3686cece39500128bc8cb62e08e4bb12d17 100755 (executable)
@@ -42,6 +42,7 @@ This script can also be run as a Simple Common Gateway Interface
 (SCGI) with the ``--scgi`` option.
 """
 
+import collections as _collections
 import logging as _logging
 import logging.handlers as _logging_handlers
 import math as _math
@@ -157,38 +158,58 @@ class CGIGalleryServer (object):
         self._base_url = base_url
         self._cache_path = cache_path
         self._serve_originals = serve_originals
+        self._text_charset = 'UTF-8'
         self._url_regexp = _re.compile('^[a-zA-Z0-9._/-]*$')
         self._rows = 3
         self._columns = 3
         self.header = []
         self.footer = []
 
-    def _http_header(self, mime='text/html', status=200):
+    def _get_charset(self, headers):
+        content_type = headers.get('Content-type', '')
+        if 'charset=' in content_type:
+            return content_type.split('charset=', 1)[-1]
+
+    def _http_headers(self, mime='text/html', status=200):
         msg = RESPONSES[status]
-        header = ['Status: {:d} {}'.format(status, msg)]
+        headers = _collections.OrderedDict()
+        headers['Status'] = '{:d} {}'.format(status, msg)
         if mime.startswith('text/'):
-            charset = '; charset=UTF-8'
+            charset = '; charset={}'.format(self._text_charset)
         else:
             charset = ''
-        header.append('Content-type: {}{}'.format(mime, charset))
-        return '\n'.join(header)
+        headers['Content-type'] = '{}{}'.format(mime, charset)
+        return headers
+
+    def _add_header(self, headers=None, stream=None):
+        if headers and self._write_http_headers:
+            for key, value in headers.items():
+                stream.write(
+                    '{}: {}\r\n'.format(key, value).encode('US-ASCII'))
+            stream.write('\r\n'.encode('US-ASCII'))
 
-    def _response(self, header=None, content='<h1>It works!</h1>',
+    def _response(self, headers=None, content='<h1>It works!</h1>',
                   stream=None):
-        if header is None:
-            header = self._http_header()
-        stream.write(header)
-        stream.write('\n\n')
+        if headers is None:
+            headers = self._http_headers()
+        self._add_header(headers=headers, stream=stream)
+        charset = self._get_charset(headers=headers)
+        if charset:
+            content = content.encode(charset)
         stream.write(content)
         raise ProcessingComplete()
 
-    def _response_stream(self, header=None, content=None, stream=None,
+    def _response_stream(self, headers=None, content=None, stream=None,
                          chunk_size=1024):
         LOG.debug('streaming response')
-        if header is None:
-            header = self._http_header()
-        stream.write(header)
-        stream.write('\n\n')
+        if headers is None:
+            headers = self._http_headers()
+        charset = self._get_charset(headers=headers)
+        if charset:
+            raise HTTPError(
+                500,
+                content='charset {} set for streamed response'.format(charset))
+        self._add_header(headers=headers, stream=stream)
         stream.flush()  # flush headers
         while True:
             chunk = content.read(chunk_size)
@@ -198,10 +219,10 @@ class CGIGalleryServer (object):
         raise ProcessingComplete()
 
     def _error(self, status=404, content=None, stream=None):
-        header = self._http_header(status=status)
+        headers = self._http_headers(status=status)
         if content is None:
             content = RESPONSES[status]
-        self._response(header=header, content=content, stream=stream)
+        self._response(headers=headers, content=content, stream=stream)
 
     def validate_url(self, url, exists=True, directory=False):
         LOG.debug('validating {} (exists={}, directory={})'.format(
@@ -568,9 +589,9 @@ class CGIGalleryServer (object):
         headers = self._http_headers(mime=mime)
         if mime in STREAMING_TYPES:
             self._response_stream(
-                header=header, content=content, stream=stream)
+                headers=headers, content=content, stream=stream)
         content = content.read()
-        self._response(header=header, content=content, stream=stream)
+        self._response(headers=headers, content=content, stream=stream)
 
     def page(self, url, page=0, stream=None):
         LOG.debug('HTML page {} {}'.format(url, page))