"""Bobo handler module
For use with medusa & python object publisher
copyright 1997 amos latteier
(code based on script_handler.py)
Use:
here is a sample fragment from a script to start medusa:
...
hs = http_server.http_server (IP_ADDRESS, HTTP_PORT, rs, lg)
...
sys.path.insert(0,'c:\\windows\\desktop\\medusa2\\www\\bobo')
import bobotest
bh=bobo_handler.bobo_handler(bobotest, debug=1) #create bobo handler
hs.install_handler(bh) #install handler in http server
...
This will install the bobo handler on the http server and give you
access to the bobotest module via urls like this:
http://myserver.com/bobotest/blah/blah
bobo_handler initalization options:
* debug: If the debug flag is set then the published module will be reloaded
whenever its source is changed on disk. This is very handy for developement.
* uri_base: If the uri_base isn't specified it defaults to /<module_name>
"""
__version__="1.03"
import cgi_module_publisher
import sys
import regex
import string
import os
try:
from cStringIO import StringIO
except:
from StringIO import StringIO
try:
import thread
mutex = thread.allocate_lock()
THREADS = 1
except:
THREADS = 0
import counter
import default_handler
import producers
split_path = default_handler.split_path
unquote = default_handler.unquote
get_header = default_handler.get_header
CONTENT_LENGTH = regex.compile ('Content-Length: \([0-9]+\)', regex.casefold)
# maps request headers to environment variables
#
header2env={'Content-Length' : 'CONTENT_LENGTH',
'Content-Type' : 'CONTENT_TYPE',
'Referer' : 'HTTP_REFERER',
'User-Agent' : 'HTTP_USER_AGENT',
'Accept' : 'HTTP_ACCEPT',
'Accept-Charset' : 'HTTP_ACCEPT_CHARSET',
'Accept-Language' : 'HTTP_ACCEPT_LANGUAGE',
'Host' : None,
'Connection' : 'CONNECTION_TYPE',
'Pragma' : None,
'Authorization' : 'HTTP_AUTHORIZATION',
'Cookie' : 'HTTP_COOKIE',
}
# convert keys to lower case for case-insensitive matching
#
for (key,value) in header2env.items():
del header2env[key]
key=string.lower(key)
header2env[key]=value
class bobo_handler:
"publishes a module via bobo"
def __init__ (self, module, uri_base=None, debug=None):
self.module = module
self.debug = debug
if self.debug:
self.last_reload=self.module_mtime()
self.hits = counter.counter()
# if uri_base is unspecified, assume it
# starts with the published module name
#
if not uri_base:
uri_base="/%s" % module.__name__
elif uri_base[-1]=="/": # kill possible trailing /
uri_base=uri_base[:-1]
self.uri_base=uri_base
uri_regex='%s.*' % self.uri_base
self.uri_regex = regex.compile(uri_regex)
def match (self, request):
uri = request.uri
if self.uri_regex.match (uri) == len(uri):
return 1
else:
return 0
def handle_request (self, request):
[path, params, query, fragment] = split_path (request.uri)
while path and path[0] == '/':
path = path[1:]
if '%' in path:
path = unquote (path)
self.hits.increment()
if query:
# cgi_publisher_module doesn't want the leading '?'
query = query[1:]
self.env = {}
self.env['REQUEST_METHOD'] = string.upper(request.command)
self.env['SERVER_PORT'] = '%s' % request.channel.server.port
self.env['SERVER_NAME'] = request.channel.server.server_name
self.env['SERVER_SOFTWARE'] = request['Server']
self.env['SCRIPT_NAME'] = self.uri_base # are script_name and path_info ok?
self.env['QUERY_STRING'] = query
try:
path_info=string.split(path,self.uri_base[1:],1)[1]
except:
path_info=''
self.env['PATH_INFO'] = path_info
self.env['GATEWAY_INTERFACE']='CGI/1.1' # what should this really be?
self.env['REMOTE_ADDR']=request.channel.addr[0]
self.env['REMOTE_HOST']=request.channel.addr[0] #what should this be?
for header in request.header:
[key,value]=string.split(header,": ",1)
key=string.lower(key)
if header2env.has_key(key):
if header2env[key]:
self.env[header2env[key]]=value
else:
key='HTTP_'+string.upper(string.join(string.split(key,"-"),"_"))
self.env[key]=value
# remove empty environment variables
#
for key in self.env.keys():
if self.env[key]=="" or self.env[key]==None:
del self.env[key]
if request.command in ["post","put"]:
request.collector=input_collector(self,request)
request.channel.set_terminator (None)
else:
sin=StringIO('')
self.continue_request(sin,request)
def continue_request(self,sin,request):
"continue handling request now that we have the stdin"
# if we have threads spawn a new one to publish the module
# so we dont freeze the server while publishing.
if THREADS:
thread.start_new_thread(self._continue_request,(sin,request))
else:
self._continue_request(sin,request)
def _continue_request(self,sin,request):
"continue handling request now that we have the stdin"
sout = StringIO()
serr = StringIO()
if self.debug:
m_time=self.module_mtime()
if m_time> self.last_reload:
reload(self.module)
self.last_reload=m_time
if THREADS:
mutex.acquire()
cgi_module_publisher.publish_module(
self.module.__name__,
stdin=sin,
stdout=sout,
stderr=serr,
environ=self.env,
#debug=1
)
if THREADS:
mutex.release()
if serr.tell():
request.log(serr.getvalue())
response=sout
response=response.getvalue()
# set response headers
[headers,html]=string.split(response,"\n\n",1)
headers=string.split(headers,"\n")
for line in headers:
[header, header_value]=string.split(line,": ",1)
if header=="Status":
[code,message]=string.split(header_value," ",1)
request.reply_code=string.atoi(code)
else:
request[header]=header_value
request.push(html)
request.done()
def module_mtime(self):
"returns the last modified date for a given module's source file"
return os.stat(self.module.__file__)[8]
def status (self):
return producers.simple_producer (
'<li>Bobo Handler'
+ '<ul>'
+ ' <li><b>Hits:</b> %d' % int(self.hits)
+ '</ul>'
)
class input_collector:
"gathers input for put and post requests"
def __init__ (self, handler, request):
self.handler = handler
self.request = request
self.data = StringIO()
# make sure there's a content-length header
self.cl = get_header (CONTENT_LENGTH, request.header)
if not self.cl:
request.error(411)
return
else:
self.cl = string.atoi(self.cl)
def collect_incoming_data (self, data):
self.data.write(data)
if self.data.tell() >= self.cl:
self.data.seek(0)
h=self.handler
r=self.request
# set the terminator back to the default
self.request.channel.set_terminator ('\r\n\r\n')
del self.handler
del self.request
h.continue_request(self.data,r)
|