diff --git a/VERSION b/VERSION
index 6d0bcfe0..002e289d 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-Version 2.4.1-alpha.1+timestamp.2012.12.24.13.48.18
+Version 2.4.1-alpha.1+timestamp.2012.12.24.13.54.21
diff --git a/certificates/web2py.com/new-digitss.csr b/certificates/web2py.com/new-digitss.csr
deleted file mode 100644
index fdca57bf..00000000
--- a/certificates/web2py.com/new-digitss.csr
+++ /dev/null
@@ -1,18 +0,0 @@
------BEGIN CERTIFICATE REQUEST-----
-MIICzjCCAbYCAQAwgYgxCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhJbGxpbm9pczER
-MA8GA1UEBxMIT2FrIFBhcmsxDzANBgNVBAoTBndlYjJweTEXMBUGA1UEAxMOd3d3
-LndlYjJweS5jb20xKTAnBgkqhkiG9w0BCQEWGm1hc3NpbW8uZGlwaWVycm9AZ21h
-aWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6AX4oTjdBThw
-xdGEDtb+XhvyW58gC9il2z5ewp/AVwYUst1MYPF1jPLv23yn4jkN4c6OExlhgsVi
-YxsWq8fEg55SEBr3gpH608FOtwicEeIHwufP7OhHiOB1rhUa5B54xIIoh5AMKwIH
-qvPlLn8Pi8DJGdAoDwUTUsFLWJdt0/XQgjHQOeIrgXRvrGPLWQglQ3/8AqWZ2TLp
-raBZv9RgXJ8IlArwfEzfdQnfkrmAMOjsBRaf7krQMAF7n+R1SqNWQeFfw//FccC9
-seHvRkbawjyHL0VezmR/eAUGw8nYZuEjhUEZnrGpD4fRWSxfZuk7nx2EXKh4qOJL
-kVQHu5/ZOQIDAQABoAAwDQYJKoZIhvcNAQEFBQADggEBANjuO4viE6MpTVyMDJNv
-Cx60q4fq5ej9QU+KDb9A5wTHl6/KRJNd7EPJl07OP1Mm0D2Krp+GfLh7vVLKvxl1
-G1f4sDkXeAD1OhcqcUEBdS4CzLCYP0RdW9T77lQ5i43C+XBJH3RGzdgofjS41JNY
-mswCv66mSMb28PoRPYiGLfW/RKO27g+viYrVoJBikntjR86x7hwBUWjpOv+jBk3a
-jtE39KDj1ID/3eYrkdz3Hz65VvK4rFSY/TwTYzRrTO/4SZjwCZfgcy1BdFRn5aSK
-pB2fxDLEg/bydBdI78PtxocFBX7qZfm9VWpj8UfN0D+SAFUyesxEuQDPIKO5FHMm
-Otk=
------END CERTIFICATE REQUEST-----
diff --git a/certificates/web2py.com/new-digitss.key b/certificates/web2py.com/new-digitss.key
deleted file mode 100644
index 6244f2b7..00000000
--- a/certificates/web2py.com/new-digitss.key
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEpgIBAAKCAQEA6AX4oTjdBThwxdGEDtb+XhvyW58gC9il2z5ewp/AVwYUst1M
-YPF1jPLv23yn4jkN4c6OExlhgsViYxsWq8fEg55SEBr3gpH608FOtwicEeIHwufP
-7OhHiOB1rhUa5B54xIIoh5AMKwIHqvPlLn8Pi8DJGdAoDwUTUsFLWJdt0/XQgjHQ
-OeIrgXRvrGPLWQglQ3/8AqWZ2TLpraBZv9RgXJ8IlArwfEzfdQnfkrmAMOjsBRaf
-7krQMAF7n+R1SqNWQeFfw//FccC9seHvRkbawjyHL0VezmR/eAUGw8nYZuEjhUEZ
-nrGpD4fRWSxfZuk7nx2EXKh4qOJLkVQHu5/ZOQIDAQABAoIBAQCAyI+wLPfApgsy
-QZCjDAI0Ch8Da9dFvAKTJpNCQ5lmtCFZBiwWwu8UROONWCt3DWP/Kb8CKtxM0XJC
-wA5dWoxg4cd62Pz4pD4suucOAOFfq63TKQfn3yWVe8DV1zEN3Tv7FqOYWwPl+0J2
-SNm0bSIb1+3HPhp9+7s1+8PucF6tfnyRMtZAXN0MURsMLHhxepi1hd1nxEhFt2gg
-F57CQVTMFnGZd9swSgJhFbMbQeBjBd/KSE8Jf+Gr3jMbpyQoqAsJrmikmz1ONvYA
-+FlIDxNFJZWXYTXld3amXXjPyDgc4VbmrtTTYHpZHjwecEf/Sj1J7MAGCXJoAwuF
-7LeByp1hAoGBAPoLl1VFyC0p3Iu2wViW1CxT2inb5eH7IuosPxG41vFKG/wom+Ww
-bT8BUwGwGXJv/PIMbAKbJ8z7SZtSkY4I+hL51r+rplwwnxW7AfuRB2wK3KEoBtUN
-EdmNlVW2DNIIJitv2Cszx7z1myRhaOj9xNz6cI5FhkGAMmE+rr8Q1e6LAoGBAO2M
-gjfu1/iI7QWXG9tS3/TE3n7JVXpt222iJh4Df6oYAa93snU0OAAV3DiURpDmbi43
-qpryiDgaM5ONCmOgYs3wM7Vpc5PdKtwZdIOcMP78Vk7hwaAsFn1k+WjIXlPcRWLi
-3iAAzjz+juXa6BGxBcQf2PmPuEEOwBrnVaLfIjPLAoGBAI6Boqs3zSFoVUjPTJxX
-we7Nj24B5J+JiNmbhApupRn5mb95yXf7uBeLgqpYmfgtuSSeTcSidumNYeMDCO/B
-T/0WpPyyMGskTtaMsVGV03wRKLM0Y8hy3XR3C+BAlEfz2wUwhiwBI9oexF/UoH0B
-fHxQw2tiVmToUk438lH0n2iHAoGBAN5iIPIN9j+kqAgIu7pqbmDuAME+6HfR6o9L
-MlX8voJVMRa8fuO8PATQcjbBkQJLxrAbyup9zMqAtPIYIra7ymOy0CWwD9RIQ7yD
-VBF1omTDhGUacbLqMy+23MqccboeRxLZAkuBlZGCWfKTKZiOWbCOhAi15C97iJpn
-IUN1ksZbAoGBANY7zwfUFPeKoChU92Jsub0daLGFBX8IRkRmDGPUvSHtMG2LRUdX
-s12Y2wCVX9nDwVHvVo1E+Xnc1UeXJZI6r9P0GnJqhTf/4Z9XULCb4E/nJdrd1zcq
-VYnb6p0ECh1N6syJ+YeBLG6aWKU0sLfri8LNxTFRza2lVP6aRLt+iE6M
------END RSA PRIVATE KEY-----
diff --git a/dist/web2py-2.1.1.tar.gz b/dist/web2py-2.1.1.tar.gz
deleted file mode 100644
index 8ca6f521..00000000
Binary files a/dist/web2py-2.1.1.tar.gz and /dev/null differ
diff --git a/temps/asynwsgi.py b/temps/asynwsgi.py
deleted file mode 100644
index edac590f..00000000
--- a/temps/asynwsgi.py
+++ /dev/null
@@ -1,640 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
-docstring
-"""
-
-__author__ = "Gu Yingbo (tensiongyb@gmail.com)"
-
-import os
-import re
-import sys
-import time
-import base64
-import asyncore
-import asynchat
-import rfc822
-import socket
-import thread
-import signal
-import collections
-from traceback import format_exc
-from urllib import unquote, splitquery
-from urlparse import urlparse
-try:
- import cStringIO as StringIO
-except ImportError:
- import StringIO
-
-RN = '\r\n'
-RNRN = '\r\n\r\n'
-QUOTED_SLASH = re.compile("(?i)%2F")
-
-COMMA_SEPARATED_HEADERS = ['ACCEPT', 'ACCEPT-CHARSET', 'ACCEPT-ENCODING',
- 'ACCEPT-LANGUAGE', 'ACCEPT-RANGES', 'ALLOW', 'CACHE-CONTROL',
- 'CONNECTION', 'CONTENT-ENCODING', 'CONTENT-LANGUAGE', 'EXPECT',
- 'IF-MATCH', 'IF-NONE-MATCH', 'PRAGMA', 'PROXY-AUTHENTICATE', 'TE',
- 'TRAILER', 'TRANSFER-ENCODING', 'UPGRADE', 'VARY', 'VIA', 'WARNING',
- 'WWW-AUTHENTICATE']
-
-class FIFO(list):
- def is_empty(self):
- return len(self)==0
- def push(self,data):
- self.append(data)
- def first(self):
- return self[0] if len(self) else None
-
-class RequestHandler(asynchat.async_chat):
-
- def __init__(self, sock, server):
- asynchat.async_chat.__init__(self, sock)
- self.server = server
- self.reset()
-
- def reset(self):
- self.ac_in_buffer = ''
- self.ac_out_buffer = ''
- self.producer_fifo.clear() # = collections.deque()
- self.set_terminator(RNRN)
- self.process = "reading headers"
- self.started_response = False
- self.status = ""
- self.outheaders = []
- self.sent_headers = False
- self.close_connection = False
- self.chunked_write = False
-
- self.rfile = StringIO.StringIO()
-
- self.environ = {
- "wsgi.version": (1, 0),
- "wsgi.url_scheme": "http",
- "wsgi.multithread": False,
- "wsgi.multiprocess": False,
- "wsgi.run_once": False,
- "wsgi.errors": sys.stderr,
- }
-
- def collect_incoming_data(self, data):
- """Buffer the data"""
- if self.process == "reading chunked":
- self.line_buffer += data
- else:
- self.rfile.write(data)
-
- def found_terminator(self):
- if self.process == "reading headers":
- self.rfile.write(RNRN)
- self.rfile.seek(0)
- try:
- self.parse_request()
- except:
- self.simple_response("500 Internal Server Error", format_exc())
- return
-
- if not self.read_chunked:
- if self.environ["REQUEST_METHOD"].upper() == "POST":
- cl = int(self.environ["CONTENT_LENGTH"])
- self.rfile = StringIO.StringIO()
- self.environ["wsgi.input"] = self.rfile
- self.process = "dealing post"
- self.set_terminator(cl)
- else:
- return self.wsgi_response()
- else:
- self.rfile = StringIO.StringIO()
- self.environ["wsgi.input"] = self.rfile
- self.process = "reading chunked"
- self.set_terminator(RN)
- self.line_buffer = ""
- self.cl = 0
- elif self.process == "dealing post":
- self.rfile.seek(0)
- return self.wsgi_response()
- elif self.process == "reading chunked":
- line = self.line_buffer.split(";", 1)
- chunk_size = int(line.pop(0), 16)
- if chunk_size <= 0:
- self.process = "reading tail headers"
- self.set_terminator(RNRN)
- else:
- self.cl += chunk_size
- self.process = "get chunked"
- self.set_terminator(self.cl+2) # browsers sometimes over-send
- elif self.process == "get chunked":
- crlf = self.rfile.getvalue()[-2:]
- if not crlf != RN:
- self.simple_response("400 Bad Request",
- "Bad chunked transfer coding "
- "(expected '\\r\\n', got %r)" % crlf)
- return
- else:
- self.process = "reading chunked"
- self.set_terminator(RN)
- self.rfile.seek(-2, 2)
- self.rfile.truncate()
- elif self.process == "reading tail headers":
- self.read_headers()
- self.rfile.seek(0)
- self.environ["CONTENT_LENGTH"] = str(self.cl) or ""
- return self.wsgi_response()
-
- def reset_when_done(self):
- self.producer_fifo.append('\0')
-
- def refill_buffer (self):
- while 1:
- if len(self.producer_fifo):
- p = self.producer_fifo.first()
- # a 'None' in the producer fifo is a sentinel,
- # telling us to close the channel.
- if p == 0:
- if not self.ac_out_buffer:
- self.producer_fifo.pop()
- self.reset()
- return
- elif p is None:
- if not self.ac_out_buffer:
- self.producer_fifo.pop()
- self.close()
- return
- elif isinstance(p, str):
- self.producer_fifo.pop()
- self.ac_out_buffer = self.ac_out_buffer + p
- return
- data = p.more()
- if data:
- self.ac_out_buffer = self.ac_out_buffer + data
- return
- else:
- self.producer_fifo.pop()
- else:
- return
-
- def wsgi_response(self):
- try:
- self.respond()
- except:
- self.simple_response("500 Internal Server Error", format_exc())
- else:
- if self.close_connection or ( not self.server.keep_going ):
- self.close_when_done()
- else:
- self.reset_when_done()
-
- def readable(self):
- return self.server.keep_going and not self.writable() and \
- asynchat.async_chat.readable(self)
-
- def handle_expt(self):
- asynchat.async_chat.handle_expt(self)
- self.close()
-
- def handle_error(self):
- asynchat.async_chat.handle_error(self)
- self.close()
-
- def parse_request(self):
- """
- Parse the next HTTP request start-line and message-headers.
- HTTP/1.1 connections are persistent by default. If a client
- requests a page, then idles (leaves the connection open),
- then rfile.readline() will raise socket.error("timed out").
- Note that it does this based on the value given to settimeout(),
- and doesn't need the client to request or acknowledge the close
- (although your TCP stack might suffer for it: cf Apache's history
- with FIN_WAIT_2).
- """
- request_line = self.rfile.readline()
- if not request_line:
- # Force self.ready = False so the connection will close.
- self.ready = False
- return
-
- if request_line == RN:
- # RFC 2616 sec 4.1: "...if the server is reading the protocol
- # stream at the beginning of a message and receives a CRLF
- # first, it should ignore the CRLF."
- # But only ignore one leading line! else we enable a DoS.
- request_line = self.rfile.readline()
- if not request_line:
- self.ready = False
- return
-
- server = self.server
- environ = self.environ
- environ["SERVER_SOFTWARE"] = "%s WSGI Server" % server.version
-
- method, path, req_protocol = request_line.strip().split(" ", 2)
- environ["REQUEST_METHOD"] = method
-
- # path may be an abs_path (including "http://host.domain.tld");
- scheme, location, path, params, qs, frag = urlparse(path)
-
- if frag:
- self.simple_response("400 Bad Request",
- "Illegal #fragment in Request-URI.")
- return
-
- if scheme:
- environ["wsgi.url_scheme"] = scheme
- if params:
- path = path + ";" + params
-
- # Unquote the path+params (e.g. "/this%20path" -> "this path").
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
- #
- # But note that "...a URI must be separated into its components
- # before the escaped characters within those components can be
- # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
- atoms = [unquote(x) for x in QUOTED_SLASH.split(path)]
- path = "%2F".join(atoms)
-
- environ["SCRIPT_NAME"] = ""
- environ["PATH_INFO"] = path
- ##if path == "*":
- ## # This means, of course, that the last wsgi_app (shortest path)
- ## # will always handle a URI of "*".
- ## environ["SCRIPT_NAME"] = ""
- ## environ["PATH_INFO"] = "*"
- ## self.wsgi_app = server.mount_points[-1][1]
- ##else:
- ## for mount_point, wsgi_app in server.mount_points:
- ## # The mount_points list should be sorted by length, descending.
- ## if path.startswith(mount_point + "/") or path == mount_point:
- ## environ["SCRIPT_NAME"] = mount_point
- ## environ["PATH_INFO"] = path[len(mount_point):]
- ## self.wsgi_app = wsgi_app
- ## break
- ## else:
- ## self.simple_response("404 Not Found")
- ## return
-
- # Note that, like wsgiref and most other WSGI servers,
- # we unquote the path but not the query string.
- environ["QUERY_STRING"] = qs
-
- # Compare request and server HTTP protocol versions, in case our
- # server does not support the requested protocol. Limit our output
- # to min(req, server). We want the following output:
- # request server actual written supported response
- # protocol protocol response protocol feature set
- # a 1.0 1.0 1.0 1.0
- # b 1.0 1.1 1.1 1.0
- # c 1.1 1.0 1.0 1.0
- # d 1.1 1.1 1.1 1.1
- # Notice that, in (b), the response will be "HTTP/1.1" even though
- # the client only understands 1.0. RFC 2616 10.5.6 says we should
- # only return 505 if the _major_ version is different.
- rp = int(req_protocol[5]), int(req_protocol[7])
- sp = int(server.protocol[5]), int(server.protocol[7])
- if sp[0] != rp[0]:
- self.simple_response("505 HTTP Version Not Supported")
- return
- # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol.
- environ["SERVER_PROTOCOL"] = req_protocol
- # set a non-standard environ entry so the WSGI app can know what
- # the *real* server protocol is (and what features to support).
- # See http://www.faqs.org/rfcs/rfc2145.html.
- environ["ACTUAL_SERVER_PROTOCOL"] = server.protocol
- self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
-
- # If the Request-URI was an absoluteURI, use its location atom.
- if location:
- environ["SERVER_NAME"] = location
-
- # then all the http headers
- try:
- self.read_headers()
- except ValueError, ex:
- self.simple_response("400 Bad Request", repr(ex.args))
- return
-
- creds = environ.get("HTTP_AUTHORIZATION", "").split(" ", 1)
- environ["AUTH_TYPE"] = creds[0]
- if creds[0].lower() == 'basic':
- user, pw = base64.decodestring(creds[1]).split(":", 1)
- environ["REMOTE_USER"] = user
-
- # Persistent connection support
- if self.response_protocol == "HTTP/1.1":
- if environ.get("HTTP_CONNECTION", "") == "close":
- self.close_connection = True
- else:
- # HTTP/1.0
- if environ.get("HTTP_CONNECTION", "") != "Keep-Alive":
- self.close_connection = True
-
- # Transfer-Encoding support
- te = None
- if self.response_protocol == "HTTP/1.1":
- te = environ.get("HTTP_TRANSFER_ENCODING")
- if te:
- te = [x.strip().lower() for x in te.split(",") if x.strip()]
-
- read_chunked = False
-
- if te:
- for enc in te:
- if enc == "chunked":
- read_chunked = True
- else:
- # Note that, even if we see "chunked", we must reject
- # if there is an extension we don't recognize.
- self.simple_response("501 Unimplemented")
- self.close_connection = True
- return
-
- self.read_chunked = read_chunked
- return
-
- if read_chunked:
- if not self.decode_chunked():
- return
-
- # From PEP 333:
- # "Servers and gateways that implement HTTP 1.1 must provide
- # transparent support for HTTP 1.1's "expect/continue" mechanism.
- # This may be done in any of several ways:
- # 1. Respond to requests containing an Expect: 100-continue request
- # with an immediate "100 Continue" response, and proceed normally.
- # 2. Proceed with the request normally, but provide the application
- # with a wsgi.input stream that will send the "100 Continue"
- # response if/when the application first attempts to read from
- # the input stream. The read request must then remain blocked
- # until the client responds.
- # 3. Wait until the client decides that the server does not support
- # expect/continue, and sends the request body on its own.
- # (This is suboptimal, and is not recommended.)
- #
- # We used to do 3, but are now doing 1. Maybe we'll do 2 someday,
- # but it seems like it would be a big slowdown for such a rare case.
- if environ.get("HTTP_EXPECT", "") == "100-continue":
- self.simple_response(100)
-
- self.ready = True
-
- def new_header(self,k,v):
- if k:
- environ = self.environ
- envname = 'HTTP_'+k
- if k in COMMA_SEPARATED_HEADERS:
- existing = environ.get(envname)
- if existing:
- v = ", ".join((existing, v))
- environ[envname] = v
-
- def read_headers(self):
- """Read header lines from the incoming stream."""
- k = v = None
- while True:
- line = self.rfile.readline()
- if not line or '\0' in line:
- # No more data--illegal end of headers
- raise ValueError("Illegal headers")
- elif line == RN:
- # Normal end of headers
- self.new_header(k,v)
- break
- elif line[0] in ' \t':
- v += line.strip()
- else:
- self.new_header(k,v)
- k, v = line.split(":", 1)
- k, v = k.strip().upper(), v.strip()
-
- environ = self.environ
- ct = environ.pop("HTTP_CONTENT_TYPE", None)
- if ct:
- environ["CONTENT_TYPE"] = ct
- cl = environ.pop("HTTP_CONTENT_LENGTH", None)
- if cl:
- environ["CONTENT_LENGTH"] = cl
-
- def decode_chunked(self):
- """Decode the 'chunked' transfer coding."""
- cl = 0
- data = StringIO.StringIO()
- while True:
- line = self.rfile.readline().strip().split(";", 1)
- chunk_size = int(line.pop(0), 16)
- if chunk_size <= 0:
- break
-## if line: chunk_extension = line[0]
- cl += chunk_size
- data.write(self.rfile.read(chunk_size))
- crlf = self.rfile.read(2)
- if crlf != RN:
- self.simple_response("400 Bad Request",
- "Bad chunked transfer coding "
- "(expected '\\r\\n', got %r)" % crlf)
- return
-
- # Grab any trailer headers
- self.read_headers()
-
- data.seek(0)
- self.environ["wsgi.input"] = data
- self.environ["CONTENT_LENGTH"] = str(cl) or ""
- return True
-
- def respond(self):
- """
- Call the appropriate WSGI app and write its iterable output.
- """
- response = self.server.wsgi_app(self.environ, self.start_response)
- try:
- for chunk in response:
- # "The start_response callable must not actually transmit
- # the response headers. Instead, it must store them for the
- # server or gateway to transmit only after the first
- # iteration of the application return value that yields
- # a NON-EMPTY string, or upon the application's first
- # invocation of the write() callable." (PEP 333)
- if chunk:
- print 'sending',chunk
- self.write(chunk)
- print 'sent!'
- finally:
- if hasattr(response, "close"):
- response.close()
-
- if not self.sent_headers:
- self.send_headers()
- if self.chunked_write:
- self.push("0\r\n")
-
- def simple_response(self, status, msg=""):
- """Write a simple response back to the client."""
- status = str(status)
- buf = ["%s %s\r\n" % (self.server.protocol, status),
- "Content-Length: %s\r\n" % len(msg)]
-
- if status[:3] == "413" and self.response_protocol == 'HTTP/1.1':
- # Request Entity Too Large
- self.close_connection = True
- buf.append("Connection: close\r\n")
-
- buf.append(RN)
- if msg:
- buf.append(msg)
- self.push(''.join(buf))
-
- if self.close_connection or ( not self.server.keep_going ):
- self.close_when_done()
- else:
- self.reset_when_done()
-
- def start_response(self, status, headers, exc_info = None):
- """WSGI callable to begin the HTTP response."""
- if self.started_response:
- if not exc_info:
- raise AssertionError("WSGI start_response called a second "
- "time with no exc_info.")
- else:
- try:
- raise exc_info[0], exc_info[1], exc_info[2]
- finally:
- exc_info = None
- self.started_response = True
- self.status = status
- self.outheaders.extend(headers)
- return self.write
-
- def write(self, chunk):
- """
- WSGI callable to write unbuffered data to the client.
-
- This method is also used internally by start_response (to write
- data from the iterable returned by the WSGI application).
- """
- if not self.started_response:
- raise AssertionError("WSGI write called before start_response.")
-
- if not self.sent_headers:
- self.send_headers()
-
- if self.chunked_write and chunk:
- buf = [hex(len(chunk))[2:], RN, chunk, RN]
- self.push(''.join(buf))
- else:
- self.push(chunk)
-
- def send_headers(self):
- """
- Assert, process, and send the HTTP response message-headers.
- """
- self.sent_headers = True
- hkeys = [key.lower() for key, value in self.outheaders]
- status = int(self.status[:3])
-
- if status == 413:
- # Request Entity Too Large. Close conn to avoid garbage.
- self.close_connection = True
- elif "content-length" not in hkeys:
- # "All 1xx (informational), 204 (no content),
- # and 304 (not modified) responses MUST NOT
- # include a message-body." So no point chunking.
- if status < 200 or status in (204, 205, 304):
- pass
- else:
- if self.response_protocol == 'HTTP/1.1':
- # Use the chunked transfer-coding
- self.chunked_write = True
- self.outheaders.append(("Transfer-Encoding", "chunked"))
- else:
- # Closing the conn is the only way to determine len.
- self.close_connection = True
-
- if "connection" not in hkeys:
- if self.response_protocol == 'HTTP/1.1':
- if self.close_connection:
- self.outheaders.append(("Connection", "close"))
- else:
- if not self.close_connection:
- self.outheaders.append(("Connection", "Keep-Alive"))
-
- if "date" not in hkeys:
- self.outheaders.append(("Date", rfc822.formatdate()))
-
- server = self.server
-
- if "server" not in hkeys:
- self.outheaders.append(("Server", server.version))
-
- buf = [server.protocol, " ", self.status, RN]
- try:
- buf += [k + ": " + v + RN for k, v in self.outheaders]
- except TypeError:
- if not isinstance(k, str):
- raise TypeError("WSGI response header key %r is not a string.")
- if not isinstance(v, str):
- raise TypeError("WSGI response header value %r is not a string.")
- else:
- raise
- buf.append(RN)
- self.push(''.join(buf))
-
-class WSGIServer(asyncore.dispatcher):
- version = "Asynwsgi Server/1.0 alpha"
- protocol = "HTTP/1.1"
- def __init__(self, bind_addr, wsgi_app, server_name=None):
- asyncore.dispatcher.__init__ (self)
- self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
- self.set_reuse_addr()
- self.bind(bind_addr)
- self.listen(1024)
- self.keep_going = True
- self.wsgi_app = wsgi_app
-
- if not server_name:
- server_name = socket.gethostname()
- self.server_name = server_name
-
- def writable(self):
- return False
-
- def readable(self):
- return self.accepting
-
- def handle_accept(self):
- try:
- conn, addr = self.accept()
- except:
- return
- RequestHandler(conn, self)
-
- def reload(self, signum, frame):
- print 'reloading...'
- self.keep_going = False
- self.close()
-
- def stop(self, signum, frame):
- asyncore.close_all()
-
- def start(self):
- signal.signal(signal.SIGTERM, self.stop)
- signal.signal(signal.SIGINT, self.stop)
- asyncore.loop(timeout=0.1, use_poll=False)
-
-def demo_app(environ, start_response):
- write = start_response('200 OK', [('content-type', 'text/html')])
- return ["
Hello World!
%s"%time.ctime()]
-
-def test_client(address='127.0.0.1:8000'):
- time.sleep(3)
- server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- ip,port = address.split(':',1)
- server.connect((ip,int(port)))
- server.send('GET /\r\n\r\n')
- while True:
- sys.stdout.write(server.recv(1))
- sys.stdout.write('CLOSE\n')
-
-if __name__ == "__main__":
- #thread.start_new_thread(test_client,())
- #thread.start_new_thread(test_client,())
- #thread.start_new_thread(test_client,())
- WSGIServer(('127.0.0.1', 8000), demo_app).start()
diff --git a/temps/asynwsgi2.py b/temps/asynwsgi2.py
deleted file mode 100644
index 91040265..00000000
--- a/temps/asynwsgi2.py
+++ /dev/null
@@ -1,630 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
-docstring
-"""
-
-__author__ = "Gu Yingbo (tensiongyb@gmail.com)"
-
-import os
-import re
-import sys
-import time
-import base64
-import asyncore
-import asynchat
-import rfc822
-import socket
-import thread
-import signal
-from traceback import format_exc
-from urllib import unquote, splitquery
-from urlparse import urlparse
-try:
- import cStringIO as StringIO
-except ImportError:
- import StringIO
-
-RN = '\r\n'
-RNRN = '\r\n\r\n'
-QUOTED_SLASH = re.compile("(?i)%2F")
-
-COMMA_SEPARATED_HEADERS = ['ACCEPT', 'ACCEPT-CHARSET', 'ACCEPT-ENCODING',
- 'ACCEPT-LANGUAGE', 'ACCEPT-RANGES', 'ALLOW', 'CACHE-CONTROL',
- 'CONNECTION', 'CONTENT-ENCODING', 'CONTENT-LANGUAGE', 'EXPECT',
- 'IF-MATCH', 'IF-NONE-MATCH', 'PRAGMA', 'PROXY-AUTHENTICATE', 'TE',
- 'TRAILER', 'TRANSFER-ENCODING', 'UPGRADE', 'VARY', 'VIA', 'WARNING',
- 'WWW-AUTHENTICATE']
-
-class RequestHandler(asynchat.async_chat):
-
- def __init__(self, sock, server):
- asynchat.async_chat.__init__(self, sock)
- self.server = server
- self.reset()
-
- def reset(self):
- self.ac_in_buffer = ''
- self.ac_out_buffer = ''
- self.producer_fifo = asynchat.fifo()
- self.set_terminator(RNRN)
- self.process = "reading headers"
- self.started_response = False
- self.status = ""
- self.outheaders = []
- self.sent_headers = False
- self.close_connection = False
- self.chunked_write = False
-
- self.rfile = StringIO.StringIO()
-
- self.environ = {
- "wsgi.version": (1, 0),
- "wsgi.url_scheme": "http",
- "wsgi.multithread": False,
- "wsgi.multiprocess": False,
- "wsgi.run_once": False,
- "wsgi.errors": sys.stderr,
- }
-
- def collect_incoming_data(self, data):
- """Buffer the data"""
- if self.process == "reading chunked":
- self.line_buffer += data
- else:
- self.rfile.write(data)
-
- def found_terminator(self):
- if self.process == "reading headers":
- self.rfile.write(RNRN)
- self.rfile.seek(0)
- try:
- self.parse_request()
- except:
- self.simple_response("500 Internal Server Error", format_exc())
- return
-
- if not self.read_chunked:
- if self.environ["REQUEST_METHOD"].upper() == "POST":
- cl = int(self.environ["CONTENT_LENGTH"])
- self.rfile = StringIO.StringIO()
- self.environ["wsgi.input"] = self.rfile
- self.process = "dealing post"
- self.set_terminator(cl)
- else:
- return self.wsgi_response()
- else:
- self.rfile = StringIO.StringIO()
- self.environ["wsgi.input"] = self.rfile
- self.process = "reading chunked"
- self.set_terminator(RN)
- self.line_buffer = ""
- self.cl = 0
- elif self.process == "dealing post":
- self.rfile.seek(0)
- return self.wsgi_response()
- elif self.process == "reading chunked":
- line = self.line_buffer.split(";", 1)
- chunk_size = int(line.pop(0), 16)
- if chunk_size <= 0:
- self.process = "reading tail headers"
- self.set_terminator(RNRN)
- else:
- self.cl += chunk_size
- self.process = "get chunked"
- self.set_terminator(self.cl+2) # browsers sometimes over-send
- elif self.process == "get chunked":
- crlf = self.rfile.getvalue()[-2:]
- if not crlf != RN:
- self.simple_response("400 Bad Request",
- "Bad chunked transfer coding "
- "(expected '\\r\\n', got %r)" % crlf)
- return
- else:
- self.process = "reading chunked"
- self.set_terminator(RN)
- self.rfile.seek(-2, 2)
- self.rfile.truncate()
- elif self.process == "reading tail headers":
- self.read_headers()
- self.rfile.seek(0)
- self.environ["CONTENT_LENGTH"] = str(self.cl) or ""
- return self.wsgi_response()
-
- def reset_when_done(self):
- self.producer_fifo.push(0)
-
- def refill_buffer (self):
- while 1:
- if len(self.producer_fifo):
- p = self.producer_fifo.first()
- # a 'None' in the producer fifo is a sentinel,
- # telling us to close the channel.
- if p == 0:
- if not self.ac_out_buffer:
- self.producer_fifo.pop()
- self.reset()
- return
- elif p is None:
- if not self.ac_out_buffer:
- self.producer_fifo.pop()
- self.close()
- return
- elif isinstance(p, str):
- self.producer_fifo.pop()
- self.ac_out_buffer = self.ac_out_buffer + p
- return
- data = p.more()
- if data:
- self.ac_out_buffer = self.ac_out_buffer + data
- return
- else:
- self.producer_fifo.pop()
- else:
- return
-
- def wsgi_response(self):
- try:
- self.respond()
- except:
- self.simple_response("500 Internal Server Error", format_exc())
- else:
- if self.close_connection or ( not self.server.keep_going ):
- self.close_when_done()
- else:
- self.reset_when_done()
-
- def readable(self):
- return self.server.keep_going and not self.writable() and \
- asynchat.async_chat.readable(self)
-
- def handle_expt(self):
- asynchat.async_chat.handle_expt(self)
- self.close()
-
- def handle_error(self):
- asynchat.async_chat.handle_error(self)
- self.close()
-
- def parse_request(self):
- """
- Parse the next HTTP request start-line and message-headers.
- HTTP/1.1 connections are persistent by default. If a client
- requests a page, then idles (leaves the connection open),
- then rfile.readline() will raise socket.error("timed out").
- Note that it does this based on the value given to settimeout(),
- and doesn't need the client to request or acknowledge the close
- (although your TCP stack might suffer for it: cf Apache's history
- with FIN_WAIT_2).
- """
- request_line = self.rfile.readline()
- if not request_line:
- # Force self.ready = False so the connection will close.
- self.ready = False
- return
-
- if request_line == RN:
- # RFC 2616 sec 4.1: "...if the server is reading the protocol
- # stream at the beginning of a message and receives a CRLF
- # first, it should ignore the CRLF."
- # But only ignore one leading line! else we enable a DoS.
- request_line = self.rfile.readline()
- if not request_line:
- self.ready = False
- return
-
- server = self.server
- environ = self.environ
- environ["SERVER_SOFTWARE"] = "%s WSGI Server" % server.version
-
- method, path, req_protocol = request_line.strip().split(" ", 2)
- environ["REQUEST_METHOD"] = method
-
- # path may be an abs_path (including "http://host.domain.tld");
- scheme, location, path, params, qs, frag = urlparse(path)
-
- if frag:
- self.simple_response("400 Bad Request",
- "Illegal #fragment in Request-URI.")
- return
-
- if scheme:
- environ["wsgi.url_scheme"] = scheme
- if params:
- path = path + ";" + params
-
- # Unquote the path+params (e.g. "/this%20path" -> "this path").
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
- #
- # But note that "...a URI must be separated into its components
- # before the escaped characters within those components can be
- # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
- atoms = [unquote(x) for x in QUOTED_SLASH.split(path)]
- path = "%2F".join(atoms)
-
- environ["SCRIPT_NAME"] = ""
- environ["PATH_INFO"] = path
- ##if path == "*":
- ## # This means, of course, that the last wsgi_app (shortest path)
- ## # will always handle a URI of "*".
- ## environ["SCRIPT_NAME"] = ""
- ## environ["PATH_INFO"] = "*"
- ## self.wsgi_app = server.mount_points[-1][1]
- ##else:
- ## for mount_point, wsgi_app in server.mount_points:
- ## # The mount_points list should be sorted by length, descending.
- ## if path.startswith(mount_point + "/") or path == mount_point:
- ## environ["SCRIPT_NAME"] = mount_point
- ## environ["PATH_INFO"] = path[len(mount_point):]
- ## self.wsgi_app = wsgi_app
- ## break
- ## else:
- ## self.simple_response("404 Not Found")
- ## return
-
- # Note that, like wsgiref and most other WSGI servers,
- # we unquote the path but not the query string.
- environ["QUERY_STRING"] = qs
-
- # Compare request and server HTTP protocol versions, in case our
- # server does not support the requested protocol. Limit our output
- # to min(req, server). We want the following output:
- # request server actual written supported response
- # protocol protocol response protocol feature set
- # a 1.0 1.0 1.0 1.0
- # b 1.0 1.1 1.1 1.0
- # c 1.1 1.0 1.0 1.0
- # d 1.1 1.1 1.1 1.1
- # Notice that, in (b), the response will be "HTTP/1.1" even though
- # the client only understands 1.0. RFC 2616 10.5.6 says we should
- # only return 505 if the _major_ version is different.
- rp = int(req_protocol[5]), int(req_protocol[7])
- sp = int(server.protocol[5]), int(server.protocol[7])
- if sp[0] != rp[0]:
- self.simple_response("505 HTTP Version Not Supported")
- return
- # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol.
- environ["SERVER_PROTOCOL"] = req_protocol
- # set a non-standard environ entry so the WSGI app can know what
- # the *real* server protocol is (and what features to support).
- # See http://www.faqs.org/rfcs/rfc2145.html.
- environ["ACTUAL_SERVER_PROTOCOL"] = server.protocol
- self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
-
- # If the Request-URI was an absoluteURI, use its location atom.
- if location:
- environ["SERVER_NAME"] = location
-
- # then all the http headers
- try:
- self.read_headers()
- except ValueError, ex:
- self.simple_response("400 Bad Request", repr(ex.args))
- return
-
- creds = environ.get("HTTP_AUTHORIZATION", "").split(" ", 1)
- environ["AUTH_TYPE"] = creds[0]
- if creds[0].lower() == 'basic':
- user, pw = base64.decodestring(creds[1]).split(":", 1)
- environ["REMOTE_USER"] = user
-
- # Persistent connection support
- if self.response_protocol == "HTTP/1.1":
- if environ.get("HTTP_CONNECTION", "") == "close":
- self.close_connection = True
- else:
- # HTTP/1.0
- if environ.get("HTTP_CONNECTION", "") != "Keep-Alive":
- self.close_connection = True
-
- # Transfer-Encoding support
- te = None
- if self.response_protocol == "HTTP/1.1":
- te = environ.get("HTTP_TRANSFER_ENCODING")
- if te:
- te = [x.strip().lower() for x in te.split(",") if x.strip()]
-
- read_chunked = False
-
- if te:
- for enc in te:
- if enc == "chunked":
- read_chunked = True
- else:
- # Note that, even if we see "chunked", we must reject
- # if there is an extension we don't recognize.
- self.simple_response("501 Unimplemented")
- self.close_connection = True
- return
-
- self.read_chunked = read_chunked
- return
-
- if read_chunked:
- if not self.decode_chunked():
- return
-
- # From PEP 333:
- # "Servers and gateways that implement HTTP 1.1 must provide
- # transparent support for HTTP 1.1's "expect/continue" mechanism.
- # This may be done in any of several ways:
- # 1. Respond to requests containing an Expect: 100-continue request
- # with an immediate "100 Continue" response, and proceed normally.
- # 2. Proceed with the request normally, but provide the application
- # with a wsgi.input stream that will send the "100 Continue"
- # response if/when the application first attempts to read from
- # the input stream. The read request must then remain blocked
- # until the client responds.
- # 3. Wait until the client decides that the server does not support
- # expect/continue, and sends the request body on its own.
- # (This is suboptimal, and is not recommended.)
- #
- # We used to do 3, but are now doing 1. Maybe we'll do 2 someday,
- # but it seems like it would be a big slowdown for such a rare case.
- if environ.get("HTTP_EXPECT", "") == "100-continue":
- self.simple_response(100)
-
- self.ready = True
-
- def new_header(self,k,v):
- if k:
- environ = self.environ
- envname = 'HTTP_'+k
- if k in COMMA_SEPARATED_HEADERS:
- existing = environ.get(envname)
- if existing:
- v = ", ".join((existing, v))
- environ[envname] = v
-
- def read_headers(self):
- """Read header lines from the incoming stream."""
- k = v = None
- while True:
- line = self.rfile.readline()
- if not line or '\0' in line:
- # No more data--illegal end of headers
- raise ValueError("Illegal headers")
- elif line == RN:
- # Normal end of headers
- self.new_header(k,v)
- break
- elif line[0] in ' \t':
- v += line.strip()
- else:
- self.new_header(k,v)
- k, v = line.split(":", 1)
- k, v = k.strip().upper(), v.strip()
-
- environ = self.environ
- ct = environ.pop("HTTP_CONTENT_TYPE", None)
- if ct:
- environ["CONTENT_TYPE"] = ct
- cl = environ.pop("HTTP_CONTENT_LENGTH", None)
- if cl:
- environ["CONTENT_LENGTH"] = cl
-
- def decode_chunked(self):
- """Decode the 'chunked' transfer coding."""
- cl = 0
- data = StringIO.StringIO()
- while True:
- line = self.rfile.readline().strip().split(";", 1)
- chunk_size = int(line.pop(0), 16)
- if chunk_size <= 0:
- break
-## if line: chunk_extension = line[0]
- cl += chunk_size
- data.write(self.rfile.read(chunk_size))
- crlf = self.rfile.read(2)
- if crlf != RN:
- self.simple_response("400 Bad Request",
- "Bad chunked transfer coding "
- "(expected '\\r\\n', got %r)" % crlf)
- return
-
- # Grab any trailer headers
- self.read_headers()
-
- data.seek(0)
- self.environ["wsgi.input"] = data
- self.environ["CONTENT_LENGTH"] = str(cl) or ""
- return True
-
- def respond(self):
- """
- Call the appropriate WSGI app and write its iterable output.
- """
- response = self.server.wsgi_app(self.environ, self.start_response)
- try:
- for chunk in response:
- # "The start_response callable must not actually transmit
- # the response headers. Instead, it must store them for the
- # server or gateway to transmit only after the first
- # iteration of the application return value that yields
- # a NON-EMPTY string, or upon the application's first
- # invocation of the write() callable." (PEP 333)
- if chunk:
- self.write(chunk)
- finally:
- if hasattr(response, "close"):
- response.close()
-
- if not self.sent_headers:
- self.send_headers()
- if self.chunked_write:
- self.push("0\r\n")
-
- def simple_response(self, status, msg=""):
- """Write a simple response back to the client."""
- status = str(status)
- buf = ["%s %s\r\n" % (self.server.protocol, status),
- "Content-Length: %s\r\n" % len(msg)]
-
- if status[:3] == "413" and self.response_protocol == 'HTTP/1.1':
- # Request Entity Too Large
- self.close_connection = True
- buf.append("Connection: close\r\n")
-
- buf.append(RN)
- if msg:
- buf.append(msg)
- self.push(''.join(buf))
-
- if self.close_connection or ( not self.server.keep_going ):
- self.close_when_done()
- else:
- self.reset_when_done()
-
- def start_response(self, status, headers, exc_info = None):
- """WSGI callable to begin the HTTP response."""
- if self.started_response:
- if not exc_info:
- raise AssertionError("WSGI start_response called a second "
- "time with no exc_info.")
- else:
- try:
- raise exc_info[0], exc_info[1], exc_info[2]
- finally:
- exc_info = None
- self.started_response = True
- self.status = status
- self.outheaders.extend(headers)
- return self.write
-
- def write(self, chunk):
- """
- WSGI callable to write unbuffered data to the client.
-
- This method is also used internally by start_response (to write
- data from the iterable returned by the WSGI application).
- """
- if not self.started_response:
- raise AssertionError("WSGI write called before start_response.")
-
- if not self.sent_headers:
- self.send_headers()
-
- if self.chunked_write and chunk:
- buf = [hex(len(chunk))[2:], RN, chunk, RN]
- self.push(''.join(buf))
- else:
- self.push(chunk)
-
- def send_headers(self):
- """
- Assert, process, and send the HTTP response message-headers.
- """
- self.sent_headers = True
- hkeys = [key.lower() for key, value in self.outheaders]
- status = int(self.status[:3])
-
- if status == 413:
- # Request Entity Too Large. Close conn to avoid garbage.
- self.close_connection = True
- elif "content-length" not in hkeys:
- # "All 1xx (informational), 204 (no content),
- # and 304 (not modified) responses MUST NOT
- # include a message-body." So no point chunking.
- if status < 200 or status in (204, 205, 304):
- pass
- else:
- if self.response_protocol == 'HTTP/1.1':
- # Use the chunked transfer-coding
- self.chunked_write = True
- self.outheaders.append(("Transfer-Encoding", "chunked"))
- else:
- # Closing the conn is the only way to determine len.
- self.close_connection = True
-
- if "connection" not in hkeys:
- if self.response_protocol == 'HTTP/1.1':
- if self.close_connection:
- self.outheaders.append(("Connection", "close"))
- else:
- if not self.close_connection:
- self.outheaders.append(("Connection", "Keep-Alive"))
-
- if "date" not in hkeys:
- self.outheaders.append(("Date", rfc822.formatdate()))
-
- server = self.server
-
- if "server" not in hkeys:
- self.outheaders.append(("Server", server.version))
-
- buf = [server.protocol, " ", self.status, RN]
- try:
- buf += [k + ": " + v + RN for k, v in self.outheaders]
- except TypeError:
- if not isinstance(k, str):
- raise TypeError("WSGI response header key %r is not a string.")
- if not isinstance(v, str):
- raise TypeError("WSGI response header value %r is not a string.")
- else:
- raise
- buf.append(RN)
- self.push(''.join(buf))
-
-
-class WSGIServer(asyncore.dispatcher):
- version = "Asynwsgi Server/1.0 alpha"
- protocol = "HTTP/1.1"
- def __init__(self, bind_addr, wsgi_app, server_name=None):
- asyncore.dispatcher.__init__ (self)
- self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
- self.set_reuse_addr()
- self.bind(bind_addr)
- self.listen(1024)
- self.keep_going = True
- self.wsgi_app = wsgi_app
-
- if not server_name:
- server_name = socket.gethostname()
- self.server_name = server_name
-
- def writable(self):
- return False
-
- def readable(self):
- return self.accepting
-
- def handle_accept(self):
- try:
- conn, addr = self.accept()
- except:
- return
- RequestHandler(conn, self)
-
- def reload(self, signum, frame):
- print 'reloading...'
- self.keep_going = False
- self.close()
-
- def stop(self, signum, frame):
- asyncore.close_all()
-
- def start(self):
- signal.signal(signal.SIGTERM, self.stop)
- signal.signal(signal.SIGINT, self.stop)
- asyncore.loop(timeout=0.1, use_poll=False)
-
-def demo_app(environ, start_response):
- write = start_response('200 OK', [('content-type', 'text/html')])
- return ["