[Zope3-checkins] CVS: Zope3/src/zope/server/http - __init__.py:1.2 chunking.py:1.2 commonhitlogger.py:1.2 http_date.py:1.2 httprequestparser.py:1.2 httpserver.py:1.2 httpserverchannel.py:1.2 httptask.py:1.2 publisherhttpserver.py:1.2

Jim Fulton jim@zope.com
Wed, 25 Dec 2002 09:15:56 -0500


Update of /cvs-repository/Zope3/src/zope/server/http
In directory cvs.zope.org:/tmp/cvs-serv20790/src/zope/server/http

Added Files:
	__init__.py chunking.py commonhitlogger.py http_date.py 
	httprequestparser.py httpserver.py httpserverchannel.py 
	httptask.py publisherhttpserver.py 
Log Message:
Grand renaming:

- Renamed most files (especially python modules) to lower case.

- Moved views and interfaces into separate hierarchies within each
  project, where each top-level directory under the zope package
  is a separate project.

- Moved everything to src from lib/python.

  lib/python will eventually go away. I need access to the cvs
  repository to make this happen, however.

There are probably some bits that are broken. All tests pass
and zope runs, but I haven't tried everything. There are a number
of cleanups I'll work on tomorrow.



=== Zope3/src/zope/server/http/__init__.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:15:55 2002
+++ Zope3/src/zope/server/http/__init__.py	Wed Dec 25 09:15:24 2002
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.


=== Zope3/src/zope/server/http/chunking.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:15:56 2002
+++ Zope3/src/zope/server/http/chunking.py	Wed Dec 25 09:15:24 2002
@@ -0,0 +1,107 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from zope.server.utilities import find_double_newline
+from zope.server.interfaces import IStreamConsumer
+
+
+class ChunkedReceiver:
+
+    __implements__ = IStreamConsumer
+
+    chunk_remainder = 0
+    control_line = ''
+    all_chunks_received = 0
+    trailer = ''
+    completed = 0
+
+    # max_control_line = 1024
+    # max_trailer = 65536
+
+
+    def __init__(self, buf):
+        self.buf = buf
+
+    def received(self, s):
+        # Returns the number of bytes consumed.
+        if self.completed:
+            return 0
+        orig_size = len(s)
+        while s:
+            rm = self.chunk_remainder
+            if rm > 0:
+                # Receive the remainder of a chunk.
+                to_write = s[:rm]
+                self.buf.append(to_write)
+                written = len(to_write)
+                s = s[written:]
+                self.chunk_remainder -= written
+            elif not self.all_chunks_received:
+                # Receive a control line.
+                s = self.control_line + s
+                pos = s.find('\n')
+                if pos < 0:
+                    # Control line not finished.
+                    self.control_line = s
+                    s = ''
+                else:
+                    # Control line finished.
+                    line = s[:pos]
+                    s = s[pos + 1:]
+                    self.control_line = ''
+                    line = line.strip()
+                    if line:
+                        # Begin a new chunk.
+                        semi = line.find(';')
+                        if semi >= 0:
+                            # discard extension info.
+                            line = line[:semi]
+                        sz = int(line.strip(), 16)  # hexadecimal
+                        if sz > 0:
+                            # Start a new chunk.
+                            self.chunk_remainder = sz
+                        else:
+                            # Finished chunks.
+                            self.all_chunks_received = 1
+                    # else expect a control line.
+            else:
+                # Receive the trailer.
+                trailer = self.trailer + s
+                if trailer.startswith('\r\n'):
+                    # No trailer.
+                    self.completed = 1
+                    return orig_size - (len(trailer) - 2)
+                elif trailer.startswith('\n'):
+                    # No trailer.
+                    self.completed = 1
+                    return orig_size - (len(trailer) - 1)
+                pos = find_double_newline(trailer)
+                if pos < 0:
+                    # Trailer not finished.
+                    self.trailer = trailer
+                    s = ''
+                else:
+                    # Finished the trailer.
+                    self.completed = 1
+                    self.trailer = trailer[:pos]
+                    return orig_size - (len(trailer) - pos)
+        return orig_size
+
+
+    def getfile(self):
+        return self.buf.getfile()


=== Zope3/src/zope/server/http/commonhitlogger.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:15:56 2002
+++ Zope3/src/zope/server/http/commonhitlogger.py	Wed Dec 25 09:15:24 2002
@@ -0,0 +1,101 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+import time
+import sys
+
+from zope.server.http.http_date import monthname
+from zope.server.logger.filelogger import FileLogger
+from zope.server.logger.resolvinglogger import ResolvingLogger
+from zope.server.logger.unresolvinglogger import UnresolvingLogger
+
+class CommonHitLogger:
+    """Outputs hits in common HTTP log format.
+    """
+
+    def __init__(self, logger_object=None, resolver=None):
+        if logger_object is None:
+            # logger_object is an IMessageLogger
+            logger_object = FileLogger(sys.stdout)
+
+        # self.output is an IRequestLogger
+        if resolver is not None:
+            self.output = ResolvingLogger(resolver, logger_object)
+        else:
+            self.output = UnresolvingLogger(logger_object)
+
+    def compute_timezone_for_log(self, tz):
+        if tz > 0:
+            neg = 1
+        else:
+            neg = 0
+            tz = -tz
+        h, rem = divmod (tz, 3600)
+        m, rem = divmod (rem, 60)
+        if neg:
+            return '-%02d%02d' % (h, m)
+        else:
+            return '+%02d%02d' % (h, m)
+
+    tz_for_log = None
+    tz_for_log_alt = None
+
+    def log_date_string(self, when):
+        logtime = time.localtime(when)
+        Y, M, D, h, m, s = logtime[:6]
+
+        if not time.daylight:
+            tz = self.tz_for_log
+            if tz is None:
+                tz = self.compute_timezone_for_log(time.timezone)
+                self.tz_for_log = tz
+        else:
+            tz = self.tz_for_log_alt
+            if tz is None:
+                tz = self.compute_timezone_for_log(time.altzone)
+                self.tz_for_log_alt = tz
+
+        return '%d/%s/%02d:%02d:%02d:%02d %s' % (
+            Y, monthname[M], D, h, m, s, tz)
+
+
+    def log(self, task):
+        """
+        Receives a completed task and logs it in the
+        common log format.
+        """
+        now = time.time()
+        request_data = task.request_data
+        req_headers = request_data.headers
+
+        user_name = task.auth_user_name or 'anonymous'
+        user_agent = req_headers.get('USER_AGENT', '')
+        referer = req_headers.get('REFERER', '')
+
+        self.output.logRequest(
+            task.channel.addr[0],
+            ' - %s [%s] "%s" %s %d "%s" "%s"\n' % (
+                user_name,
+                self.log_date_string(now),
+                request_data.first_line,
+                task.status,
+                task.bytes_written,
+                referer,
+                user_agent
+                )
+            )


=== Zope3/src/zope/server/http/http_date.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:15:56 2002
+++ Zope3/src/zope/server/http/http_date.py	Wed Dec 25 09:15:24 2002
@@ -0,0 +1,152 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+import re
+import string
+import time
+
+def concat (*args):
+    return ''.join (args)
+
+def join (seq, field=' '):
+    return field.join (seq)
+
+def group (s):
+    return '(' + s + ')'
+
+short_days = ['sun','mon','tue','wed','thu','fri','sat']
+long_days = ['sunday','monday','tuesday','wednesday',
+             'thursday','friday','saturday']
+
+short_day_reg = group(join (short_days, '|'))
+long_day_reg = group(join (long_days, '|'))
+
+daymap = {}
+for i in range(7):
+    daymap[short_days[i]] = i
+    daymap[long_days[i]] = i
+
+hms_reg = join (3 * [group('[0-9][0-9]')], ':')
+
+months = ['jan','feb','mar','apr','may','jun','jul',
+          'aug','sep','oct','nov','dec']
+
+monmap = {}
+for i in range(12):
+    monmap[months[i]] = i+1
+
+months_reg = group (join (months, '|'))
+
+# From draft-ietf-http-v11-spec-07.txt/3.3.1
+#       Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
+#       Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
+#       Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
+
+# rfc822 format
+rfc822_date = join (
+        [concat (short_day_reg,','),        # day
+         group('[0-9][0-9]?'),                        # date
+         months_reg,                                        # month
+         group('[0-9]+'),                                # year
+         hms_reg,                                                # hour minute second
+         'gmt'
+         ],
+        ' '
+        )
+
+rfc822_reg = re.compile (rfc822_date)
+
+def unpack_rfc822 (m):
+    g = m.group
+    a = string.atoi
+    return (
+            a(g(4)),                   # year
+            monmap[g(3)],        # month
+            a(g(2)),                # day
+            a(g(5)),                # hour
+            a(g(6)),                # minute
+            a(g(7)),                # second
+            0,
+            0,
+            0
+            )
+
+    # rfc850 format
+rfc850_date = join (
+        [concat (long_day_reg,','),
+         join (
+                 [group ('[0-9][0-9]?'),
+                  months_reg,
+                  group ('[0-9]+')
+                  ],
+                 '-'
+                 ),
+         hms_reg,
+         'gmt'
+         ],
+        ' '
+        )
+
+rfc850_reg = re.compile (rfc850_date)
+# they actually unpack the same way
+def unpack_rfc850 (m):
+    g = m.group
+    a = string.atoi
+    return (
+            a(g(4)),                   # year
+            monmap[g(3)],        # month
+            a(g(2)),                # day
+            a(g(5)),                # hour
+            a(g(6)),                # minute
+            a(g(7)),                # second
+            0,
+            0,
+            0
+            )
+
+    # parsdate.parsedate        - ~700/sec.
+    # parse_http_date            - ~1333/sec.
+
+weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+             'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+
+def build_http_date (when):
+    year, month, day, hh, mm, ss, wd, y, z = time.gmtime(when)
+    return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
+            weekdayname[wd],
+            day, monthname[month], year,
+            hh, mm, ss)
+
+def parse_http_date (d):
+    d = string.lower (d)
+    tz = time.timezone
+    m = rfc850_reg.match (d)
+    if m and m.end() == len(d):
+        retval = int (time.mktime (unpack_rfc850(m)) - tz)
+    else:
+        m = rfc822_reg.match (d)
+        if m and m.end() == len(d):
+            retval = int (time.mktime (unpack_rfc822(m)) - tz)
+        else:
+            return 0
+            # Thanks to Craig Silverstein <csilvers@google.com> for pointing
+            # out the DST discrepancy
+    if time.daylight and time.localtime(retval)[-1] == 1: # DST correction
+        retval = retval + (tz - time.altzone)
+    return retval


=== Zope3/src/zope/server/http/httprequestparser.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:15:56 2002
+++ Zope3/src/zope/server/http/httprequestparser.py	Wed Dec 25 09:15:24 2002
@@ -0,0 +1,203 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+This server uses asyncore to accept connections and do initial
+processing but threads to do work.
+
+$Id$
+"""
+
+import re
+from urllib import unquote
+
+from zope.server.fixedstreamreceiver import FixedStreamReceiver
+from zope.server.buffers import OverflowableBuffer
+from zope.server.utilities import find_double_newline
+from zope.server.interfaces import IStreamConsumer
+
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+
+class HTTPRequestParser:
+    """A structure that collects the HTTP request.
+
+    Once the stream is completed, the instance is passed to
+    a server task constructor.
+    """
+
+    __implements__ = IStreamConsumer
+
+    completed = 0  # Set once request is completed.
+    empty = 0        # Set if no request was made.
+    header_plus = ''
+    chunked = 0
+    content_length = 0
+    body_rcv = None
+    # Other attributes: first_line, header, headers, command, uri, version,
+    # path, query, fragment
+
+    # headers is a mapping containing keys translated to uppercase
+    # with dashes turned into underscores.
+
+    def __init__(self, adj):
+        """
+        adj is an Adjustments object.
+        """
+        self.headers = {}
+        self.adj = adj
+
+    def received(self, data):
+        """
+        Receives the HTTP stream for one request.
+        Returns the number of bytes consumed.
+        Sets the completed flag once both the header and the
+        body have been received.
+        """
+        if self.completed:
+            return 0  # Can't consume any more.
+        datalen = len(data)
+        br = self.body_rcv
+        if br is None:
+            # In header.
+            s = self.header_plus + data
+            index = find_double_newline(s)
+            if index >= 0:
+                # Header finished.
+                header_plus = s[:index]
+                consumed = len(data) - (len(s) - index)
+                self.in_header = 0
+                # Remove preceeding blank lines.
+                header_plus = header_plus.lstrip()
+                if not header_plus:
+                    self.empty = 1
+                    self.completed = 1
+                else:
+                    self.parse_header(header_plus)
+                    if self.body_rcv is None:
+                        self.completed = 1
+                return consumed
+            else:
+                # Header not finished yet.
+                self.header_plus = s
+                return datalen
+        else:
+            # In body.
+            consumed = br.received(data)
+            if br.completed:
+                self.completed = 1
+            return consumed
+
+
+    def parse_header(self, header_plus):
+        """
+        Parses the header_plus block of text (the headers plus the
+        first line of the request).
+        """
+        index = header_plus.find('\n')
+        if index >= 0:
+            first_line = header_plus[:index].rstrip()
+            header = header_plus[index + 1:]
+        else:
+            first_line = header_plus.rstrip()
+            header = ''
+        self.first_line = first_line
+        self.header = header
+
+        lines = self.get_header_lines()
+        headers = self.headers
+        for line in lines:
+            index = line.find(':')
+            if index > 0:
+                key = line[:index]
+                value = line[index + 1:].strip()
+                key1 = key.upper().replace('-', '_')
+                headers[key1] = value
+            # else there's garbage in the headers?
+
+        command, uri, version = self.crack_first_line()
+        self.command = str(command)
+        self.uri = str(uri)
+        self.version = version
+        self.split_uri()
+
+        if version == '1.1':
+            te = headers.get('TRANSFER_ENCODING', '')
+            if te == 'chunked':
+                from zope.server.http.chunking import ChunkedReceiver
+                self.chunked = 1
+                buf = OverflowableBuffer(self.adj.inbuf_overflow)
+                self.body_rcv = ChunkedReceiver(buf)
+        if not self.chunked:
+            cl = int(headers.get('CONTENT_LENGTH', 0))
+            self.content_length = cl
+            if cl > 0:
+                buf = OverflowableBuffer(self.adj.inbuf_overflow)
+                self.body_rcv = FixedStreamReceiver(cl, buf)
+
+
+    def get_header_lines(self):
+        """
+        Splits the header into lines, putting multi-line headers together.
+        """
+        r = []
+        lines = self.header.split('\n')
+        for line in lines:
+            if line and line[0] in ' \t':
+                r[-1] = r[-1] + line[1:]
+            else:
+                r.append(line)
+        return r
+
+    first_line_re = re.compile (
+        '([^ ]+) (?:[^ :?#]+://[^ ?#/]*)?([^ ]+)(( HTTP/([0-9.]+))$|$)')
+
+    def crack_first_line(self):
+        r = self.first_line
+        m = self.first_line_re.match (r)
+        if m is not None and m.end() == len(r):
+            if m.group(3):
+                version = m.group(5)
+            else:
+                version = None
+            return m.group(1).upper(), m.group(2), version
+        else:
+            return None, None, None
+
+    path_regex = re.compile (
+    #     path    query     fragment
+        r'([^?#]*)(\?[^#]*)?(#.*)?'
+        )
+
+    def split_uri(self):
+        m = self.path_regex.match (self.uri)
+        if m.end() != len(self.uri):
+            raise ValueError, "Broken URI"
+        else:
+            path, query, self.fragment = m.groups()
+            if path and '%' in path:
+                path = unquote(path)
+            self.path = path
+            if query:
+                query = query[1:]
+            self.query = query
+
+    def getBodyStream(self):
+        body_rcv = self.body_rcv
+        if body_rcv is not None:
+            return body_rcv.getfile()
+        else:
+            return StringIO('')


=== Zope3/src/zope/server/http/httpserver.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:15:56 2002
+++ Zope3/src/zope/server/http/httpserver.py	Wed Dec 25 09:15:24 2002
@@ -0,0 +1,56 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+This server uses asyncore to accept connections and do initial
+processing but threads to do work.
+
+$Id$
+"""
+
+from zope.server.serverbase import ServerBase
+from zope.server.http.httpserverchannel import HTTPServerChannel
+
+
+class HTTPServer(ServerBase):
+    """This is a generic HTTP Server."""
+
+    __implements__ = ServerBase.__implements__
+
+    channel_class = HTTPServerChannel
+    SERVER_IDENT = 'zope.server.http'
+
+    def executeRequest(self, task):
+        """Execute an HTTP request."""
+        # This is a default implementation, meant to be overridden.
+        body = "The HTTP server is running!\r\n" * 10
+        task.response_headers['Content-Type'] = 'text/plain'
+        task.response_headers['Content-Length'] = str(len(body))
+        task.write(body)
+
+
+if __name__ == '__main__':
+
+    from zope.server.taskthreads import ThreadedTaskDispatcher
+    td = ThreadedTaskDispatcher()
+    td.setThreadCount(4)
+    HTTPServer('', 8080, task_dispatcher=td)
+
+    try:
+        import asyncore
+        while 1:
+            asyncore.poll(5)
+
+    except KeyboardInterrupt:
+        print 'shutting down...'
+        td.shutdown()


=== Zope3/src/zope/server/http/httpserverchannel.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:15:56 2002
+++ Zope3/src/zope/server/http/httpserverchannel.py	Wed Dec 25 09:15:24 2002
@@ -0,0 +1,30 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from zope.server.serverchannelbase import ServerChannelBase
+from zope.server.http.httptask import HTTPTask
+from zope.server.http.httprequestparser import HTTPRequestParser
+
+
+class HTTPServerChannel(ServerChannelBase):
+    """HTTP-specific Server Channel"""
+
+    __implements__ = ServerChannelBase.__implements__
+
+    task_class = HTTPTask
+    parser_class = HTTPRequestParser


=== Zope3/src/zope/server/http/httptask.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:15:56 2002
+++ Zope3/src/zope/server/http/httptask.py	Wed Dec 25 09:15:24 2002
@@ -0,0 +1,234 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+This server uses asyncore to accept connections and do initial
+processing but threads to do work.
+
+$Id$
+"""
+
+import socket
+import time
+
+from zope.server.http.http_date import build_http_date
+
+from zope.server.interfaces import IHeaderOutput
+from zope.server.interfaces import ITask
+
+rename_headers = {
+    'CONTENT_LENGTH' : 'CONTENT_LENGTH',
+    'CONTENT_TYPE'   : 'CONTENT_TYPE',
+    'CONNECTION'     : 'CONNECTION_TYPE',
+    }
+
+class HTTPTask:
+    """An HTTP task accepts a request and writes to a channel.
+
+       Subclass this and override the execute() method.
+    """
+
+    __implements__ = ITask, IHeaderOutput  #, IOutputStream
+
+    instream = None
+    close_on_finish = 1
+    status = '200'
+    reason = 'Ok'
+    wrote_header = 0
+    accumulated_headers = None
+    bytes_written = 0
+    auth_user_name = ''
+    cgi_env = None
+
+    def __init__(self, channel, request_data):
+        self.channel = channel
+        self.request_data = request_data
+        self.response_headers = {
+            'Server': channel.server.SERVER_IDENT,
+            }
+        version = request_data.version
+        if version not in ('1.0', '1.1'):
+            # fall back to a version we support.
+            version = '1.0'
+        self.version = version
+
+    def service(self):
+        'See ITask'
+        try:
+            try:
+                self.start()
+                self.channel.server.executeRequest(self)
+                self.finish()
+            except socket.error:
+                self.close_on_finish = 1
+                if self.channel.adj.log_socket_errors:
+                    raise
+        finally:
+            self.channel.end_task(self.close_on_finish)
+
+    def cancel(self):
+        'See ITask'
+        self.channel.close_when_done()
+
+    def defer(self):
+        'See ITask'
+        pass
+
+    def setResponseStatus(self, status, reason):
+        """See the IHeaderOutput interface."""
+        self.status = status
+        self.reason = reason
+
+    def setResponseHeaders(self, mapping):
+        """See the IHeaderOutput interface."""
+        self.response_headers.update(mapping)
+
+    def appendResponseHeaders(self, lst):
+        """See the IHeaderOutput interface."""
+        accum = self.accumulated_headers
+        if accum is None:
+            self.accumulated_headers = accum = []
+        accum.extend(lst)
+
+    def wroteResponseHeader(self):
+        """See the IHeaderOutput interface."""
+        return self.wrote_header
+
+    def setAuthUserName(self, name):
+        """See the IHeaderOutput interface."""
+        self.auth_user_name = name
+
+    def prepareResponseHeaders(self):
+        version = self.version
+        # Figure out whether the connection should be closed.
+        connection = self.request_data.headers.get('CONNECTION', '').lower()
+        close_it = 0
+        response_headers = self.response_headers
+
+        if version == '1.0':
+            if connection == 'keep-alive':
+                if not ('Content-Length' in response_headers):
+                    close_it = 1
+                else:
+                    response_headers['Connection'] = 'Keep-Alive'
+            else:
+                close_it = 1
+        elif version == '1.1':
+            if connection == 'close':
+                close_it = 1
+            elif 'Transfer-Encoding' in response_headers:
+                if not response_headers['Transfer-Encoding'] == 'chunked':
+                    close_it = 1
+            elif self.status == '304':
+                # Replying with headers only.
+                pass
+            elif not ('Content-Length' in response_headers):
+                close_it = 1
+        else:
+            # Close if unrecognized HTTP version.
+            close_it = 1
+
+        self.close_on_finish = close_it
+        if close_it:
+            self.response_headers['Connection'] = 'close'
+
+    def buildResponseHeader(self):
+        self.prepareResponseHeaders()
+        first_line = 'HTTP/%s %s %s' % (self.version, self.status, self.reason)
+        lines = [first_line] + ['%s: %s' % hv
+                                for hv in self.response_headers.items()]
+        accum = self.accumulated_headers
+        if accum is not None:
+            lines.extend(accum)
+        res = '%s\r\n\r\n' % '\r\n'.join(lines)
+        return res
+
+    def getCGIEnvironment(self):
+        """Returns a CGI-like environment."""
+        env = self.cgi_env
+        if env is not None:
+            # Return the cached copy.
+            return env
+
+        request_data = self.request_data
+        path = request_data.path
+        channel = self.channel
+        server = channel.server
+
+        while path and path.startswith('/'):
+            path = path[1:]
+
+        env = {}
+        env['REQUEST_METHOD'] = request_data.command.upper()
+        env['SERVER_PORT'] = str(server.port)
+        env['SERVER_NAME'] = server.server_name
+        env['SERVER_SOFTWARE'] = server.SERVER_IDENT
+        env['SERVER_PROTOCOL'] = "HTTP/%s" % self.version
+        env['CHANNEL_CREATION_TIME'] = channel.creation_time
+        env['SCRIPT_NAME']=''
+        env['PATH_INFO']='/' + path
+        query = request_data.query
+        if query:
+            env['QUERY_STRING'] = query
+        env['GATEWAY_INTERFACE'] = 'CGI/1.1'
+        addr = channel.addr[0]
+        env['REMOTE_ADDR'] = addr
+
+        # If the server has a resolver, try to get the
+        # remote host from the resolver's cache.
+        resolver = getattr(server, 'resolver', None)
+        if resolver is not None:
+            dns_cache = resolver.cache
+            if addr in dns_cache:
+                remote_host = dns_cache[addr][2]
+                if remote_host is not None:
+                    env['REMOTE_HOST'] = remote_host
+
+        env_has = env.has_key
+
+        for key, value in request_data.headers.items():
+            value = value.strip()
+            mykey = rename_headers.get(key, None)
+            if mykey is None:
+                mykey = 'HTTP_%s' % key
+            if not env_has(mykey):
+                env[mykey] = value
+
+        self.cgi_env = env
+        return env
+
+    def start(self):
+        now = time.time()
+        self.start_time = now
+        self.response_headers['Date'] = build_http_date (now)
+
+    def finish(self):
+        if not self.wrote_header:
+            self.write('')
+        hit_log = self.channel.server.hit_log
+        if hit_log is not None:
+            hit_log.log(self)
+
+    def write(self, data):
+        channel = self.channel
+        if not self.wrote_header:
+            rh = self.buildResponseHeader()
+            channel.write(rh)
+            self.bytes_written += len(rh)
+            self.wrote_header = 1
+        if data:
+            channel.write(data)
+            self.bytes_written += len(data)
+
+    def flush(self):
+        self.channel.flush()


=== Zope3/src/zope/server/http/publisherhttpserver.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:15:56 2002
+++ Zope3/src/zope/server/http/publisherhttpserver.py	Wed Dec 25 09:15:24 2002
@@ -0,0 +1,49 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from zope.server.http.httpserver import HTTPServer
+from zope.publisher.publish import publish
+
+
+class PublisherHTTPServer(HTTPServer):
+    """Zope Publisher-specific HTTP Server"""
+
+    __implements__ = HTTPServer.__implements__
+
+    def __init__(self, request_factory, sub_protocol=None, *args, **kw):
+        sub_protocol = str(sub_protocol)
+
+        self.request_factory = request_factory
+
+        # An HTTP server is not limited to server up HTML; it can be
+        # used for other protocols, like XML-RPC, SOAP and so as well
+        # Here we just allow the logger to output the sub-protocol type.
+        if sub_protocol:
+            self.SERVER_IDENT += ' (%s)' %sub_protocol
+
+        HTTPServer.__init__(self, *args, **kw)
+
+    def executeRequest(self, task):
+        """Overrides HTTPServer.executeRequest()."""
+        env = task.getCGIEnvironment()
+        instream = task.request_data.getBodyStream()
+
+        request = self.request_factory(instream, task, env)
+        response = request.response
+        response.setHeaderOutput(task)
+        publish(request)