Tidbits @ Kassemi

A collection of opinions, thoughts, tricks and misc. information.

Wednesday, June 07, 2006

 

Quck server part 2

Just decided I'd keep on updating my progress on this one.
Dump it on top of cherrypy for a quick, dirty little server for simpler requests.


'''
A BASIC server to embed inside another for simple, fast operations
Copyright (C) 2006 James Kassemi (Tweeked Ideas)

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

_________________________________________________________________

You'll have to bind this to a different port to handle the requests. If you
don't want to deal with apparent port differences in your code, simply
use a rewrite rule in your apache configuration, much like you do to redirect
cherrypy requests in a production environment.

Also, there are different types of request classes available here for you
to play around with... If you use the Request_Quick_CPSession class you
will be able to piggy-back the cherrypy session id generator to create
some simple sessions. I've got a database session class in cherrypy that
I also have wrapped around a request class for this, which allows manipulations
of the session from here... eh, obvious. Have fun!

'''

import socket
import thread
import threading
import Queue
import urlparse
import time
import traceback

# Straight from cherrypy
import errno
socket_errors_to_ignore = []
for _ in ("EPIPE", "ETIMEDOUT", "ECONNREFUSED", "ECONNRESET",
"EHOSTDOWN", "EHOSTUNREACH",
"WSAECONNABORTED", "WSAECONNREFUSED", "WSAECONNRESET",
"WSAENETRESET", "WSAETIMEDOUT"):
if _ in dir(errno):
socket_errors_to_ignore.append(getattr(errno, _))
socket_errors_to_ignore = dict.fromkeys(socket_errors_to_ignore).keys()
# End

WORKER_COUNT = 50
SOCKET_QUEUE = 10

TIMEOUT = 10

_SHUTDOWN = 1
STATUS_CODES = {
200: 'OK',
404: 'Not Found',
500: 'Internal Error'
}

request = threading.local()
info = threading.local()
session = threading.local()

start_thread = []
stop_thread = []
start_request = []
stop_request = []

SESSION_DATA = dict()

class Session(dict):
def __init__(self, id):
self.id = id
try:
self.update(SESSION_DATA[id])
except: pass
self['_id'] = id

def _save(self):
SESSION_DATA[self.id] = dict()
SESSION_DATA[self.id].update(self)

class Request:
''' Request processing. '''

def __init__(self, socket):
for item in start_request:
item(self)
self.socket = socket
self.rfile = self.socket.makefile("r", -1)
self.wfile = self.socket.makefile("w", -1)
self.header_temp = ''

def end_headers(self):
self.wfile.write(self.header_temp)
self.wfile.write('\r\n')
self.header_temp = ''

def send_header(self, name, value):
self.header_temp += '%s: %s\r\n' % (name, value)

def send_response(self, code):
self.header_temp = 'HTTP/1.1 %i %s\r\n' % (code, STATUS_CODES[code])

def path_process(self):
path = self.request_line.split(' ')[1]
parsed = urlparse.urlparse(path)
path = parsed[2]
args = parsed[4]
args = args.replace(';', '&')
arg_dict = dict()
try:
args = [arg_dict.update({item[0]:item[1]}) for item in [arg.split('=') for arg in args.split('&')]]
except: pass
try:
path = path[1:].split('/')
return getattr(apps[path[0]], path[1], False)(**arg_dict)
except: return False

def respond(self, code, ctype, content):
self.send_response(code)
self.send_header('Server', 'TweekedIdeas/1.0')
self.send_header('Content-Type', ctype)
self.end_headers()
for line in content:
self.wfile.write(line)
self.wfile.flush()

def process(self):
self.request_line = self.rfile.readline()
self.request_headers = dict()
while True:
header = self.rfile.readline()
if header.strip() == '': break
dl = header.find(':')
self.request_headers[header[0:dl]]=header[dl+2:].rstrip()
request.headers = self.request_headers
try:
res = self.path_process()
except Exception, e:
e2 = traceback.format_exc()
print e2
self.respond(500, 'text/plain', 'Internal Server Error\n\n%s' % e2)
return
if res:
self.respond(200, 'text/html', res)
else:
self.respond(404, 'text/html', 'Not Found')

def destroy(self):
self.socket.close()
self.wfile.close()
self.rfile.close()
for item in stop_request:
item(self)

class Request_Advanced(Request):
def path_process(self):
# Allows for generators and more complex layout.
path = self.request_line.split(' ')[1]
parsed = urlparse.urlparse(path)
path = parsed[2]
args = parsed[4]
args = args.replace(';', '&')
arg_dict = dict()
try:
args = [arg_dict.update({item[0]:item[1]}) for item in [arg.split('=') for arg in args.split('&')]]
except: arg_dict = dict()
try:
path = path[1:].split('/')
focus = app
for part in path:
focus = getattr(focus, part, False)
if not focus: return False
ret_val = focus(**arg_dict)
ret_val_type = type(ret_val)
if type(ret_val) == tuple:
from Cheetah.Template import Template
t = Template.compile(file=ret_val[0])()
data = str(t)
#data = _render(ret_val[1], ret_val[0], 'cheetah', focus)
print '...', data
return data
return ret_val
except: traceback.print_exc()
return False

class Request_Quick(Request):
def path_process(self):
global apps
path = self.request_line.split(' ')[1]
parsed = urlparse.urlparse(path)
path = parsed[2][1:]
args = parsed[4]
arg_dict = dict()
try:
args = [arg_dict.update({item[0]:item[1]}) for item in [arg.split('=') for arg in args.split('&')]]
except: arg_dict = dict()
focus = apps.get(path, False)
if not focus: return False
return focus(**arg_dict)

class Request_Quick_CPSession(Request):
def path_process(self):
global apps
c = info.cookie
try:
c.load(request.headers['Cookie'])
info.session = Session(c['session_id'].value)
except KeyError:
info.session = dict()

path = self.request_line.split(' ')[1]
parsed = urlparse.urlparse(path)
path = parsed[2][1:]
args = parsed[4]
arg_dict = dict()
try:
args = [arg_dict.update({item[0]:item[1]}) for item in [arg.split('=') for arg in args.split('&')]]
except: arg_dict = dict()
focus = apps.get(path, False)
if not focus: return False
ret = focus(**arg_dict)
info.session._save()
return ret

class Worker(threading.Thread):
''' Sits around and handles requests as they appear. '''

def __init__(self, server):
self.server = server
self.ready = False
self.go = True
threading.Thread.__init__(self)

def run(self):
for item in start_thread:
item(self)
while self.go:
self.ready = True
request = self.server.request_pool.get()
if request == _SHUTDOWN:
self.ready=False
for item in stop_thread:
item(self)
return
try:
try:
request.process()
except socket.error, e:
errno = e.args[0]
if errno not in socket_errors_to_ignore:
traceback.print_exc()
except:
traceback.print_exc()
return
finally:
request.destroy()

def stop(self):
self.go = False

class Server:
''' Grabs incoming connections and pools them. '''

def __init__(self, host='', port=8080, thread_count=10, socket_queue=5, request_class=Request):
self.host = host
self.port = port
WORKER_COUNT = thread_count
SOCKET_QUEUE = socket_queue
self.request_class = request_class
self.request_pool = Queue.Queue(-1)
self.workers = []
self.stop_flag = False

def start(self):
addr = (self.host, self.port)

import os
self.socket = socket.socket()
try: os.unlink(addr)
except: pass
try: os.chmod(addr, 0777)
except: pass
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
time.sleep(.1)
self.socket.bind(addr)
self.socket.listen(SOCKET_QUEUE)
for i in xrange(WORKER_COUNT):
self.workers.append(Worker(self))
for worker in self.workers:
worker.start()
while not worker.ready:
time.sleep(.1)
while not self.stop_flag:
s, addr = self.socket.accept()
s.settimeout(TIMEOUT)
self.request_pool.put(self.request_class(s), False)

def stop(self):
self.stop_flag = True
print "Shutting down AJAX server."
for worker in self.workers:
self.request_pool.put(_SHUTDOWN)
for worker in self.workers:
worker.stop
time.sleep(.05)
while True:
try:
del self.workers[0]
except: break
print "Done"

apps = dict()

def mount(app, name):
global apps
apps[name] = app

server = None

def start(host='', port=8080, threads=10, squeue=5, catch=False):
global server
server = Server(host=host, port=port, thread_count=threads,
socket_queue=squeue, request_class=Request_Quick_CPSession)

print "AJAX server started on %s:%i." % (host, port)

thread.start_new_thread(server.start, ())

if catch:
try:
while True:
time.sleep(3)
except:
server.stop()
server.socket.close()

Comments: Post a Comment



<< Home

Archives

August 2005   September 2005   October 2005   November 2005   December 2005   January 2006   February 2006   March 2006   April 2006   June 2006   July 2006   August 2006   September 2006   October 2006   November 2006  

This page is powered by Blogger. Isn't yours?