[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)