Index: /trunk/trac/web/_fcgi.py
===================================================================
--- /trunk/trac/web/_fcgi.py (revision 17540)
+++ /trunk/trac/web/_fcgi.py (revision 17541)
@@ -40,26 +40,16 @@
__version__ = '$Revision: 2025 $'
+import _thread
+import codecs
+import errno
import io
+import os
+import select
+import signal
+import socket
+import struct
import sys
-import os
-import signal
-import struct
-import select
-import socket
-import errno
+import threading
import traceback
-
-try:
- import thread
- import threading
- thread_available = True
-except ImportError:
- import dummy_thread as thread
- import dummy_threading as threading
- thread_available = False
-
-# Apparently 2.3 doesn't define SHUT_WR? Assume it is 1 in this case.
-if not hasattr(socket, 'SHUT_WR'):
- socket.SHUT_WR = 1
__all__ = ['WSGIServer']
@@ -138,5 +128,5 @@
self._shrinkThreshold = conn.server.inputStreamShrinkThreshold
- self._buf = ''
+ self._buf = b''
self._bufList = []
self._pos = 0 # Current read position.
@@ -160,5 +150,5 @@
def read(self, n=-1):
if self._pos == self._avail and self._eof:
- return ''
+ return b''
while True:
if n < 0 or (self._avail - self._pos) < n:
@@ -177,5 +167,5 @@
# Merge buffer list, if necessary.
if self._bufList:
- self._buf += ''.join(self._bufList)
+ self._buf += _bytes_join(self._bufList)
self._bufList = []
r = self._buf[self._pos:newPos]
@@ -186,9 +176,9 @@
def readline(self, length=None):
if self._pos == self._avail and self._eof:
- return ''
+ return b''
while True:
# Unfortunately, we need to merge the buffer list early.
if self._bufList:
- self._buf += ''.join(self._bufList)
+ self._buf += _bytes_join(self._bufList)
self._bufList = []
# Find newline.
@@ -317,4 +307,5 @@
def write(self, data):
assert not self.closed
+ assert type(data) is bytes
if not data:
@@ -337,5 +328,5 @@
# Only need to flush if this OutputStream is actually buffered.
if self._buffered:
- data = ''.join(self._bufList)
+ data = _bytes_join(self._bufList)
self._bufList = []
self._write(data)
@@ -397,22 +388,22 @@
are returned.
"""
- nameLength = ord(s[pos])
- if nameLength & 128:
- nameLength = struct.unpack('!L', s[pos:pos+4])[0] & 0x7fffffff
+ namelen = s[pos]
+ if namelen & 128:
+ namelen = struct.unpack('!L', s[pos:pos+4])[0] & 0x7fffffff
pos += 4
else:
pos += 1
- valueLength = ord(s[pos])
- if valueLength & 128:
- valueLength = struct.unpack('!L', s[pos:pos+4])[0] & 0x7fffffff
+ valuelen = s[pos]
+ if valuelen & 128:
+ valuelen = struct.unpack('!L', s[pos:pos+4])[0] & 0x7fffffff
pos += 4
else:
pos += 1
- name = s[pos:pos+nameLength]
- pos += nameLength
- value = s[pos:pos+valueLength]
- pos += valueLength
+ name = str(s[pos:pos+namelen], 'iso-8859-1')
+ pos += namelen
+ value = str(s[pos:pos+valuelen], 'iso-8859-1')
+ pos += valuelen
return pos, (name, value)
@@ -424,17 +415,25 @@
The encoded string is returned.
"""
- nameLength = len(name)
- if nameLength < 128:
- s = chr(nameLength)
+ name = name.encode('iso-8859-1')
+ value = value.encode('iso-8859-1')
+
+ namelen = len(name)
+ if namelen < 128:
+ namelen = _code2bytes(namelen)
else:
- s = struct.pack('!L', nameLength | 0x80000000)
-
- valueLength = len(value)
- if valueLength < 128:
- s += chr(valueLength)
+ namelen = struct.pack('!L', namelen | 0x80000000)
+
+ valuelen = len(value)
+ if valuelen < 128:
+ valuelen = _code2bytes(valuelen)
else:
- s += struct.pack('!L', valueLength | 0x80000000)
-
- return s + name + value
+ valuelen = struct.pack('!L', valuelen | 0x80000000)
+
+ return namelen + valuelen + name + value
+
+_bytes_join = b''.join
+_str_join = ''.join
+_code2bytes = lambda code: b'%c' % code
+_utf8_writer = lambda f: codecs.getwriter('utf-8')(f)
class Record(object):
@@ -450,5 +449,5 @@
self.contentLength = 0
self.paddingLength = 0
- self.contentData = ''
+ self.contentData = b''
@staticmethod
@@ -464,5 +463,5 @@
data = sock.recv(length)
except socket.error as e:
- if e[0] == errno.EAGAIN:
+ if e.errno == errno.EAGAIN:
select.select([sock], [], [])
continue
@@ -475,5 +474,5 @@
recvLen += dataLen
length -= dataLen
- return ''.join(dataList), recvLen
+ return _bytes_join(dataList), recvLen
def read(self, sock):
@@ -516,4 +515,5 @@
Writes data to a socket and does not return until all the data is sent.
"""
+ assert type(data) is bytes
length = len(data)
while length:
@@ -521,5 +521,5 @@
sent = sock.send(data)
except socket.error as e:
- if e[0] == errno.EAGAIN:
+ if e.errno == errno.EAGAIN:
select.select([], [sock], [])
continue
@@ -545,5 +545,5 @@
self._sendall(sock, self.contentData)
if self.paddingLength:
- self._sendall(sock, '\x00'*self.paddingLength)
+ self._sendall(sock, b'\x00' * self.paddingLength)
class Request(object):
@@ -563,5 +563,6 @@
self.stdin = inputStreamClass(conn)
self.stdout = OutputStream(conn, self, FCGI_STDOUT)
- self.stderr = OutputStream(conn, self, FCGI_STDERR, buffered=True)
+ self.stderr = _utf8_writer(OutputStream(conn, self, FCGI_STDERR,
+ buffered=True))
self.data = inputStreamClass(conn)
@@ -585,5 +586,5 @@
self._end(appStatus, protocolStatus)
except socket.error as e:
- if e[0] != errno.EPIPE:
+ if e.errno != errno.EPIPE:
raise
@@ -645,6 +646,6 @@
try:
while True:
- r, w, e = select.select([self._sock], [], [])
- if not r or not self._sock.recv(1024):
+ rlist, wlist, xlist = select.select([self._sock], [], [])
+ if not rlist or not self._sock.recv(1024):
break
except:
@@ -660,6 +661,6 @@
except EOFError:
break
- except (select.error, socket.error) as e:
- if e[0] == errno.EBADF: # Socket was closed by Request.
+ except select.error as e:
+ if e.errno == errno.EBADF: # Socket was closed by Request.
break
raise
@@ -675,10 +676,10 @@
while self._keepGoing:
try:
- r, w, e = select.select([self._sock], [], [], 1.0)
+ rlist, wlist, xlist = select.select([self._sock], [], [], 1.0)
except ValueError:
# Sigh. ValueError gets thrown sometimes when passing select
# a closed socket.
raise EOFError
- if r: break
+ if rlist: break
if not self._keepGoing:
return
@@ -876,5 +877,5 @@
def _start_request(self, req):
- thread.start_new_thread(req.run, ())
+ _thread.start_new_thread(req.run, ())
def _do_params(self, inrec):
@@ -948,31 +949,22 @@
self.handler = handler
self.maxwrite = maxwrite
- if thread_available:
- try:
- import resource
- # Attempt to glean the maximum number of connections
- # from the OS.
- maxConns = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
- except (ImportError, AttributeError):
- maxConns = 100 # Just some made up number.
- maxReqs = maxConns
- if multiplexed:
- self._connectionClass = MultiplexedConnection
- maxReqs *= 5 # Another made up number.
- else:
- self._connectionClass = Connection
- self.capability = {
- FCGI_MAX_CONNS: maxConns,
- FCGI_MAX_REQS: maxReqs,
- FCGI_MPXS_CONNS: multiplexed and 1 or 0
- }
+ try:
+ import resource
+ # Attempt to glean the maximum number of connections
+ # from the OS.
+ maxConns = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
+ except (ImportError, AttributeError):
+ maxConns = 100 # Just some made up number.
+ maxReqs = maxConns
+ if multiplexed:
+ self._connectionClass = MultiplexedConnection
+ maxReqs *= 5 # Another made up number.
else:
self._connectionClass = Connection
- self.capability = {
- # If threads aren't available, these are pretty much correct.
- FCGI_MAX_CONNS: 1,
- FCGI_MAX_REQS: 1,
- FCGI_MPXS_CONNS: 0
- }
+ self.capability = {
+ FCGI_MAX_CONNS: maxConns,
+ FCGI_MAX_REQS: maxReqs,
+ FCGI_MPXS_CONNS: 1 if multiplexed else 0,
+ }
self._bindAddress = bindAddress
self._umask = umask
@@ -987,8 +979,8 @@
sock.getpeername()
except socket.error as e:
- if e[0] == errno.ENOTSOCK:
+ if e.errno == errno.ENOTSOCK:
# Not a socket, assume CGI context.
isFCGI = False
- elif e[0] != errno.ENOTCONN:
+ elif e.errno != errno.ENOTCONN:
raise
@@ -1072,15 +1064,15 @@
while self._keepGoing:
try:
- r, w, e = select.select([sock], [], [], timeout)
+ rlist, wlist, xlist = select.select([sock], [], [], timeout)
except select.error as e:
- if e[0] == errno.EINTR:
+ if e.errno == errno.EINTR:
continue
raise
- if r:
+ if rlist:
try:
clientSock, addr = sock.accept()
except socket.error as e:
- if e[0] in (errno.EINTR, errno.EAGAIN):
+ if e.errno in (errno.EINTR, errno.EAGAIN):
continue
raise
@@ -1094,5 +1086,5 @@
# messages (either in a new thread or this thread).
conn = self._connectionClass(clientSock, addr, self)
- thread.start_new_thread(conn.run, ())
+ _thread.start_new_thread(conn.run, ())
self._mainloopPeriodic()
@@ -1134,7 +1126,8 @@
should be overridden.
"""
- import cgitb
- req.stdout.write('Content-Type: text/html\r\n\r\n' +
- cgitb.html(sys.exc_info()))
+ out = _utf8_writer(req.stdout)
+ out.write('Content-Type: text/plain; charset=utf-8\r\n\r\n')
+ traceback.print_exc(file=out)
+ out.flush()
class WSGIServer(Server):
@@ -1164,5 +1157,5 @@
# Used to force single-threadedness
- self._app_lock = thread.allocate_lock()
+ self._app_lock = _thread.allocate_lock()
def handler(self, req):
@@ -1175,5 +1168,5 @@
environ.update(self.environ)
- environ['wsgi.version'] = (1,0)
+ environ['wsgi.version'] = (1, 0)
environ['wsgi.input'] = req.stdin
if self._bindAddress is None:
@@ -1183,5 +1176,5 @@
environ['wsgi.errors'] = stderr
environ['wsgi.multithread'] = not isinstance(req, CGIRequest) and \
- thread_available and self.multithreaded
+ self.multithreaded
# Rationale for the following: If started by the web server
# (self._bindAddress is None) in either FastCGI or CGI mode, the
@@ -1207,26 +1200,15 @@
def write(data):
- assert type(data) is str, 'write() argument must be string'
+ assert type(data) is bytes, 'write() argument must be bytes'
assert headers_set, 'write() before start_response()'
if not headers_sent:
- status, responseHeaders = headers_sent[:] = headers_set
- found = False
- for header,value in responseHeaders:
- if header.lower() == 'content-length':
- found = True
- break
- if not found and result is not None:
- try:
- if len(result) == 1:
- responseHeaders.append(('Content-Length',
- str(len(data))))
- except:
- pass
- s = 'Status: %s\r\n' % status
- for header in responseHeaders:
- s += '%s: %s\r\n' % header
- s += '\r\n'
- req.stdout.write(s)
+ status, headers = headers_sent[:] = headers_set
+ def generator():
+ yield 'Status: %s\r\n' % status
+ for header in headers:
+ yield '%s: %s\r\n' % header
+ yield '\r\n'
+ req.stdout.write(_str_join(generator()).encode('iso-8859-1'))
req.stdout.write(data)
@@ -1266,11 +1248,11 @@
write(data)
if not headers_sent:
- write('') # in case body was empty
+ write(b'') # in case body was empty
finally:
if hasattr(result, 'close'):
result.close()
except socket.error as e:
- if e[0] != errno.EPIPE:
- raise # Don't let EPIPE propagate beyond server
+ if e.errno != errno.EPIPE:
+ raise # Don't let EPIPE propagate beyond server
finally:
if not self.multithreaded:
Index: /trunk/trac/web/fcgi_frontend.py
===================================================================
--- /trunk/trac/web/fcgi_frontend.py (revision 17540)
+++ /trunk/trac/web/fcgi_frontend.py (revision 17541)
@@ -27,5 +27,4 @@
use_flup = False
-
class FlupMiddleware(object):
"""Flup doesn't URL unquote the PATH_INFO, so we need to do it."""
@@ -43,8 +42,9 @@
try:
from flup.server.fcgi import WSGIServer
+ except ImportError:
+ use_flup = False
+ else:
params['maxThreads'] = 15
dispatch_request = FlupMiddleware(dispatch_request)
- except ImportError:
- use_flup = False
if not use_flup:
Index: /trunk/trac/web/templates/deploy_trac.fcgi
===================================================================
--- /trunk/trac/web/templates/deploy_trac.fcgi (revision 17540)
+++ /trunk/trac/web/templates/deploy_trac.fcgi (revision 17541)
@@ -34,14 +34,12 @@
raise
except Exception as e:
- print("Content-Type: text/plain\r\n\r\n", end=' ')
+ print("Content-Type: text/plain", end="\r\n")
+ print("", end="\r\n")
print("Oops...")
- print()
+ print("")
print("Trac detected an internal error:")
- print()
+ print("")
print(e)
- print()
+ print("", flush=True)
import traceback
- import io
- tb = io.Bytes()
- traceback.print_exc(file=tb)
- print(tb.getvalue())
+ traceback.print_exc(file=sys.stdout)