Update of /cvsroot/webware/Webware/WebKit/Adapters
In directory usw-pr-cvs1:/tmp/cvs-serv10193/WebKit/Adapters
Added Files:
Adapter.py CGIAdapter.py FCGIAdapter.py LRWPAdapter.py
ModPythonAdapter.py ModSnakeAdapter.py OneShot.cgi
OneShotAdapter.py WebKit.cgi __init__.py fcgi.py lrwplib.py
Log Message:
Rearranged the WebKit directory, so all the adapters are in an
Adapters subdirectory -- including WebKit.cgi, OneShot.cgi,
and all the subdirectories of Native.
Also added LRWPAdapter for the Xitami web server.
Updated InstallGuide.html -- both minor changes to paths and
general updating (including adapters which have been added
since that was written).
--- NEW FILE: Adapter.py ---
import os, sys, time, socket
from marshal import dumps, loads
from WebKit.Object import Object
from MiscUtils.Configurable import Configurable
import struct, errno
class Adapter(Configurable, Object):
def __init__(self, webKitDir):
Configurable.__init__(self)
Object.__init__(self)
self._webKitDir = webKitDir
self._respData = ''
def name(self):
return self.__class__.__name__
def defaultConfig(self):
return {
'NumRetries': 20,
'SecondsBetweenRetries': 3
}
def configFilename(self):
return os.path.join(self._webKitDir, 'Configs/%s.config' % self.name())
def transactWithAppServer(self, env, myInput, host, port):
"""
Used by subclasses that are communicating with a separate app server via
socket. Returns the unmarshaled response dictionary.
"""
dict = {
'format': 'CGI',
'time': time.time(),
'environ': env,
}
# @@ gat 2002-03-21: Changed retry strategy. Now, we'll only retry the initial
# connection to the appserver. After that, any failure means the request failed.
# This is to avoid processing a request twice.
retries = 0
while 1:
try:
# Send our request to the AppServer
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
break
except socket.error:
# retry
if retries <= self.setting('NumRetries'):
retries = retries + 1
time.sleep(self.setting('SecondsBetweenRetries'))
else:
raise 'timed out waiting for connection to app server'
data = dumps(dict)
s.send(dumps(int(len(data))))
s.send(data)
sent=0
inputLength = len(myInput)
while sent < inputLength:
chunk = s.send(myInput[sent:])
sent = sent+chunk
s.shutdown(1)
# Get the response from the AppServer
bufsize = 8*1024
# @@ 2000-04-26 ce: this should be configurable, also we should run some tests on different sizes
# @@ 2001-01-25 jsl: It really doesn't make a massive difference. 8k is fine and recommended.
## s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, struct.pack('ll',0,5)) #wait for 0.5 seconds for data
while 1:
data = s.recv(bufsize)
if not data:
break
self.processResponse(data)
return self._respData
def processResponse(self, data):
""" Process response data as it arrives."""
self._respData = self._respData + data
--- NEW FILE: CGIAdapter.py ---
#!/usr/bin/env python
"""
CGIAdapter.py
This is the CGI Adapter for the WebKit AppServer.
This CGI script collects information about the request, puts it into a
package, and then sends it to the WebKit application server over TCP/IP.
This script expects to find a file in it's directory called
'address.text' that specifies the address of the app server.
The file is written by the app server upon successful startup
and contains nothing but:
hostname:port
with no other characters, not even a newline. For example,
localhost:8086
or even:
:8086
...since an empty string is a special case indicating the local host.
"""
import string, time, sys
timestamp = time.time()
from socket import *
from marshal import dumps, loads
from Adapter import Adapter
debugging = 0 # set 1 if you want to see the raw response dictionary, instead of a normal page
class CGIAdapter(Adapter):
def run(self):
import os, sys
try:
# MS Windows: no special translation of end-of-lines
if os.name=='nt':
import msvcrt
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
myInput = ''
if os.environ.has_key('CONTENT_LENGTH'):
length = int(os.environ['CONTENT_LENGTH'])
myInput = myInput + sys.stdin.read(length)
# 2000-05-20 ce: For use in collecting raw request dictionaries for use in Testing/stress.py
# Leave this code here in case it's needed again
#
#counter = int(open('counter.text').read())
#counter = counter + 1
#open('counter.text', 'w').write(str(counter))
#open('rr-%02d.rr' % counter, 'w').write(str(dict))
(host, port) = string.split(open(os.path.join(self._webKitDir, 'address.text')).read(), ':')
if os.name=='nt' and host=='': # MS Windows doesn't like a blank host name
host = 'localhost'
port = int(port)
response = self.transactWithAppServer(os.environ.data, myInput, host, port)
## # deliver it!
## write = sys.stdout.write
## if debugging:
## write('Content-type: text/html\n\n<html><body>')
## write('<p> Your adapter has <b>debugging</b> set to true. <p>')
## write(HTMLEncode(str(response)))
## write('</body></html>')
except:
import traceback
sys.stderr.write('[%s] [error] WebKit.CGIAdapter: Error while responding to request (unknown)\n' % (time.asctime(time.localtime(time.time()))))
sys.stderr.write('Python exception:\n')
traceback.print_exc(file=sys.stderr)
output = apply(traceback.format_exception, sys.exc_info())
output = string.join(output, '')
output = HTMLEncode(output)
sys.stdout.write('''Content-type: text/html
<html><body>
<p><pre>ERROR
%s</pre>
</body></html>\n''' % output)
def processResponse(self, data):
sys.stdout.write(data)
sys.stdout.flush()
HTMLCodes = [
['&', '&'],
['<', '<'],
['>', '>'],
['"', '"'],
]
def HTMLEncode(s, codes=HTMLCodes):
""" Returns the HTML encoded version of the given string. This is useful to display a plain ASCII text string on a web page. (We could get this from WebUtils, but we're keeping CGIAdapter independent of everything but standard Python.) """
for code in codes:
s = string.replace(s, code[0], code[1])
return s
def main(webKitDir):
CGIAdapter(webKitDir).run()
--- NEW FILE: FCGIAdapter.py ---
#!/usr/bin/env python
"""
FCGIAdapter.py
FCGI Adapter for the WebKit application environment.
Note: FCGI for Webware is not available on Windows.
This script is started by the Web Server and is kept running.
When a request comes through here, this script collects information
about the request, puts it into a package, and then invokes the
WebKit Application to handle it.
Original CGI implementaion by Chuck Esterbrook.
FastCGI Implementation by Jay Love. Based on threaded fcgi example "sz_fcgi" provided by "Andreas Jung"
SETUP
To use this adapter, you must have a fastcgi capable web server.
For Apache, you'll need to add the following lines to your httpd.conf file, or
put them in another file and include that file in httpd.conf
#I have the file in my cgi-bin directory, but you might as well put it in html.
#the -host is the port it communicates on
FastCgiExternalServer ../cgi-bin/FCGIWebKit.py -host localhost:33333 # the path is from the SERVER ROOT
<Location /FCGIWebKit.py> #or whatever name you chose for the file above
SetHandler fastcgi-script
Options ExecCGI FollowSymLinks
</Location>
You could also take an extension oriented approach in Apache using '.fcgi':
AddHandler fastcgi-script fcgi
And then using, in your URLs, 'WebKit.fcgi' which is a link to this file. e.g.,:
http://localhost/Webware/WebKit/WebKit.fcgi/Introspect
FUTURE
(*) There are some interesting lines at the top of fcgi.py:
# Set various FastCGI constants
# Maximum number of requests that can be handled
FCGI_MAX_REQS=1
FCGI_MAX_CONNS = 1
# Boolean: can this application multiplex connections?
FCGI_MPXS_CONNS=0
Do these need to be adjusted in order to realize the full benefits of FastCGI?
(*) Has anyone measured the performance difference between CGIAdapter and FCGIAdapter? What are the results?
JSL- It's twice as fast as straight CGI
CHANGES
* 2000-05-08 ce:
* Fixed bug in exception handler to send first message to stderr, instead of stdout
* Uncommented the line for reading 'address.text'
* Switched from eval() encoding to marshal.dumps() encoding in accordance with AppServer
* Increased rec buffer size from 8KB to 32KB
* Removed use of pr() for feeding app server results back to webserver. Figure that's slightly more efficient.
* Added notes about how I set this up with Apache to what was already there.
*2001-03-14 jsl:
* Fixed problem with post data
"""
##"""Set WebKitDir to the directory where WebKit is located"""
WebKitDir = '/data/Linux/python/Webware/WebKit'
import fcgi, time
from marshal import dumps, loads
from socket import *
import string
import os
import sys
timestamp = time.time()
_AddressFile='address.text'
HTMLCodes = [
['&', '&'],
['<', '<'],
['>', '>'],
['"', '"'],
]
def HTMLEncode(s, codes=HTMLCodes):
""" Returns the HTML encoded version of the given string. This is useful to display a plain ASCII text string on a web page. (We could get this from WebUtils, but we're keeping CGIAdapter independent of everything but standard Python.) """
for code in codes:
s = string.replace(s, code[0], code[1])
return s
if os.name != 'posix':
print "This adapter is only available on UNIX"
sys.exit(1)
fcgi._startup()
if not fcgi.isFCGI():
print "No FCGI Environment Available"
print "This module cannot be run from the command line"
sys.exit(1)
addrfile=os.path.join(WebKitDir, _AddressFile)
(host, port) = string.split(open(addrfile).read(), ':')
port = int(port)
os.chdir(WebKitDir)
sys.path.append(os.path.abspath(os.path.join(WebKitDir, "..")))
from Adapter import Adapter
class FCGIAdapter(Adapter):
def run(self):
"""Block waiting for new request"""
while fcgi.isFCGI():
req=fcgi.FCGI()
self.FCGICallback(req)
def FCGICallback(self,req):
"""This function is called whenever a request comes in"""
import sys
try:
# Transact with the app server
response = self.transactWithAppServer(req.env, req.inp.read(), host, port)
# deliver it!
req.out.write(response)
req.out.flush()
except:
import traceback
# Log the problem to stderr
stderr = req.err
stderr.write('[%s] [error] WebKit.FCGIAdapter: Error while responding to request (unknown)\n' % (
time.asctime(time.localtime(time.time()))))
stderr.write('Python exception:\n')
traceback.print_exc(file=stderr)
# Report the problem to the browser
output = apply(traceback.format_exception, sys.exc_info())
output = string.join(output, '')
output = HTMLEncode(output)
self.pr('''Content-type: text/html
<html><body>
<p><pre>ERROR
%s</pre>
</body></html>\n''' % output)
req.Finish()
return
#easy print function
def pr(self,*args):
"""just a quick and easy print function"""
try:
req=self.req
s=''
for i in args: s=s+str(i)
req.out.write(s+'\n')
req.out.flush()
except:
pass
#print "Starting"
fcgiloop = FCGIAdapter(WebKitDir)
fcgiloop.run()
--- NEW FILE: LRWPAdapter.py ---
#!/usr/bin/env python
#-----------------------------------------------------------------------------
# Name: LRWPAdapter.py
#
# Purpose: LRWP Adapter for the WebKit AppServer and the Xitami Web Server.
# Adapted from the CGI Adapter for WebKit.
#
# Author: Jim Madsen
#
# Created: 09/27/02
#-----------------------------------------------------------------------------
## """Set Program Parameters"""
webKitDir = 'C:\Program Files\Python22\Lib\site-packages\Webware\WebKit'
LRWPappName = 'testing'
LRWPhost = 'localhost'
LRWPport = 81
#-----------------------------------------------------------------------------
import os, sys, string
from Adapter import Adapter
from lrwplib import LRWP
class LRWPAdapter(Adapter):
def __init__(self, webkitdir):
Adapter.__init__(self, webkitdir)
if sys.platform == 'win32':
import msvcrt
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
# Get Host and Port information for WebKit AppServer
(self.host, self.port) = string.split(open(os.path.join(self._webKitDir, 'address.text')).read(), ':')
self.port = int(self.port)
def lrwpConnect(self, LRWPappName, LRWPhost, LRWPport):
try:
#Make connection to Xitami
self.lrwp = LRWP(LRWPappName, LRWPhost, LRWPport)
self.lrwp.connect()
print ('\r\n Connected to Xitami -- Listening for ' + LRWPappName + '\r\n')
self.LRWPappName = LRWPappName
except:
sys.exit('Could not make proper connection to Xitami')
def handler(self):
while (1):
try:
# Accept requests
self.request = self.lrwp.acceptRequest()
# Read input from request object
self.myInput = ''
if self.request.env.has_key('CONTENT_LENGTH'):
length = int(self.request.env['CONTENT_LENGTH'])
self.myInput = self.myInput + self.request.inp.read(length)
# Fix environment variables due to the way Xitami reports them under LRWP
self.request.env['SCRIPT_NAME'] = ('/' + self.LRWPappName)
self.request.env['REQUEST_URI'] = ('/' + self.LRWPappName + self.request.env['PATH_INFO'])
# Transact with the app server
self.response = self.transactWithAppServer(self.request.env, self.myInput, self.host, self.port)
# Log page handled to the console
print self.request.env['REQUEST_URI']
# Close request to handle another
self.request.finish()
# Capture Ctrl-C...shutdown will occur on next request handled
except KeyboardInterrupt:
print '\r\n Closing connection to Xitami \r\n'
self.lrwp.close()
sys.exit(' Clean Exit')
except:
print 'Error handling requests'
# Output through request object
def processResponse(self, data):
self.request.out.write(data)
def main():
# Startup LRWP to WebKit interface
lrwpInterface = LRWPAdapter(webKitDir)
lrwpInterface.lrwpConnect(LRWPappName, LRWPhost, LRWPport)
lrwpInterface.handler()
if __name__ == '__main__':
main()
--- NEW FILE: ModPythonAdapter.py ---
###########################################
# mod_python adapter for WebKit
#
#
# Contributed by: Dave Wallace
# Modified by Jay Love and Geoff Talvola
#
##########################################
"""
Here's how I set up my Apache conf:
<Location /WK >
SetHandler python-program
# add the directory that contains ModPythonAdapter.py
PythonPath "sys.path+['/path/to/WebKit']"
PythonOption AppWorkDir /path/to/dir/with/address.text
PythonHandler ModPythonAdapter
PythonDebug On
</Location>
If you used the MakeAppWorkDir.py script to make a separate
application working directory, specify that path for the AppWorkDir
option, otherwise it should be in your WebKit directory in which case
you should use /path/to/WebKit/address.text
http://localhost/WK/Welcome
You may also send all requests with a .psp extension to WebKit by adding these lines, outside
of any location or dircetory.
AddHandler python-program .psp
PythonPath "sys.path+['/path/to/WebKit']"
PythonHandler modpHandler::pspHandler
PythonOption AppWorkDir /path/to/dir/with/address.text
"""
# Fix the current working directory -- this gets initialized incorrectly
# for some reason when run using mod_python.
import os
try:
os.chdir(os.path.abspath(os.path.dirname(__file__)))
except:
pass
from mod_python import apache
import time
from socket import *
import sys
import string
from Adapter import Adapter
debug=0
__adapter = None
bufsize = 32*1024
class ModPythonAdapter(Adapter):
def __init__(self, host, port, webkitdir):
Adapter.__init__(self, webkitdir)
self.host = host
self.port = port
def handler(self, req):
self.reset(req)
try:
# Get input
myInput = self.input(req)
# get the apache module to do the grunt work of
# building the environment
env=apache.build_cgi_env(req)
# Fix up the path
if not env.has_key('PATH_INFO'): env['PATH_INFO']=req.path_info
# Communicate with the app server
respdict = self.transactWithAppServer(env, myInput, self.host, self.port)
# Respond back to Apache
#self.respond(req, respdict)
except:
self.handleException(req)
return apache.OK
def pspHandler(self, req):
self.reset(req)
try:
# Get input
myInput = self.input(req)
# get the apache module to do the grunt work of
# building the environment
env=apache.build_cgi_env(req)
# Special environment setup needed for psp handler
env['WK_ABSOLUTE']=1
# Fix up the path
if not env.has_key('PATH_INFO'): env['PATH_INFO']=req.path_info
# Communicate with the app server
respdict = self.transactWithAppServer(env, myInput, self.host, self.port)
# Respond back to Apache
self.respond(req, respdict)
except:
self.handleException(req)
return apache.OK
def typehandler(self, req):
""" Not being used yet.
Probably never be used, b/c the req.handler field is read only in mod_python.
"""
self.reset(req)
debug=1
if debug:
ot = open("/tmp/log2.txt","a")
ot.write("In Type Handler\n")
ot.flush()
if req.filename == None:
return apache.DECLINED
fn = req.filename
if debug:
ot.write("TH: Filename: %s\n"%fn)
ext = fn[string.rfind(fn,"."):]
if debug:
ot.write("TH: Extension: %s\n"%ext)
if debug:
ot.write("Req_Handler = %s\n"%req.handler)
ot.flush()
ot.close()
if ext == ".psp":
req.handler = "python-program"
return apache.OK
else:
return apache.DECLINED
def input(self, req):
myInput = ''
inp = req.read(bufsize)
# this looks like bad performance if we don't get it all
# in the first buffer
while inp:
myInput = myInput + inp
inp = req.read(bufsize)
return myInput
def processResponse(self, data):
req = self.request()
if self.doneHeader():
req.write(data)
return
headerData = self.headerData() + data
self.setHeaderData(headerData)
headerend = string.find(headerData, "\r\n\r\n")
if headerend < 0:
return
headers = headerData[:headerend]
for header in string.split(headers, "\r\n"):
colon = string.find(header, ':')
name = header[:colon]
value = header[colon+1:]
req.headers_out.add(name, value)
if string.lower(name) == 'content-type': req.content_type = value
if string.lower(name) == 'status': req.status = int(string.split(string.lstrip(value),' ')[0])
req.send_http_header()
req.write(headerData[headerend+4:])
self.setDoneHeader(1)
def handleException(self, req):
import traceback
apache.log_error('WebKit mod_python: Error while responding to request\n')
apache.log_error('Python exception:\n')
traceback.print_exc(file=sys.stderr)
output = apply(traceback.format_exception, sys.exc_info())
output = string.join(output, '')
output = string.replace(output, '&', '&')
output = string.replace(output, '<', '<')
output = string.replace(output, '>', '>')
req.write('''
<html><body>
<p><pre>ERROR
%s</pre>
</body></html>\n''' % output)
def reset(self, request):
self.setDoneHeader(0)
self.setHeaderData('')
self.setRequest(request)
# These methods are non-thread-safe. On platforms like NT where Apache runs multi-threaded,
# and the same ModPythonAdapter instance may be called simultaneously for different requests,
# they need to replaced with threadsafe versions. See WinModPythonAdapter below.
def doneHeader(self):
return self._doneHeader
def setDoneHeader(self, doneHeader):
self._doneHeader = doneHeader
def headerData(self):
return self._headerData
def setHeaderData(self, headerData):
self._headerData = headerData
def request(self):
return self._request
def setRequest(self, request):
self._request = request
# NT-specific, thread-safe version of ModPythonAdapter. Requires Win32 extensions.
if os.name == 'nt':
import win32api
# This is a Windows-specific thread-safe version of ModPythonAdapter. It replaces the
# non-thread-safe [set]doneHeader, [set]headerData, and [set]request with versions
# that store the information keyed by thread ID.
#
# This seems a bit hokey, but it was easy to write and it works.
OriginalModPythonAdapter = ModPythonAdapter
class WinModPythonAdapter(OriginalModPythonAdapter):
def __init__(self, host, port, webkitdir):
OriginalModPythonAdapter.__init__(self, host, port, webkitdir)
self._threadSafeStorage = {}
def threadSafeValue(self, name):
threadID = win32api.GetCurrentThreadId()
return self._threadSafeStorage[threadID, name]
def setThreadSafeValue(self, name, value):
threadID = win32api.GetCurrentThreadId()
self._threadSafeStorage[threadID, name] = value
def doneHeader(self):
return self.threadSafeValue('doneHeader')
def setDoneHeader(self, doneHeader):
self.setThreadSafeValue('doneHeader', doneHeader)
def headerData(self):
return self.threadSafeValue('headerData')
def setHeaderData(self, headerData):
self.setThreadSafeValue('headerData', headerData)
def request(self):
return self.threadSafeValue('request')
def setRequest(self, request):
self.setThreadSafeValue('request', request)
# Replace ModPythonAdapter with the Windows-safe version.
ModPythonAdapter = WinModPythonAdapter
def _adapter(req):
global __adapter
if __adapter is None:
appWorkDir = req.get_options()['AppWorkDir']
WEBWARE_ADDRESS_FILE = os.path.join(appWorkDir, 'address.text')
(host, port) = string.split(open(WEBWARE_ADDRESS_FILE).read(), ':')
port = int(port)
__adapter = ModPythonAdapter(host, port, appWorkDir)
return __adapter
def handler(req):
return _adapter(req).handler(req)
def pspHandler(req):
return _adapter(req).pspHandler(req)
def typehandler(req):
return _adapter(req).typehandler(req)
--- NEW FILE: ModSnakeAdapter.py ---
"""
WebWare adapter for mod_snake.
- Gifted to the WebWare project by Jon Travis (jtravis@...)
Usage:
Add the following lines to your httpd.conf file:
-- Snip here --
SnakeModuleDir /path/to/Webware
SnakeModuleDir /path/to/Webware/WebKit
SnakeModule ModSnakeAdapter.ModSnakeAdapter
WebwareAddress /path/to/Webware/WebKit/address.text
AddHandler webware .psp
<Location /wpy>
SetHandler webware
</Location>
-- Snip here --
Using the above configuration will tag all .psp files for processing
by the webware handler. All files in the /wpy location will also be
given the same handler.
To change the chunk size that the mod_snake adaptor uses for reading
and writing data, simply add the directive:
WebwareChunkSize 69
(or whatever your new chunksize is)
"""
import mod_snake
import string
import time
import os
from marshal import dumps, loads
from socket import *
try:
from cStringIO import StringIO
except:
from StringIO import StringIO
# Keys into the server-config dictionary
PER_SVR_SERVER = 0 # server_rec
PER_SVR_PORT = 1 # Port from webware address file
PER_SVR_ADDRESS = 2 # Address from webware address file
PER_SVR_CHUNKSIZE = 3 # Size of chunks to read and write
DEFAULT_CHUNKSIZE = 32 * 1024
from WebKit.Adapters.Adapter import Adapter
class ModSnakeAdapter(Adapter):
def __init__(self, module):
hooks = { "create_svr_config" : self.create_svr_config,
"content_handler" : self.content_handler,
}
for hook in hooks.keys():
module.add_hook(hook, hooks[hook])
directives = { "WebwareAddress" : (mod_snake.RSRC_CONF,mod_snake.TAKE1,
self.cmd_WebwareAddress),
"WebwareChunkSize":(mod_snake.RSRC_CONF,mod_snake.TAKE1,
self.cmd_WebwareChunkSize)
}
module.add_directives(directives)
Adapter.__init__(self,'')
def create_svr_config(self, server):
return { PER_SVR_SERVER : server,
PER_SVR_PORT : '8086',
PER_SVR_ADDRESS : 'localhost',
PER_SVR_CHUNKSIZE : DEFAULT_CHUNKSIZE,
}
def cmd_WebwareChunkSize(self, per_dir, per_svr, chunksize):
chunksize = int(chunksize)
if chunksize <= 0:
return "chunksize must be > 0"
per_svr[PER_SVR_CHUNKSIZE] = int(chunksize)
def cmd_WebwareAddress(self, per_dir, per_svr, file):
(host, port) = string.split(open(file).read(), ':')
per_svr[PER_SVR_PORT] = int(port)
per_svr[PER_SVR_ADDRESS] = host
self._webKitDir = os.path.dirname(file)
def content_handler(self, per_dir, per_svr, request):
if request.handler != 'webware':
return mod_snake.DECLINED
res = request.setup_client_block(mod_snake.REQUEST_CHUNKED_ERROR)
if res:
raise "Failed to setup client blocking method"
request.should_client_block()
strdata = StringIO()
while 1:
data, err = request.get_client_block(per_svr[PER_SVR_CHUNKSIZE])
if err <= 0:
break
strdata.write(data)
# Setup the subprocess environment, because os.environ suxx0r3z
request.add_common_vars()
request.add_cgi_vars()
env = {}
for key, val in request.subprocess_env.items():
env[key] = val
env["GATEWAY_INTERFACE"] = mod_snake.get_version()
response = self.transactWithAppServer(env, strdata.getvalue(), \
per_svr[PER_SVR_ADDRESS], per_svr[PER_SVR_PORT])
self.respond( request, response)
return mod_snake.OK
def respond(self, req, respdict):
headerend = string.find(respdict, "\n\n")
headers = respdict[:headerend]
for i in string.split(headers, "\n"):
header = string.split(i, ":")
req.headers_out[header[0]] = string.join(header[1:], ":")
if string.lower(header[0]) == 'content-type': req.content_type = header[1]
if string.lower(header[0]) == 'status': req.status = int(string.split(string.lstrip(header[1]),' ')[0])
req.send_http_header()
req.rwrite(respdict[headerend+2:])
--- NEW FILE: OneShot.cgi ---
#!/usr/bin/env python
# If the Webware installation is located somewhere else,
# then set the WebwareDir variable to point to it.
# For example, WebwareDir = '/Servers/Webware'
WebwareDir = None
# If you used the MakeAppWorkDir.py script to make a separate
# application working directory, specify it here.
AppWorkDir = None
try:
import os, sys
if WebwareDir:
sys.path.insert(1, WebwareDir)
else:
WebwareDir = os.path.dirname(os.path.dirname(os.getcwd()))
webKitDir = os.path.join(WebwareDir, 'WebKit')
if AppWorkDir is None:
AppWorkDir = webKitDir
else:
sys.path.insert(1, AppWorkDir)
import WebKit.Adapters.OneShotAdapter
WebKit.Adapters.OneShotAdapter.main(AppWorkDir)
except:
import string, sys, traceback
from time importx asctime, localtime, time
sys.stderr.write('[%s] [error] WebKit: Error in adapter\n' % asctime(localtime(time())))
sys.stderr.write('Error while executing script\n')
traceback.print_exc(file=sys.stderr)
output = apply(traceback.format_exception, sys.exc_info())
output = string.join(output, '')
output = string.replace(output, '&', '&')
output = string.replace(output, '<', '<')
output = string.replace(output, '>', '>')
output = string.replace(output, '"', '"')
sys.stdout.write('''Content-type: text/html
<html><body>
<p>ERROR
<p><pre>%s</pre>
</body></html>\n''' % output)
--- NEW FILE: OneShotAdapter.py ---
"""
OneShotAdapter.py
This is a special version of the CGIAdapter that doesn't require a persistent AppServer process. This is mostly useful during development when repeated changes to classes forces the developer to restart the app server to make the changes take effect.
An example, URL:
http://127.0.0.1/OneShot.cgi/MyPage
"""
# 2000-08-07 ce: For accuracy purposes, we want to record the timestamp as early as possible.
import time
_timestamp = time.time()
# 2000-08-07 ce: We have to reassign sys.stdout *immediately* because it's referred to as a default parameter value in Configurable.py which happens to be our ancestor class as well as the ancestor class of AppServer and Application. The Configurable method that uses sys.stdout for a default parameter value must not execute before we rewire sys.stdout. Tricky, tricky.
# 2000-12-04 ce: Couldn't this be fixed by Configurable taking None as the default and then using sys.stdout if arg==None?
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
import sys
_real_stdout = sys.stdout
sys.stdout = _console = StringIO() # to capture the console output of the application
import os, string
from Adapter import *
from MiscUtils.Funcs import charWrap
from WebUtils.Funcs import htmlEncode
class OneShotAdapter(Adapter):
def defaultConfig(self):
config = Adapter.defaultConfig(self)
config.update({
'ShowConsole': 0,
'ConsoleWidth': 80, # use 0 to turn off
'ConsoleHangingIndent': 4,
})
return config
def run(self):
try:
# myInput = ''
# if os.environ.has_key('CONTENT_LENGTH'):
# length = int(os.environ['CONTENT_LENGTH'])
# if length:
# myInput = sys.stdin.read(length)
#myInput = sys.stdin.read()
# MS Windows: no special translation of end-of-lines
if os.name=='nt':
import msvcrt
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
dict = {
'format': 'CGI',
'time': _timestamp,
'environ': os.environ.data, # ce: a little tricky. We use marshal which only works on built-in types, so we need environ's dictionary
# 'input': myInput,
'input': sys.stdin,
}
print 'ONE SHOT MODE\n'
from OneShotAppServer import OneShotAppServer
appSvr = OneShotAppServer(self._webKitDir)
# It is important to call transaction.die() after using it, rather than just
# letting it fall out of scope, to avoid circular references
from WebKit.ASStreamOut import ASStreamOut
rs = ASStreamOut()
transaction = appSvr.dispatchRawRequest(dict, rs)
rs.close()
transaction.die()
del transaction
appSvr.shutDown()
appSvr = None
sys.stdout = _real_stdout
# MS Windows: no special translation of end-of-lines
if os.name=='nt':
import msvcrt
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
write = sys.stdout.write
write(rs._buffer)
if self.setting('ShowConsole'):
# show the contents of the console, but only if we
# are serving up an HTML file
endheaders = string.find(rs._buffer,"\r\n\r\n")
if endheaders == None:
endheaders = string.find(rs._buffer,"\n\n")
if not endheaders:
print "No Headers Found"
return
headers = string.split(rs._buffer[:endheaders],"\n")
entries = []
for i in headers:
entries.append(string.split(i, ":"))
found = 0
for name, value in entries:
if name.lower()=='content-type':
found = 1
break
if found and string.strip(string.lower(value))=='text/html':
self.showConsole(_console.getvalue())
except:
import traceback
sys.stderr.write('[%s] [error] WebKit.OneShotAdapter: Error while responding to request (unknown)\n' % (time.asctime(time.localtime(time.time()))))
sys.stderr.write('Python exception:\n')
traceback.print_exc(file=sys.stderr)
output = apply(traceback.format_exception, sys.exc_info())
output = string.join(output, '')
output = string.replace(output, '&', '&')
output = string.replace(output, '<', '<')
output = string.replace(output, '>', '>')
output = string.replace(output, '"', '"')
sys.stdout.write('''Content-type: text/html
<html><body>
<p><pre>ERROR
%s</pre>
</body></html>\n''' % output)
def showConsole(self, contents):
width = self.setting('ConsoleWidth')
if width:
contents = charWrap(contents, self.setting('ConsoleWidth'), self.setting('ConsoleHangingIndent'))
contents = htmlEncode(contents)
sys.stdout.write('<br><p><table><tr><td bgcolor=#EEEEEE><pre>%s</pre></td></tr></table>' % contents)
def main(webKitDir=None):
if webKitDir is None:
import os
webKitDir = os.getcwd()
try:
OneShotAdapter(webKitDir).run()
except:
import traceback
sys.stderr.write('[%s] [error] OneShotAdapter: Error while responding to request (unknown)\n' % (time.asctime(time.localtime(time.time()))))
sys.stderr.write('Python exception:\n')
traceback.print_exc(file=sys.stderr)
output = apply(traceback.format_exception, sys.exc_info())
output = string.join(output, '')
output = string.replace(output, '&', '&')
output = string.replace(output, '<', '<')
output = string.replace(output, '>', '>')
output = string.replace(output, '"', '"')
sys.stdout.write('''Content-type: text/html
<html><body>
<p><pre>ERROR
%s</pre>
</body></html>\n''' % output)
--- NEW FILE: WebKit.cgi ---
#!/usr/bin/env python
# If the Webware installation is located somewhere else,
# then set the WebwareDir variable to point to it.
# For example, WebwareDir = '/Servers/Webware'
WebwareDir = None
# If you used the MakeAppWorkDir.py script to make a separate
# application working directory, specify it here.
AppWorkDir = None
try:
import os, sys
if WebwareDir:
sys.path.insert(1, WebwareDir)
else:
WebwareDir = os.path.dirname(os.path.dirname(os.getcwd()))
webKitDir = os.path.join(WebwareDir, 'WebKit')
if AppWorkDir is None:
AppWorkDir = webKitDir
else:
sys.path.insert(1, AppWorkDir)
try:
import WebKit.Adapters.CGIAdapter
except ImportError:
cgiAdapter = os.path.join(webKitDir, 'CGIAdapter.py')
if not os.path.exists(cgiAdapter):
sys.stdout.write("""\
Content-type: text/html
<html><body>
<p>ERROR
<p>I can't find the file %s.
<p>If that file really doesn't exist, then you need to edit WebKit.cgi so
that WebwareDir points to the actual Webware installation directory.
<p>If that file does exist, then its permissions probably need to be modified
with chmod so that WebKit.cgi can read it. You may also need to modify
the permissions on parent directories.
""" % cgiAdapter)
else:
raise
else:
WebKit.CGIAdapter.main(AppWorkDir)
except:
import string, sys, traceback
from time import asctime, localtime, time
sys.stderr.write('[%s] [error] WebKit: Error in adapter\n' % asctime(localtime(time())))
sys.stderr.write('Error while executing script\n')
traceback.print_exc(file=sys.stderr)
output = apply(traceback.format_exception, sys.exc_info())
output = string.join(output, '')
output = string.replace(output, '&', '&')
output = string.replace(output, '<', '<')
output = string.replace(output, '>', '>')
output = string.replace(output, '"', '"')
sys.stdout.write('''Content-type: text/html
<html><body>
<p>ERROR
<p><pre>%s</pre>
</body></html>\n''' % output)
--- NEW FILE: __init__.py ---
#
--- NEW FILE: fcgi.py ---
#!/bin/env python
#------------------------------------------------------------------------
# Copyright (c) 1998 by Total Control Software
# All Rights Reserved
#------------------------------------------------------------------------
#
# Module Name: fcgi.py
#
# Description: Handles communication with the FastCGI module of the
# web server without using the FastCGI developers kit, but
# will also work in a non-FastCGI environment, (straight CGI.)
# This module was originally fetched from someplace on the
# Net (I don't remember where and I can't find it now...) and
# has been significantly modified to fix several bugs, be more
# readable, more robust at handling large CGI data and return
# document sizes, and also to fit the model that we had previously
# used for FastCGI.
#
# WARNING: If you don't know what you are doing, don't tinker with this
# module!
#
# Creation Date: 1/30/98 2:59:04PM
#
# License: This is free software. You may use this software for any
# purpose including modification/redistribution, so long as
# this header remains intact and that you do not claim any
# rights of ownership or authorship of this software. This
# software has been tested, but no warranty is expressed or
# implied.
#
#------------------------------------------------------------------------
import os, sys, string, socket, errno
from cStringIO import StringIO
import cgi
#---------------------------------------------------------------------------
# Set various FastCGI constants
# Maximum number of requests that can be handled
FCGI_MAX_REQS=1
FCGI_MAX_CONNS = 1
# Supported version of the FastCGI protocol
FCGI_VERSION_1 = 1
# Boolean: can this application multiplex connections?
FCGI_MPXS_CONNS=0
# Record types
FCGI_BEGIN_REQUEST = 1 ; FCGI_ABORT_REQUEST = 2 ; FCGI_END_REQUEST = 3
FCGI_PARAMS = 4 ; FCGI_STDIN = 5 ; FCGI_STDOUT = 6
FCGI_STDERR = 7 ; FCGI_DATA = 8 ; FCGI_GET_VALUES = 9
FCGI_GET_VALUES_RESULT = 10
FCGI_UNKNOWN_TYPE = 11
FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
# Types of management records
ManagementTypes = [FCGI_GET_VALUES]
FCGI_NULL_REQUEST_ID=0
# Masks for flags component of FCGI_BEGIN_REQUEST
FCGI_KEEP_CONN = 1
# Values for role component of FCGI_BEGIN_REQUEST
FCGI_RESPONDER = 1 ; FCGI_AUTHORIZER = 2 ; FCGI_FILTER = 3
# Values for protocolStatus component of FCGI_END_REQUEST
FCGI_REQUEST_COMPLETE = 0 # Request completed nicely
FCGI_CANT_MPX_CONN = 1 # This app can't multiplex
FCGI_OVERLOADED = 2 # New request rejected; too busy
FCGI_UNKNOWN_ROLE = 3 # Role value not known
error = 'fcgi.error'
#---------------------------------------------------------------------------
# The following function is used during debugging; it isn't called
# anywhere at the moment
def error(msg):
"Append a string to /tmp/err"
errf=open('/tmp/err', 'a+')
errf.write(msg+'\n')
errf.close()
#---------------------------------------------------------------------------
class record:
"Class representing FastCGI records"
def __init__(self):
self.version = FCGI_VERSION_1
self.recType = FCGI_UNKNOWN_TYPE
self.reqId = FCGI_NULL_REQUEST_ID
self.content = ""
#----------------------------------------
def readRecord(self, sock):
s = map(ord, sock.recv(8))
self.version, self.recType, paddingLength = s[0], s[1], s[6]
self.reqId, contentLength = (s[2]<<8)+s[3], (s[4]<<8)+s[5]
self.content = ""
while len(self.content) < contentLength:
data = sock.recv(contentLength - len(self.content))
self.content = self.content + data
if paddingLength != 0:
padding = sock.recv(paddingLength)
# Parse the content information
c = self.content
if self.recType == FCGI_BEGIN_REQUEST:
self.role = (ord(c[0])<<8) + ord(c[1])
self.flags = ord(c[2])
elif self.recType == FCGI_UNKNOWN_TYPE:
self.unknownType = ord(c[0])
elif self.recType == FCGI_GET_VALUES or self.recType == FCGI_PARAMS:
self.values={}
pos=0
while pos < len(c):
name, value, pos = readPair(c, pos)
self.values[name] = value
elif self.recType == FCGI_END_REQUEST:
b = map(ord, c[0:4])
self.appStatus = (b[0]<<24) + (b[1]<<16) + (b[2]<<8) + b[3]
self.protocolStatus = ord(c[4])
#----------------------------------------
def writeRecord(self, sock):
content = self.content
if self.recType == FCGI_BEGIN_REQUEST:
content = chr(self.role>>8) + chr(self.role & 255) + chr(self.flags) + 5*'\000'
elif self.recType == FCGI_UNKNOWN_TYPE:
content = chr(self.unknownType) + 7*'\000'
elif self.recType==FCGI_GET_VALUES or self.recType==FCGI_PARAMS:
content = ""
for i in self.values.keys():
content = content + writePair(i, self.values[i])
elif self.recType==FCGI_END_REQUEST:
v = self.appStatus
content = chr((v>>24)&255) + chr((v>>16)&255) + chr((v>>8)&255) + chr(v&255)
content = content + chr(self.protocolStatus) + 3*'\000'
cLen = len(content)
eLen = (cLen + 7) & (0xFFFF - 7) # align to an 8-byte boundary
padLen = eLen - cLen
hdr = [ self.version,
self.recType,
self.reqId >> 8,
self.reqId & 255,
cLen >> 8,
cLen & 255,
padLen,
0]
hdr = string.joinfields(map(chr, hdr), '')
sock.send(hdr + content + padLen*'\000')
#---------------------------------------------------------------------------
def readPair(s, pos):
nameLen=ord(s[pos]) ; pos=pos+1
if nameLen & 128:
b=map(ord, s[pos:pos+3]) ; pos=pos+3
nameLen=((nameLen&127)<<24) + (b[0]<<16) + (b[1]<<8) + b[2]
valueLen=ord(s[pos]) ; pos=pos+1
if valueLen & 128:
b=map(ord, s[pos:pos+3]) ; pos=pos+3
valueLen=((valueLen&127)<<24) + (b[0]<<16) + (b[1]<<8) + b[2]
return ( s[pos:pos+nameLen], s[pos+nameLen:pos+nameLen+valueLen],
pos+nameLen+valueLen )
#---------------------------------------------------------------------------
def writePair(name, value):
l=len(name)
if l<128: s=chr(l)
else:
s=chr(128|(l>>24)&255) + chr((l>>16)&255) + chr((l>>8)&255) + chr(l&255)
l=len(value)
if l<128: s=s+chr(l)
else:
s=s+chr(128|(l>>24)&255) + chr((l>>16)&255) + chr((l>>8)&255) + chr(l&255)
return s + name + value
#---------------------------------------------------------------------------
def HandleManTypes(r, conn):
if r.recType == FCGI_GET_VALUES:
r.recType = FCGI_GET_VALUES_RESULT
v={}
vars={'FCGI_MAX_CONNS' : FCGI_MAX_CONNS,
'FCGI_MAX_REQS' : FCGI_MAX_REQS,
'FCGI_MPXS_CONNS': FCGI_MPXS_CONNS}
for i in r.values.keys():
if vars.has_key(i): v[i]=vars[i]
r.values=vars
r.writeRecord(conn)
#---------------------------------------------------------------------------
#---------------------------------------------------------------------------
_isFCGI = 1 # assume it is until we find out for sure
def isFCGI():
global _isFCGI
return _isFCGI
#---------------------------------------------------------------------------
_init = None
_sock = None
class FCGI:
def __init__(self):
self.haveFinished = 0
if _init == None:
_startup()
if not isFCGI():
self.haveFinished = 1
self.inp, self.out, self.err, self.env = \
sys.stdin, sys.stdout, sys.stderr, os.environ
return
if os.environ.has_key('FCGI_WEB_SERVER_ADDRS'):
good_addrs=string.split(os.environ['FCGI_WEB_SERVER_ADDRS'], ',')
good_addrs=map(string.strip(good_addrs)) # Remove whitespace
else:
good_addrs=None
self.conn, addr=_sock.accept()
stdin, data="", ""
self.env = {}
self.requestId=0
remaining=1
# Check if the connection is from a legal address
if good_addrs!=None and addr not in good_addrs:
raise error, 'Connection from invalid server!'
while remaining:
r=record(); r.readRecord(self.conn)
if r.recType in ManagementTypes:
HandleManTypes(r, self.conn)
elif r.reqId==0:
# Oh, poopy. It's a management record of an unknown
# type. Signal the error.
r2=record()
r2.recType=FCGI_UNKNOWN_TYPE ; r2.unknownType=r.recType
r2.writeRecord(self.conn)
continue # Charge onwards
# Ignore requests that aren't active
elif r.reqId != self.requestId and r.recType != FCGI_BEGIN_REQUEST:
continue
# If we're already doing a request, ignore further BEGIN_REQUESTs
elif r.recType == FCGI_BEGIN_REQUEST and self.requestId != 0:
continue
# Begin a new request
if r.recType == FCGI_BEGIN_REQUEST:
self.requestId = r.reqId
if r.role == FCGI_AUTHORIZER: remaining=1
elif r.role == FCGI_RESPONDER: remaining=2
elif r.role == FCGI_FILTER: remaining=3
elif r.recType == FCGI_PARAMS:
if r.content == "":
remaining=remaining-1
else:
for i in r.values.keys():
self.env[i] = r.values[i]
elif r.recType == FCGI_STDIN:
if r.content == "":
remaining=remaining-1
else:
stdin=stdin+r.content
elif r.recType==FCGI_DATA:
if r.content == "":
remaining=remaining-1
else:
data=data+r.content
# end of while remaining:
self.inp = sys.stdin = StringIO(stdin)
self.err = sys.stderr = StringIO()
self.out = sys.stdout = StringIO()
self.data = StringIO(data)
def __del__(self):
self.Finish()
def Finish(self, status=0):
if not self.haveFinished:
self.haveFinished = 1
self.err.seek(0,0)
self.out.seek(0,0)
r=record()
r.recType = FCGI_STDERR
r.reqId = self.requestId
data = self.err.read()
while data:
chunk, data = self.getNextChunk(data)
r.content = chunk
r.writeRecord(self.conn)
r.content="" ; r.writeRecord(self.conn) # Terminate stream
r.recType = FCGI_STDOUT
data = self.out.read()
while data:
chunk, data = self.getNextChunk(data)
r.content = chunk
r.writeRecord(self.conn)
r.content="" ; r.writeRecord(self.conn) # Terminate stream
r=record()
r.recType=FCGI_END_REQUEST
r.reqId=self.requestId
r.appStatus=status
r.protocolStatus=FCGI_REQUEST_COMPLETE
r.writeRecord(self.conn)
self.conn.close()
def getFieldStorage(self):
method = 'GET'
if self.env.has_key('REQUEST_METHOD'):
method = string.upper(self.env['REQUEST_METHOD'])
if method == 'GET':
return cgi.FieldStorage(environ=self.env, keep_blank_values=1)
else:
return cgi.FieldStorage(fp=self.inp, environ=self.env, keep_blank_values=1)
def getNextChunk(self, data):
chunk = data[:8192]
data = data[8192:]
return chunk, data
Accept = FCGI # alias for backwards compatibility
#---------------------------------------------------------------------------
def _startup():
global _init
_init = 1
try:
s=socket.fromfd(sys.stdin.fileno(), socket.AF_INET,
socket.SOCK_STREAM)
s.getpeername()
except socket.error, (err, errmsg):
if err!=errno.ENOTCONN: # must be a non-fastCGI environment
global _isFCGI
_isFCGI = 0
return
global _sock
_sock = s
#---------------------------------------------------------------------------
def _test():
counter=0
try:
while isFCGI():
req = Accept()
counter=counter+1
try:
fs = req.getFieldStorage()
size = string.atoi(fs['size'].value)
doc = ['*' * size]
except:
doc = ['<HTML><HEAD><TITLE>FCGI TestApp</TITLE></HEAD>\n<BODY>\n']
doc.append('<H2>FCGI TestApp</H2><P>')
doc.append('<b>request count</b> = %d<br>' % counter)
doc.append('<b>pid</b> = %s<br>' % os.getpid())
if req.env.has_key('CONTENT_LENGTH'):
cl = string.atoi(req.env['CONTENT_LENGTH'])
doc.append('<br><b>POST data (%s):</b><br><pre>' % cl)
keys = fs.keys()
keys.sort()
for k in keys:
val = fs[k]
if type(val) == type([]):
doc.append(' <b>%-15s :</b> %s\n' % (k, val))
else:
doc.append(' <b>%-15s :</b> %s\n' % (k, val.value))
doc.append('</pre>')
doc.append('<P><HR><P><pre>')
keys = req.env.keys()
keys.sort()
for k in keys:
doc.append('<b>%-20s :</b> %s\n' % (k, req.env[k]))
doc.append('\n</pre><P><HR>\n')
doc.append('</BODY></HTML>\n')
doc = string.join(doc, '')
req.out.write('Content-length: %s\r\n'
'Content-type: text/html\r\n'
'Cache-Control: no-cache\r\n'
'\r\n'
% len(doc))
req.out.write(doc)
req.Finish()
except:
import traceback
f = open('traceback', 'w')
traceback.print_exc( file = f )
# f.write('%s' % doc)
if __name__=='__main__':
#import pdb
#pdb.run('_test()')
_test()
--- NEW FILE: lrwplib.py ---
#!python
#------------------------------------------------------------------------
# Copyright (c) 1997 by Total Control Software
# All Rights Reserved
#------------------------------------------------------------------------
#
# Module Name: lrwplib.py
#
# Description: Class LRWP handles the connection to the LRWP agent in
# Xitami. This class can be used standalone or derived
# from to override behavior.
#
# Creation Date: 11/11/97 8:36:21PM
#
# License: This is free software. You may use this software for any
# purpose including modification/redistribution, so long as
# this header remains intact and that you do not claim any
# rights of ownership or authorship of this software. This
# software has been tested, but no warranty is expressed or
# implied.
#
#------------------------------------------------------------------------
import sys, socket, string
import os, cgi
from cStringIO import StringIO
__version__ = '1.0'
LENGTHSIZE = 9
LENGTHFMT = '%09d'
#---------------------------------------------------------------------------
# Exception objects
ConnectError = 'lrwp.ConnectError'
ConnectionClosed = 'lrwp.ConnectionClosed'
SocketError = 'lrwp.SocketError'
#---------------------------------------------------------------------------
class Request:
'''
Encapsulates the request/response IO objects and CGI-Environment.
An instance of this class is returned
'''
def __init__(self, lrwp):
self.inp = lrwp.inp
self.out = lrwp.out
self.err = lrwp.out
self.env = lrwp.env
self.lrwp = lrwp
def finish(self):
self.lrwp.finish()
def getFieldStorage(self):
method = 'POST'
if self.env.has_key('REQUEST_METHOD'):
method = string.upper(self.env['REQUEST_METHOD'])
if method == 'GET':
return cgi.FieldStorage(environ=self.env, keep_blank_values=1)
else:
return cgi.FieldStorage(fp=self.inp, environ=self.env, keep_blank_values=1)
#---------------------------------------------------------------------------
class LRWP:
def __init__(self, name, host, port, vhost='', filter='', useStdio=0):
'''
Construct an LRWP object.
name The name or alias of this request handler. Requests
matching http://host/name will be directed to this
LRWP object.
host Hostname or IP address to connect to.
port Port number to connect on.
vhost If this handler is to only be available to a specific
virtual host, name it here.
filter A space separated list of file extenstions that should
be directed to this handler in filter mode. (Not yet
supported.)
'''
self.name = name
self.host = host
self.port = port
self.vhost = vhost
self.filter = filter
self.useStdio = useStdio
self.sock = None
self.env = None
self.inp = None
self.out = None
#----------------------------------------
def connect(self):
'''
Establishes the connection to the web server, using the parameters given
at construction.
'''
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((self.host, self.port)) # Changed to two item tuple with Python 2.0 - I think!
self.sock.send("%s\xFF%s\xFF%s" % (self.name, self.vhost, self.filter) )
buf = self.sock.recv(1024)
if buf != 'OK':
raise ConnectError, buf
except socket.error, val:
raise SocketError, val
#----------------------------------------
def acceptRequest(self):
'''
Wait for, and accept a new request from the Web Server. Reads the
name=value pairs that comprise the CGI environment, followed by the
post data, if any. Constructs and returns a Request object.
'''
if self.out:
self.finish()
try:
# get the length of the environment data
data = self.recvBlock(LENGTHSIZE)
if not data: # server closed down
raise ConnectionClosed
length = string.atoi(data)
# and then the environment data
data = self.recvBlock(length)
if not data: # server closed down
raise ConnectionClosed
data = string.split(data, '\000')
self.env = {}
for x in data:
x = string.split(x, '=')
if len(x) > 1:
self.env[x[0]] = string.join(x[1:], '=')
# now get the size of the POST data
data = self.recvBlock(LENGTHSIZE)
if not data: # server closed down
raise ConnectionClosed
length = string.atoi(data)
# and the POST data...
if length:
data = self.recvBlock(length)
if not data: # server closed down
raise ConnectionClosed
self.inp = StringIO(data)
else:
self.inp = StringIO()
self.out = StringIO()
# do the switcheroo on the sys IO objects, etc.
if self.useStdio:
self.saveStdio = sys.stdin, sys.stdout, sys.stderr, os.environ
sys.stdin, sys.stdout, sys.stderr, os.environ = \
self.inp, self.out, self.out, self.env
return Request(self)
except socket.error, val:
raise SocketError, val
#----------------------------------------
def recvBlock(self, size):
'''
Pull an exact number of bytes from the socket, taking into
account the possibility of multiple packets...
'''
numRead = 0
data = []
while numRead < size:
buf = self.sock.recv(size - numRead);
if not buf:
return ''
data.append(buf)
numRead = numRead + len(buf)
return string.join(data, '')
#----------------------------------------
def finish(self):
'''
Complete the request and send the output back to the webserver.
'''
doc = self.out.getvalue()
size = LENGTHFMT % (len(doc), )
try:
self.sock.send(size)
self.sock.send(doc)
except socket.error, val:
raise SocketError, val
if self.useStdio:
sys.stdin, sys.stdout, sys.stderr, os.environ = self.saveStdio
self.env = None
self.inp = None
self.out = None
#----------------------------------------
def close(self):
'''
Close the LRWP connection to the web server.
'''
self.sock.close()
self.sock = None
self.env = None
self.inp = None
self.out = None
#---------------------------------------------------------------------------
def _test():
import os, time
eol = '\r\n'
appname = 'testapp1'
vhost = ''
host = 'localhost'
port = 5081
if len(sys.argv) > 1:
appname = sys.argv[1]
if len(sys.argv) > 2:
host = sys.argv[2]
if len(sys.argv) > 3:
port = string.atoi(sys.argv[3])
if len(sys.argv) > 4:
vhost = sys.argv[4]
lrwp = LRWP(appname, host, port, vhost)
lrwp.connect()
count = 0
while count < 5: # exit after servicing 5 requests
req = lrwp.acceptRequest()
doc = ['<HTML><HEAD><TITLE>LRWP TestApp ('+appname+')</TITLE></HEAD>\n<BODY>\n']
count = count + 1
doc.append('<H2>LRWP test app ('+appname+')</H2><P>')
doc.append('<b>request count</b> = %d<br>' % (count, ))
if hasattr(os, 'getpid'):
doc.append('<b>pid</b> = %s<br>' % (os.getpid(), ))
doc.append('<br><b>post data:</b> ' + req.inp.read() + '<br>')
doc.append('<P><HR><P><pre>')
keys = req.env.keys()
keys.sort()
for k in keys:
doc.append('<b>%-20s :</b> %s\n' % (k, req.env[k]))
doc.append('\n</pre><P><HR>\n')
doc.append('</BODY></HTML>\n')
req.out.write('Content-type: text/html' + eol)
req.out.write(eol)
req.out.write(string.join(doc, ''))
req.finish()
lrwp.close()
if __name__ == '__main__':
#import pdb
#pdb.run('_test()')
_test()
|