in an attempt to learn wsgiserver better I tried recoding it. This is
what I got so far. It works with the included "hello world" wsgi
application. This is not a priority but if we can fix the remaining
bugs it could open the door to further improvements.
To run it stand alone try:
python web2pyserver.py 8001
and you will have a "hello world" app running on http://127.0.0.1/8001
To use it with web2py place it in the gluon folder, edit gluon/main.py
and replace
self.server = wsgiserver.CherryPyWSGIServer(
with:
import web2pyserver
self.server = web2pyserver.web2pyWSGIServer(
There are some known problems:
1) POST does not work. It should but it does not. I am trying to
figure out why.
2) One in two requests is not served properly.
3) It chokes on more than 10 concurrent requests (and so does
cherrypy) on my PC.
Perhaps you can help me debug this.
Massimo
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"web2py-users" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/web2py?hl=en
-~----------~----~----~----~------~----~------~--~---
import threading
import socket
import logging
import sys
import Queue
import re
import errno
import rfc822
import cStringIO
import signal
import time
import traceback
import copy
BUF_SIZE=10000
SERVER_NAME = 'web2py'
ACTUAL_SERVER_PROTOCOL='HTTP/1.1' # should this be determined from request?
regex_head = re.compile('^((http|https|HTTP|HTTPS)\://[^/]+)?(?P<method>\w+)\s+(?P<uri>\S+)\s+(?P<protocol>\S+)')
regex_header = re.compile('\s*(?P<key>.*?)\s*\:\s*(?P<value>.*?)\s*$')
regex_chunk = re.compile('^(?P<size>\w+)')
class ChunkedReader:
def __init__(self,stream):
self.stream=stream
self.buffer=None
def chunk_read(self):
if not self.buffer or self.buffer.tell()==self.buffer_size:
self.buffer_size=int(regex_chunk.match(self.stream.readline()).group('size'),16)
if self.buffer_size:
self.buffer=cStringIO.StringIO(self.stream.read(self.buffer_size))
def read(self,size):
data=''
while size:
self.chunk_read()
if not self.buffer_size: break
read_size=min(size,self.buffer_size)
data+=self.buffer.read(read_size)
size-=read_size
return data
def readline(self):
data=''
for c in self.read(1):
if not c: break
data+=c
if c=='\n': break
return data
def readlines(self):
yield self.readline()
def errors_numbers(errnames):
return set([getattr(errno, k) for k in errnames if hasattr(errno,k)])
socket_errors_to_ignore = errors_numbers((
"EPIPE",
"EBADF", "WSAEBADF",
"ENOTSOCK", "WSAENOTSOCK",
"ETIMEDOUT", "WSAETIMEDOUT",
"ECONNREFUSED", "WSAECONNREFUSED",
"ECONNRESET", "WSAECONNRESET",
"ECONNABORTED", "WSAECONNABORTED",
"ENETRESET", "WSAENETRESET",
"EHOSTDOWN", "EHOSTUNREACH",
))
class Worker(threading.Thread):
queue=Queue.Queue()
threads=set()
wsgi_apps=[]
server_name=None
min_threads=1
def die(self):
self.threads.remove(self)
return
def run(self):
while True:
(self.client_socket,self.client_address) = self.queue.get()
if not self.client_socket: return self.die()
print 'from',self.client_address
try:
self.wsgi_file=self.client_socket.makefile('rb',BUF_SIZE)
while True:
self.build_environ(self.wsgi_file,self.client_address)
for data in self.wsgi_apps[0](self.environ,self.start_response):
try:
if self.chunked_response:
self.client_socket.sendall('%x\r\n' % len(data))
self.client_socket.sendall(data)
except socket.error, e:
print 'error',e
if e.args[0] not in socket_errors_to_ignore:
raise
if self.chunked_response:
self.client_socket.sendall('0\r\n')
self.wsgi_file.close()
print 'wsgi_file.close()'
if self.connection!='Keep-Alive':
print 'keep alive'
break
print 'end looping'
except Exception, e:
print 'exception',e
logging.error(str(traceback.format_exc()))
self.try_error_response()
print 'client_socket.closing'
self.client_socket.close()
def build_environ(self,input_file,client_address):
first_line=self.wsgi_file.readline()
match = regex_head.match(first_line)
request_method = match.group('method')
uri = match.group('uri')
request_protocol=match.group('protocol')
k = uri.find('?')
if k<0: k = len(uri)
(path_info,query_string)=(uri[:k],uri[k+1:])
self.environ={'wsgi.version':(1,0),
'wsgi.input':self.wsgi_file,
'wsgi.url_encoding':'utf-8',
'wsgi.url_scheme':'http',
'wsgi.errors':sys.stderr,
'ACTUAL_SERVER_PROTOCOL':ACTUAL_SERVER_PROTOCOL,
'CLIENT_ADDR':self.client_address[0],
'CLIENT_PORT':self.client_address[1],
'REQUEST_URI':uri,
'PATH_INFO':uri,
'REQUEST_METHOD':request_method,
'PATH_INFO': path_info,
'QUERY_STRING': query_string}
for line in self.wsgi_file:
if line=='\r\n': break
match=regex_header.match(line)
if not match: continue
key=match.group('key').upper().replace('-','_')
if isinstance(key,unicode):
key=key.encode('ISO-8859-1')
value=match.group('value')
# commented out for speed up. is this necessary?
# try:
# value = value.decode('utf-8').encode('utf-8')
# except UnicodeDecodeError:
# value = value.decode('ISO-8859-1').encode('utf-8')
self.environ['HTTP_'+key]=value
request_encoding=self.environ.get('HTTP_TRANSFER_ENCODING',None)
if not request_encoding:
self.chunked_request=False
elif request_encoding=='chunked':
self.chunked_request=True
else:
raise Exception, "Not supported"
if request_protocol=='HTTP/1.1' and request_method!='HEAD':
self.chunked_response=True
else:
self.chunked_response=False
self.chunked_request=False
self.chunked_response=False
if self.chunked_request:
self.environ['wsgi.input']=ChunkedReader(self.wsgi_file)
# a test:
"""
try:
body=''
content_length=int(self.environ['HTTP_CONTENT_LENGTH'])
body=self.wsgi_file.read(content_length)
self.environ['wsgi.input']=cStringIO.StringIO(body)
except Exception,e:
pass
print body
"""
def start_response(self,status,headers):
header_keys = [x.lower() for (x,y) in headers]
if not 'date' in header_keys:
headers.append(('Date',rfc822.formatdate()))
if not 'server' in header_keys:
headers.append(('Server',self.server_name))
self.connection=self.environ.get('HTTP_CONNECTION','close')
headers.append(('Connection',self.connection))
self.response_headers=headers
serialized_headers=''.join(['%s: %s\r\n' % (k,v) for (k,v) in headers])
self.client_socket.sendall("HTTP/1.1 200 OK\r\n%s\r\n" % serialized_headers)
def try_error_response(self,status="500 INSERTNAL SERVER ERROR"):
try:
self.client_socket.sendall("%s %s\r\nContent-Length: 0\r\nContent-Type: text/plain\r\n\r\n" %
(self.environ['ACTUAL_SERVER_PROTOCOL'],status))
except: pass
class web2pyWSGIServer:
def __init__(self, bind_addr, wsgi_app, min_threads=10, server_name=None,
max_threads=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
if isinstance(bind_addr,str): bind_addr=bind_addr.split(':')
self.address=bind_addr[0]
self.port=bind_addr[1]
self.min_threads=min_threads
self.max_threads=max_threads
self.request_queue_size=request_queue_size
self.timeout=timeout
self.shutdown_timeout=shutdown_timeout
self.ssl_interface=None
self.server_name=server_name or SERVER_NAME
Worker.wsgi_apps.append(wsgi_app)
Worker.server_name=server_name
Worker.min_threads=self.min_threads
Worker.threads.update([Worker() for k in xrange(self.min_threads)])
def start(self):
for thread in Worker.threads: thread.start()
for res in socket.getaddrinfo(self.address, self.port, socket.AF_UNSPEC,
socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
try:
af, socktype, proto, canonname, sa = res
self.socket=socket.socket(af, socktype, proto)
break
except:
continue
if not self.socket:
raise IOException # unable to connect
self.socket.bind(sa)
self.socket.listen(self.request_queue_size)
if self.ssl_interface:
self.socket=ssl_interface(self.socket)
try:
while True:
try:
(client_socket,client_address) = self.socket.accept()
except KeyboardInterrupt:
return self.stop()
except Exception,e:
logging.error(str(e))
continue
if hasattr(client_socket,'settimeout'):
client_socket.settimeout(self.timeout)
Worker.queue.put((client_socket,client_address))
if not Worker.queue.empty() and len(Worker.threads)<self.max_threads:
for k in range(self.min_threads):
new_worker=Worker()
Worker.threads.add(new_worker)
new_worker.start()
elif Worker.queue.empty() and len(Worker.threads)>self.min_threads:
for k in range(self.min_threads):
Worker.queue.put((None,None))
except Exception, e:
logging.error(str(traceback.format_exc()))
return self.stop()
def kill(self,status,frame):
sys.exit(1)
def stop(self):
signal.signal(signal.SIGALRM,self.kill)
signal.alarm(self.shutdown_timeout)
threads=copy.copy(Worker.threads)
for thread in threads: Worker.queue.put((None,None))
return
def test_wsgi_app(environ, start_response):
status = '200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
return ['hello world!\n']
#logging.basicConfig(level=logging.INFO)
logging.basicConfig(level=logging.WARN)
if __name__=='__main__':
s=web2pyWSGIServer('127.0.0.1:'+sys.argv[1],test_wsgi_app,min_threads=5,max_threads=10)
print 'starting!'
s.start()