Commit de577f0b authored by Fred Drake's avatar Fred Drake

ZServer has moved into lib/python/; it is no longer needed at the top level.

parent 392f1997
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
import time, thread
class DebugLogger:
"""
Logs debugging information about how ZServer is handling requests
and responses. This log can be used to help locate troublesome requests.
The format is:
<code> <request id> <time> <data>
where:
'code' is B for begin, I for received input, A for received output,
E for sent output.
'request id' is a unique request id.
'time' is the time in localtime ISO format.
'data' is the HTTP method and the PATH INFO for B, the size of the input
for I, the HTTP status code and the size of the output for A, or
nothing for E.
Note: This facility will be probably be adapted to the zLOG framework.
"""
def __init__(self, filename):
self.filename = filename
self.file=open(filename, 'a+b')
l=thread.allocate_lock()
self._acquire=l.acquire
self._release=l.release
self.log('U', '000000000', 'System startup')
def reopen(self):
self.file.close()
self.file=open(self.filename, 'a+b')
self.log('U', '000000000', 'Logfile reopened')
def log(self, code, request_id, data=''):
self._acquire()
try:
t=time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime(time.time()))
self.file.write(
'%s %s %s %s\n' % (code, request_id, t, data)
)
self.file.flush()
finally:
self._release()
def log(*args): pass
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""
FTP Request class for FTP server.
The FTP Request does the dirty work of turning an FTP request into something
that ZPublisher can understand.
"""
from ZPublisher.HTTPRequest import HTTPRequest
from cStringIO import StringIO
import os
from base64 import encodestring
import re
class FTPRequest(HTTPRequest):
def __init__(self, path, command, channel, response, stdin=None,
environ=None,globbing=None,recursive=0):
# we need to store the globbing information to pass it
# to the ZPublisher and the manage_FTPlist function
# (ajung)
self.globbing = globbing
self.recursive= recursive
if stdin is None: stdin=StringIO()
if environ is None:
environ=self._get_env(path, command, channel, stdin)
self._orig_env=environ
HTTPRequest.__init__(self, stdin, environ, response, clean=1)
# support for cookies and cookie authentication
self.cookies=channel.cookies
if not self.cookies.has_key('__ac') and channel.userid != 'anonymous':
self.other['__ac_name']=channel.userid
self.other['__ac_password']=channel.password
for k,v in self.cookies.items():
if not self.other.has_key(k):
self.other[k]=v
def retry(self):
self.retry_count=self.retry_count+1
r=self.__class__(stdin=self.stdin,
environ=self._orig_env,
response=self.response.retry(),
channel=self, # For my cookies
)
return r
def _get_env(self, path, command, channel, stdin):
"Returns a CGI style environment"
env={}
env['SCRIPT_NAME']='/%s' % channel.module
env['REQUEST_METHOD']='GET' # XXX what should this be?
env['SERVER_SOFTWARE']=channel.server.SERVER_IDENT
if channel.userid != 'anonymous':
env['HTTP_AUTHORIZATION']='Basic %s' % re.sub('\012','',
encodestring('%s:%s' % (channel.userid, channel.password)))
env['SERVER_NAME']=channel.server.hostname
env['SERVER_PORT']=str(channel.server.port)
env['REMOTE_ADDR']=channel.client_addr[0]
env['GATEWAY_INTERFACE']='CGI/1.1' # that's stretching it ;-)
# FTP commands
#
if type(command)==type(()):
args=command[1:]
command=command[0]
if command in ('LST','CWD','PASS'):
env['PATH_INFO']=self._join_paths(channel.path,
path, 'manage_FTPlist')
elif command in ('MDTM','SIZE'):
env['PATH_INFO']=self._join_paths(channel.path,
path, 'manage_FTPstat')
elif command=='RETR':
env['PATH_INFO']=self._join_paths(channel.path,
path, 'manage_FTPget')
elif command in ('RMD','DELE'):
env['PATH_INFO']=self._join_paths(channel.path,
path, 'manage_delObjects')
env['QUERY_STRING']='ids=%s' % args[0]
elif command=='MKD':
env['PATH_INFO']=self._join_paths(channel.path,
path, 'manage_addFolder')
env['QUERY_STRING']='id=%s' % args[0]
elif command=='RNTO':
env['PATH_INFO']=self._join_paths(channel.path,
path, 'manage_renameObject')
env['QUERY_STRING']='id=%s&new_id=%s' % (args[0],args[1])
elif command=='STOR':
env['PATH_INFO']=self._join_paths(channel.path, path)
env['REQUEST_METHOD']='PUT'
env['CONTENT_LENGTH']=len(stdin.getvalue())
else:
env['PATH_INFO']=self._join_paths(channel.path, path, command)
# Fake in globbing information
env['GLOBBING'] = self.globbing
env['FTP_RECURSIVE'] = self.recursive
return env
def _join_paths(self,*args):
path=apply(os.path.join,args)
path=os.path.normpath(path)
if os.sep != '/':
path=path.replace(os.sep,'/')
return path
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""
Response class for the FTP Server.
"""
from ZServer.HTTPResponse import ZServerHTTPResponse
from PubCore.ZEvent import Wakeup
from cStringIO import StringIO
import marshal
class FTPResponse(ZServerHTTPResponse):
"""
Response to an FTP command
"""
def __str__(self):
# return ZServerHTTPResponse.__str__(self)
# ZServerHTTPResponse.__str__(self) return HTTP headers
# Why should be send them to the FTP client ??? (ajung)
return ''
def outputBody(self):
pass
def setCookie(self, name, value, **kw):
self.cookies[name]=value
def appendCookie(self, name, value):
self.cookies[name]=self.cookies[name] + value
def expireCookie(self, name, **kw):
if self.cookies.has_key(name):
del self.cookies[name]
def _cookie_list(self):
return []
def _marshalledBody(self):
return marshal.loads(self.body)
def setMessage(self, message):
self._message = message
def getMessage(self):
return getattr(self, '_message', '')
class CallbackPipe:
"""
Sends response object to a callback. Doesn't write anything.
The callback takes place in Medusa's thread, not the request thread.
"""
def __init__(self, callback, args):
self._callback=callback
self._args=args
self._producers=[]
def close(self):
pass
def write(self, text, l=None):
if text:
self._producers.append(text)
def finish(self, response):
self._response=response
Wakeup(self.apply) # move callback to medusas thread
def apply(self):
result=apply(self._callback, self._args+(self._response,))
# break cycles
self._callback=None
self._response=None
self._args=None
return result
def make_response(channel, callback, *args):
# XXX should this be the FTPResponse constructor instead?
r=FTPResponse(stdout=CallbackPipe(callback, args), stderr=StringIO())
r.setHeader('content-type','text/plain')
r.cookies=channel.cookies
return r
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
# Medusa ICP server
#
# Why would you want to use this?
# see http://www.zope.org/Members/htrd/icp/intro
import sys, string, os, socket, errno, struct
import asyncore
from medusa import counter
ICP_OP_QUERY = 1
ICP_OP_HIT = 2
ICP_OP_MISS = 3
ICP_OP_ERR = 4
ICP_OP_MISS_NOFETCH = 21
ICP_OP_DENIED = 22
class BaseICPServer(asyncore.dispatcher):
REQUESTS_PER_LOOP = 4
_shutdown = 0
def __init__ (self,ip,port):
asyncore.dispatcher.__init__(self)
self.create_socket (socket.AF_INET, socket.SOCK_DGRAM)
self.set_reuse_addr()
self.bind((ip,port))
if ip=='':
addr = 'any'
else:
addr = ip
self.log_info('ICP server started\n\tAddress: %s\n\tPort: %s' % (addr,port) )
def clean_shutdown_control(self,phase,time_in_this_phase):
if phase==1:
# Stop responding to requests.
if not self._shutdown:
self._shutdown = 1
self.log_info('shutting down ICP')
if time_in_this_phase<2.0:
# We have not yet been deaf long enough for our front end proxies to notice.
# Do not allow shutdown to proceed yet
return 1
else:
# Shutdown can proceed. We dont need a socket any more
self.close()
return 0
def handle_read(self):
for i in range(self.REQUESTS_PER_LOOP):
try:
request, whence = self.socket.recvfrom(16384)
except socket.error,e:
if e[0]==errno.EWOULDBLOCK:
break
else:
raise
else:
if self.check_whence(whence):
reply = self.calc_reply(request)
if reply:
self.socket.sendto(reply,whence)
def readable(self):
return not self._shutdown
def writable(self):
return 0
def handle_write (self):
self.log_info ('unexpected write event', 'warning')
def handle_error (self): # don't close the socket on error
(file,fun,line), t, v, tbinfo = asyncore.compact_traceback()
self.log_info('Problem in ICP (%s:%s %s)' % (t, v, tbinfo),
'error')
def check_whence(self,whence):
return 1
def calc_reply(self,request):
if len(request)>20:
opcode,version,length,number,options,opdata,junk = struct.unpack('!BBHIIII',request[:20])
if version==2:
if opcode==ICP_OP_QUERY:
if len(request)!=length:
out_opcode = ICP_OP_ERR
else:
url = request[24:]
if url[-1:]=='\x00':
url = url[:-1]
out_opcode = self.check_url(url)
return struct.pack('!BBHIIII',out_opcode,2,20,number,0,0,0)
def check_url(self,url):
# derived classes replace this with a more
# useful policy
return ICP_OP_MISS
class ICPServer(BaseICPServer):
# Products that want to do special ICP handling should .append their hooks into
# this list. Each hook is called in turn with the URL as a parameter, and
# they must return an ICP_OP code from above or None. The first
# non-None return is used as the ICP response
hooks = []
def check_url(self,url):
for hook in self.hooks:
r = hook(url)
if r is not None:
return r
return ICP_OP_MISS
ZServer Installation
--------------------
Requirements
ZServer comes with Zope 2. Though ZServer can be used with earlier
versions of Zope, this is not supported and not covered by this
document.
Configuration
To run ZServer you simply execute the z2.py start script which is
located in your Zope directory. You can pass commandline arguments
to the start script in order to run Zope with different options. In
order to see a list of options use the '-h' help option.
Here's an example of use::
$ python2.1 z2.py -w 8888 -f "" -p "" -m "" &
This example starts Zope using a web server on port 8888. It does
not start and FTP server, or a PCGI server, or a Monitor server. It
also starts the server running in the backaground.
Shell scripts and batch files
You may also wish to create a shell script (or batch file under
win32) to set environment variables (such as ZOPE_DEBUG_MODE and
PYTHONHOME) and run the start script.
Here's an example shell script for a binary Zope release::
ZOPE_DEBUG_MODE=1
export ZOPE_DEBUG_MODE
PYTHONHOME=/home/Zope
export PYTHONHOME
/home/Zope/bin/python /home/Zope/z2.py -w 9673 &
Note: If ZServer fails because it can't find some standard Python
libaries there's a good bet that you need to set the PYTHONHOME as
shown above.
Here's an example batch file::
set ZOPE_DEBUG_MODE=1
"\Program Files\Zope\bin\python" "\Program Files\Zope\z2.py -w
8888 -f 8021"
Now you're ready to go.
Starting ZServer
To start ZServer run the start script::
$ python2.1 z2.py
To stop the server type 'control-c'.
Note: If you've created a shell script or batch file to run ZServer
use that instead.
You should see some Medusa information come up on the screen as Zope
starts.
A log file will be written to the 'var' directory, named
'Z2.log' by default.
Using ZServer
Once you start ZServer is will publish Zope (or any Python module)
on HTTP and/or FTP. To access Zope via HTTP point your browser at
the server like so::
http://www.example.com:9673/
This assumes that you have chosen to put HTTP on port 9673 and that
you are publishing a module named whose URL prefix is set to ''.
Note: to publish Zope normally you publish the 'lib/python/Zope.py'
module.
To access Zope via FTP you need to FTP to it at the port you set FTP
to run on. For example::
ftp www.example.com 9221
This opens a FTP session to your machine on port 9221, ZServer's
default FTP port. When you are prompted to log in you should supply
a Zope username and password. (Probably you should use an account
with the 'Manager' role, unless you have configured Zope to allow
FTP access to the 'Anonymous' role.) You can also enter 'anonymous'
and any password for anonymous FTP access. Once you have logged in
you can start issuing normal FTP commands.
Right now ZServer supports most basic FTP commands.
Note: When you log in your working directory is set to '/'. If you
do not have FTP permissions in this directory, you will need to 'cd'
to a directory where you have permissions before you can do
anything. See above for more information about logging into FTP.
Advanced Usage: zdaemon.py and the Zope NT service.
One problem you may notice with ZServer is that once the server is
shutdown, either through the web management interface, or by some
other means, it will not automatically be restarted.
On Unix you can use zdeamon.py to keep Zope running. Specifying
the '-Z' switch when starting Zope runs zdaemon.py. Zdeamon
will restart Zope when it Zope is restarted through the web, and in
case of an unexpected error.
On NT, use the Zope service for the same functionality. See ZServer.py
for more information on running ZServer as a service.
Where to go from here
Check out the README.txt file. It contains information on what
ZServer can do, how it works and and what you can do if you run into
problems.
And don't forget to have fun!
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""
ZServer pipe utils. These producers basically function as callbacks.
"""
import asyncore
import sys
class ShutdownProducer:
"shuts down medusa"
def more(self):
asyncore.close_all()
class LoggingProducer:
"logs request"
def __init__(self, logger, bytes, method='log'):
self.logger=logger
self.bytes=bytes
self.method=method
def more(self):
getattr(self.logger, self.method)(self.bytes)
self.logger=None
return ''
class CallbackProducer:
"Performs a callback in the channel's thread"
def __init__(self, callback):
self.callback=callback
def more(self):
self.callback()
self.callback=None
return ''
class file_part_producer:
"producer wrapper for part of a file[-like] objects"
# match http_channel's outgoing buffer size
out_buffer_size = 1<<16
def __init__(self, file, lock, start, end):
self.file=file
self.lock=lock
self.start=start
self.end=end
def more(self):
end=self.end
if not end: return ''
start=self.start
if start >= end: return ''
file=self.file
size=end-start
bsize=self.out_buffer_size
if size > bsize: size=bsize
self.lock.acquire()
try:
file.seek(start)
data = file.read(size)
finally:
self.lock.release()
if data:
start=start+len(data)
if start < end:
self.start=start
return data
self.end=0
del self.file
return data
class file_close_producer:
def __init__(self, file):
self.file=file
def more(self):
file=self.file
if file is not None:
file.close()
self.file=None
return ''
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Simple Event Manager Based on Pipes
"""
from ZServer.medusa.thread.select_trigger import trigger
from ZServer.medusa.asyncore import socket_map
class simple_trigger(trigger):
def handle_close(self):
pass
the_trigger=simple_trigger()
def Wakeup(thunk=None):
global the_trigger
try:
the_trigger.pull_trigger(thunk)
except OSError, why:
# this broken pipe as a result of perhaps a signal
# we want to handle this gracefully so we get rid of the old
# trigger and install a new one.
if why[0] == 32:
del socket_map[the_trigger._fileno]
the_trigger = simple_trigger() # adds itself back into socket_map
the_trigger.pull_trigger(thunk)
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
import thread
from ZServerPublisher import ZServerPublisher
class ZRendevous:
def __init__(self, n=1):
sync=thread.allocate_lock()
self._a=sync.acquire
self._r=sync.release
pool=[]
self._lists=pool, [], []
self._a()
try:
while n > 0:
l=thread.allocate_lock()
l.acquire()
pool.append(l)
thread.start_new_thread(ZServerPublisher,
(self.accept,))
n=n-1
finally: self._r()
def accept(self):
self._a()
try:
pool, requests, ready = self._lists
while not requests:
l=pool[-1]
del pool[-1]
ready.append(l)
self._r()
l.acquire()
self._a()
pool.append(l)
r=requests[0]
del requests[0]
return r
finally: self._r()
def handle(self, name, request, response):
self._a()
try:
pool, requests, ready = self._lists
requests.append((name, request, response))
if ready:
l=ready[-1]
del ready[-1]
l.release()
finally: self._r()
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
from ZPublisher import publish_module
class ZServerPublisher:
def __init__(self, accept):
while 1:
try:
name, request, response=accept()
publish_module(
name,
request=request,
response=response)
finally:
response._finish()
request=response=None
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
import ZRendezvous
_handle=None
_n=1
def handle(*args, **kw):
global _handle
if _handle is None: _handle=ZRendezvous.ZRendevous(_n).handle
return apply(_handle, args, kw)
def setNumberOfThreads(n):
global _n
_n=n
global setNumberOfThreads
del setNumberOfThreads
ZServer README
--------------
What is ZServer?
ZServer is an integration of the Zope application server and the
Medusa information server. See the ZServer architecture document for
more information::
http://www.zope.org/Documentation/Reference/ZServer
ZServer gives you HTTP, FTP, WebDAV, PCGI, and remote interactive
Python access. In later releases it will probably offer more
protocols such as FastCGI, etc.
What is Medusa?
Medusa is a Python server framework with uses a single threaded
asynchronous sockets approach. For more information see::
http://www.nightmare.com/medusa
There's also an interesting Medusa tutorial at::
http://www.nightmare.com:8080/nm/apps/medusa/docs/programming.html
ZServer HTTP support
ZServer offers HTTP 1.1 publishing for Zope. It does not support
publishing files from the file system. You can specify the HTTP port
using the -w command line argument for the z2.py start script. You
can also specify CGI environment variables on the command line using
z2.py
ZServer FTP support
What you can do with FTP
FTP access to Zope allows you to FTP to the Zope object hierarchy
in order to perform managerial tasks. You can:
* Navigate the object hierarchy with 'cd'
* Replace the content of Documents, Images, and Files
* Create Documents, Images, Files, Folders
* Delete objects and Folders.
So basically you can do more than is possible with HTTP PUT. Also,
unlike PUT, FTP gives you access to Document content. So when you
download a Document you are getting its content, not what it looks
like when it is rendered.
Using FTP
To FTP into Zope, ZServer must be configured to serve FTP. By
default ZServer serves FTP on port 9221. So to connect to Zope you
would issue a command like so::
$ ftp localhost 9221
When logging in to FTP, you have some choices. You can connect
anonymously by using a username of 'anonymous' and any password.
Or you can login as a Zope user. Since Zope users are defined at
different locations in the object hierarchy, authentication can be
problematic. There are two solutions:
* login and then cd to the directory where you are defined.
* login with a special name that indicates where you are
defined.
The format of the special name is '<username>@<path>'. For
example::
joe@Marketing/Projects
FTP permissions
FTP support is provided for Folders, Documents, Images, and Files.
You can control access to FTP via the new 'FTP access' permission.
This permission controls the ability to 'cd' to a Folder and to
download objects. Uploading and deleting and creating objects are
controlled by existing permissions.
FTP limits
You can set limits for the number of simultaneous FTP connections.
You can separately configure the number of anonymous and
authenticated connections. Right now this setting is set in
'ZServerFTP.py'. In the future, it may be more easy to configure.
Properties and FTP: The next step
The next phase of FTP support will allow you to edit properties of
all Zope objects. Probably properties will be exposed via special
files which will contain an XML representation of the object's
properties. You could then download the file, edit the XML and
upload it to change the object's properties.
We do not currently have a target date for FTP property support.
How does FTP work?
The ZServer's FTP channel object translates FTP requests into
ZPublisher requests. The FTP channel then analyses the response
and formulates an appropriate FTP response. The FTP channel
stores some state such as the current working directory and the
username and password.
On the Zope side of things, the 'lib/python/OFS/FTPInterface.py'
module defines the Zope FTP interface, for listing sub-items,
stating, and getting content. The interface is implemented in
'SimpleItem', and in other Zope classes. Programmers will not
need to implement the entire interface if they inherit from
'SimpleItem'. All the other FTP functions are handled by
existing methods like 'manage_delObjects', and 'PUT', etc.
ZServer PCGI support
ZServer will service PCGI requests with both inet and unix domain
sockets. This means you can use ZServer instead of
'pcgi_publisher.py' as your long running PCGI server process. In the
future, PCGI may be able to activate ZServer.
Using PCGI instead of HTTP allows you to forward requests from
another web server to ZServer. The CGI environment and HTTP headers
are controlled by the web server, so you don't need to worry about
managing the ZServer environment. However, this configuration will
impose a larger overhead than simply using the web server as an HTTP
proxy for ZServer.
To use PCGI, configure your PCGI info files to communicate with
ZServer by setting the PCGI_PORT, PCGI_SOCKET_FILE, and PCGI_NAME.
The other PCGI settings are currently ignored by ZServer.
ZServer's PCGI support will work with mod_pcgi.
ZServer monitor server
ZServer now includes the Medusa monitor server. This basically gives
you a remote, secure Python prompt. You can interactively access Zope.
This is a very powerful, but dangerous tool. Be careful.
To use the monitor server specify a monitor port number using the -m
option with the z2.py start script. The default port is 9999.
To connect to the monitor server use the 'ZServer/medusa/monitor_client.py'
or 'ZServer/medusa/monitor_client_win32.py' script. For example::
$ python2.1 ZServer/medusa/monitor_client.py localhost 9999
You will then be asked to enter a password. This is the Zope super manager
password which is stored in the 'access' file.
Then you will be greeted with a Python prompt. To access Zope import
the Zope module::
>>> import Zope
The Zope top level Zope object is available via the 'Zope.app' function::
>>> a=Zope.app()
From this object you can reach all other Zope objects as subobjects.
Remember if you make changes to Zope objects and want those changes to be
saved you need to commmit the transaction::
>>> get_transaction().commit()
ZServer WebDAV support
WebDAV is a new protocol for managing web resources. WebDAV operates
over HTTP. Since WebDAV uses HTTP, ZServer doesn't really have to do
anything special, except stay out of Zope's way when handling WebDAV
requests.
The only major WebDAV client at this time is Internet Explorer 5. It
works with Zope.
Differences between ZopeHTTPServer and ZServer
ZopeHTTPServer is old and no longer being actively maintained.
Both ZopeHTTPServer and ZServer are Python HTTP servers.
ZopeHTTPServer is built on the standard Python SimpleHTTPServer
framework. ZServer is built on Medusa.
ZopeHTTPServer is very limited. It can only publish one module at a
time. It can only publish via HTTP. It has no support for thread
pools.
ZServer on the other hand is more complex and supports publishing
multiple modules, thread pools, and it uses a new threaded
architecture for accessing ZPublisher.
Running ZServer as nobody
Normally ZServer will run with the userid of the user who starts
it. However, if ZServer is started by root, it will attempt to
become nobody or any userid you specify with the -u argument to the
z2.py start script.
ZServer is similar to ZopeHTTPServer in these respects.
If you run Zope with different userids you must be aware of
permission issues. Zope must be able to read and write to the 'var'
directory. If you change the userid Zope is running under you will
probably need to change the permissions on the 'var' directory
and the files in it in order for Zope to run under a different
userid.
Support
Questions and comments should go to 'support@digicool.com'.
You can report bugs and check on the status of bugs using the Zope
bug collector::
http://www.zope.org/Resources/Collector/
License
ZServer is covered by the ZPL despite the fact that it comes with
much of the Medusa source code. The portions of Medusa that come
with ZServer are licensed under the ZPL.
Outstanding issues
The FTP interface for Zope objects may be changed.
HTTP 1.1 support is ZServer is incomplete, though it should work for
most HTTP 1.1 clients.
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""
Special HTTP handler which forces GET requests to return the "source"
of documents (uses 'manage_FTPget'). Works around current WebDAV
clients' failure to implement the "source-link" feature of the spec.
"""
__version__ = "1.0"
from HTTPServer import zhttp_handler
import os
class WebDAVSrcHandler( zhttp_handler ):
"""
"""
def get_environment( self, request ):
"""
Munge the request to ensure that we call manage_FTPGet.
"""
env = zhttp_handler.get_environment( self, request )
# Set a flag to indicate this request came through the WebDAV source
# port server.
env['WEBDAV_SOURCE_PORT'] = 1
if env['REQUEST_METHOD'] == 'GET':
path_info = env['PATH_INFO']
path_info = os.path.join( path_info, 'manage_FTPget' )
path_info = os.path.normpath( path_info )
if os.sep != '/':
path_info = path_info.replace( os.sep, '/' )
env['PATH_INFO'] = path_info
# Workaround for lousy WebDAV implementation of M$ Office 2K.
# Requests for "index_html" are *sometimes* send as "index_html."
# We check the user-agent and remove a trailing dot for PATH_INFO
# and PATH_TRANSLATED
if env.get("HTTP_USER_AGENT","").find("Microsoft Data Access Internet Publishing Provider")>-1:
if env["PATH_INFO"][-1]=='.':
env["PATH_INFO"] = env["PATH_INFO"][:-1]
if env["PATH_TRANSLATED"][-1]=='.':
env["PATH_TRANSLATED"] = env["PATH_TRANSLATED"][:-1]
return env
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""
ZServer as a NT service.
The serice starts up and monitors a ZServer process.
Features:
* When you start the service it starts ZServer
* When you stop the serivice it stops ZServer
* It monitors ZServer and restarts it if it exits abnormally
* If ZServer is shutdown from the web, the service stops.
* If ZServer cannot be restarted, the service stops.
Usage:
Installation
The ZServer service should be installed by the Zope Windows
installer. You can manually install, uninstall the service from
the commandline.
ZService.py [options] install|update|remove|start [...]
|stop|restart [...]|debug [...]
Options for 'install' and 'update' commands only:
--username domain\username : The Username the service is to run
under
--password password : The password for the username
--startup [manual|auto|disabled] : How the service starts,
default = manual
Commands
install : Installs the service
update : Updates the service, use this when you change
ZServer.py
remove : Removes the service
start : Starts the service, this can also be done from the
services control panel
stop : Stops the service, this can also be done from the
services control panel
restart : Restarts the service
debug : Runs the service in debug mode
You can view the usage options by running ZServer.py without any
arguments.
Note: you may have to register the Python service program first,
win32\pythonservice.exe /register
Starting Zope
Start Zope by clicking the 'start' button in the services control
panel. You can set Zope to automatically start at boot time by
choosing 'Auto' startup by clicking the 'statup' button.
Stopping Zope
Stop Zope by clicking the 'stop' button in the services control
panel. You can also stop Zope through the web by going to the
Zope control panel and by clicking 'Shutdown'.
Event logging
Zope events are logged to the NT application event log. Use the
event viewer to keep track of Zope events.
Registry Settings
You can change how the service starts ZServer by editing a registry
key.
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\
<Service Name>\Parameters\start
The value of this key is the command which the service uses to
start ZServer. For example:
"C:\Program Files\Zope\bin\python.exe"
"C:\Program Files\Zope\z2.py" -w 8888
TODO:
* Integrate it into the Windows installer.
* Add ZLOG logging in addition to event log logging.
* Make it easier to run multiple Zope services with one Zope install
This script does for NT the same sort of thing zdaemon.py does for UNIX.
Requires Python win32api extensions.
"""
import sys, os, time, imp
# Some fancy path footwork is required here because we
# may be run from python.exe or lib/win32/PythonService.exe
home=os.path.split(os.path.split(sys.executable)[0])[0]
if sys.executable[-10:]!='python.exe':
home=os.path.split(home)[0]
home=os.path.split(home)[0]
sys.path.append(os.path.join(home, 'bin'))
sys.path.append(os.path.join(home, 'ZServer'))
sys.path.append(os.path.join(home, 'bin', 'lib', 'win32'))
sys.path.append(os.path.join(home, 'bin', 'lib', 'win32', 'lib'))
# pythoncom and pywintypes are special, and require these hacks when
# we dont have a standard Python installation around.
import win32api
def magic_import(modulename, filename):
# by Mark Hammond
try:
# See if it does import first!
return __import__(modulename)
except ImportError:
pass
# win32 can find the DLL name.
h = win32api.LoadLibrary(filename)
found = win32api.GetModuleFileName(h)
# Python can load the module
mod = imp.load_module(modulename, None, found, ('.dll', 'rb', imp.C_EXTENSION))
# inject it into the global module list.
sys.modules[modulename] = mod
# And finally inject it into the namespace.
globals()[modulename] = mod
win32api.FreeLibrary(h)
magic_import('pywintypes','pywintypes21.dll')
import win32serviceutil, win32service, win32event, win32process
try: import servicemanager
except: pass
class ZServerService(win32serviceutil.ServiceFramework):
# Some trickery to determine the service name. The WISE
# installer will write an svcname.txt to the ZServer dir
# that we can use to figure out our service name.
path=os.path.join(home, 'ZServer', 'svcname.txt')
file=open(path, 'r')
_svc_name_=file.readline().strip()
file.close()
_svc_display_name_ = "Zope (%s)" % _svc_name_
restart_min_time=5 # if ZServer restarts before this many
# seconds then we have a problem, and
# need to stop the service.
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
def SvcDoRun(self):
self.start_zserver()
while 1:
rc=win32event.WaitForMultipleObjects(
(self.hWaitStop, self.hZServer), 0, win32event.INFINITE)
if rc - win32event.WAIT_OBJECT_0 == 0:
break
else:
self.restart_zserver()
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING, 5000)
def SvcStop(self):
servicemanager.LogInfoMsg('Stopping Zope.')
try:
self.stop_zserver()
except:
pass
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
win32event.SetEvent(self.hWaitStop)
def start_zserver(self):
sc=self.get_start_command()
result=win32process.CreateProcess(None, self.get_start_command(),
None, None, 0, 0, None, None, win32process.STARTUPINFO())
self.hZServer=result[0]
self.last_start_time=time.time()
servicemanager.LogInfoMsg('Starting Zope.')
def stop_zserver(self):
win32process.TerminateProcess(self.hZServer,0)
def restart_zserver(self):
if time.time() - self.last_start_time < self.restart_min_time:
servicemanager.LogErrorMsg('Zope died and could not be restarted.')
self.SvcStop()
code=win32process.GetExitCodeProcess(self.hZServer)
if code == 0:
# Exited with a normal status code,
# assume that shutdown is intentional.
self.SvcStop()
else:
servicemanager.LogWarningMsg('Restarting Zope.')
self.start_zserver()
def get_start_command(self):
return win32serviceutil.GetServiceCustomOption(self,'start')
def set_start_command(value):
"sets the ZServer start command if the start command is not already set"
current=win32serviceutil.GetServiceCustomOption(ZServerService,
'start', None)
if current is None:
win32serviceutil.SetServiceCustomOption(ZServerService,'start',value)
if __name__=='__main__':
win32serviceutil.HandleCommandLine(ZServerService)
if 'install' in sys.argv:
command='"%s" "%s" -S' % (sys.executable, os.path.join(home,'z2.py'))
set_start_command(command)
print "Setting ZServer start command to:", command
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
import sys
from medusa import asyncore
sys.modules['asyncore'] = asyncore
from medusa.test import max_sockets
CONNECTION_LIMIT=max_sockets.max_select_sockets()
ZSERVER_VERSION='1.1b1'
try:
import App.version_txt
ZOPE_VERSION=App.version_txt.version_txt()
except:
ZOPE_VERSION='experimental'
# Try to poke zLOG default logging into asyncore
# XXX We should probably should do a better job of this,
# however that would mean that ZServer required zLOG.
# (Is that really a bad thing?)
try:
from zLOG import LOG, register_subsystem, BLATHER, INFO, WARNING, ERROR
except ImportError:
pass
else:
register_subsystem('ZServer')
severity={'info':INFO, 'warning':WARNING, 'error': ERROR}
def log_info(self, message, type='info'):
if message[:14]=='adding channel' or \
message[:15]=='closing channel' or \
message == 'Computing default hostname':
LOG('ZServer', BLATHER, message)
else:
LOG('ZServer', severity[type], message)
import asyncore
asyncore.dispatcher.log_info=log_info
# A routine to try to arrange for request sockets to be closed
# on exec. This makes it easier for folks who spawn long running
# processes from Zope code. Thanks to Dieter Maurer for this.
try:
import fcntl
def requestCloseOnExec(sock):
try:
fcntl.fcntl(sock.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC)
except: # XXX What was this supposed to catch?
pass
except (ImportError, AttributeError):
def requestCloseOnExec(sock):
pass
import asyncore
from medusa import resolver, logger
from HTTPServer import zhttp_server, zhttp_handler
from PCGIServer import PCGIServer
from FCGIServer import FCGIServer
from FTPServer import FTPServer
from PubCore import setNumberOfThreads
from medusa.monitor import secure_monitor_server
# override the service name in logger.syslog_logger
logger.syslog_logger.svc_name='ZServer'
-----BEGIN PGP SIGNED MESSAGE-----
I am proud to announce the first alpha release of Medusa.
Medusa is a 'server platform' - it provides a framework for
implementing asynchronous socket-based servers (tcp/ip and on unix,
unix domain sockets).
An asynchronous socket server is a server that can communicate with
many other socket clients and servers simultaneously, by multiplexing
I/O within a single process/thread. In the context of an HTTP server,
this means a single process can serve hundreds or even thousands of
clients, depending only on the operating system's configuration and
limitations.
There are several advantages to this approach:
o performance - no fork() or thread() start-up costs per hit.
o scalability - the overhead per client can be kept rather small,
on the order of several kilobytes of memory.
o persistence - a single-process server can easily coordinate the
actions of several different connections. This makes things like
proxy servers and gateways easy to implement. It also makes it
possible to share resources like database handles.
This first release of Medusa includes HTTP, FTP, and 'monitor' (remote
python interpreter) servers. Medusa can simultaneously support
several instances of either the same or different server types - for
example you could start up two HTTP servers, an FTP server, and a
monitor server. Then you could connect to the monitor server to
control and manipulate medusa while it is running.
Other servers and clients have been written (SMTP, POP3, NNTP), and
several are in the planning stages. [One particularly interesting
side-project is an integrated asynchronous mSQL client.]
I am distributing Medusa under a 'free for non-commercial use'
license. Python source code is included.
Medusa has not yet been placed under a 'real-world' load, such an
environment is difficult to simulate. I am very interested in all
feedback about Medusa's performance, but especially so for high-load
situations (greater than 1 hit/sec).
More information is available at:
http://www.nightmare.com/medusa/
- -Sam
rushing@nightmare.com
-----BEGIN PGP SIGNATURE-----
Version: 2.6.2
Comment: Processed by Mailcrypt 3.4, an Emacs/PGP interface
iQCVAwUBMv/2OGys8OGgJmJxAQGUyAQAgL+LMgz1nVEDzYvx6NROcRU5oMSNMQPG
4aUdZ3lMthAgfCrQ9bipVMtR2ouUeluC8qlZeaaeoT+mnMi5svoURZpAfCv0tIc4
CYfO6Ih8B1xaXaGC/ygRgIqN2alUXmyZmVoVxtAj6uFczP27i8QQ/3mSWLv7OskL
9Qg6fNo2Zg4=
=3anM
-----END PGP SIGNATURE-----
This is a major update of Medusa. Almost everything has been
rewritten; the web server has been rewritten from scratch
twice. [ain't Python nice. 8^) ]
Here is a quick rundown of the improvements and new features:
HTTP Server:
Good support for persistent connections (HTTP/1.0 _and_ 1.1)
required a redesign of the request-handling mechanism. Requests are
now separate objects, and as much as possible differences between
the two protocol versions have been hidden from the user. [I should
say 'extender'].
HTTP/1.0 persistence is provided via the 'Connection: Keep-Alive'
mechanism supported by most currently available browsers.
HTTP/1.1 default persistence is implemented, along with various
features to support pipelining, including the 'chunked' transfer
encoding. [which is enabled by default in situations where the
extension is providing data dynamically]
[a note on a change in terminology: 'extensions' are now 'handlers']
Sample request handlers for the basic authentication scheme and the
PUT method are provided, along with a demonstration of a
'publishing' interface - this allows the server to support updatable
web pages.
A sample handler for unix user directories (the familiar '~user/'
URI) is included.
The new handler mechanism is quite general and powerful: It is easy
to write handlers that 'wrap' other handlers to provide combined
behavior. (For example, the 'publishing' demo wraps an
authentication handler around a PUT handler to provide authentication
for page updates).
Sophisticated Logging:
A few different logging objects are implemented:
An implementation of the Unix 'syslog' is included: it understands
the syslog protocol natively, and can thus log asynchronously to
either the local host, or to a remote host. This means it will also
work on non-unix platforms.
A 'socket' logger: send log info directly to a network connection.
A 'file' logger: log into any file object.
The above logging objects can be combined using the 'multi' logger.
DNS resolver:
A simple asynchronous caching DNS resolver is included - this
piggybacks off of a known name server. The resolver is currently
used to provide resolved hostnames for logging for the other
servers.
'Monitor' server:
This is a 'remote interpreter' server. Server administrators can
use this to get directly at the server WHILE IT IS RUNNING. I use
it to upgrade pieces of the server, or to install or remove handlers
`on the fly'. It is optionally protected by an MD5-based challenge-
response authentication, and by a stream-cipher encryption.
Encryption is available if you have access to the Python
Cryptography Toolkit, or something like it.
It's been difficult to convey the power and convenience of this
server: Anything that can be done from a python prompt can be done
while connected to it. It is also a tremendous aid when debugging
servers or extensions.
'Chat' server:
For reasons I won't pretend to understand, servers supporting
IRC-like 'chat' rooms of various types are quite popular in the
commercial world: This is a quick demonstration of how to write such
a server, and how to integrate it with medusa. This simple example
could easily be integrated into the web server to provide a
web-navigated, web-administered chat server.
That was the good news, here's the 'bad' :
==================================================
I've ditched the command-line interface for the time being. In order
to make it sufficiently powerful I found myself inventing yet another
'configuration language'. This seemed rather silly given python's
innate ability to handle such things. So now medusa is driven by a
user 'script'. A sample script is provided with judicious commentary.
Probably the most glaring omission in Medusa is the lack of CGI support.
I have dropped this for several reasons:
1) it is unreasonably difficult to support in a portable fashion
2) it is clearly a hack in the worst sense of the word; insecure and
inefficient. why not just use inetd?
3) much more powerful things can be done within the framework of
Medusa with much less effort.
4) CGI can easily be provided using Apache or any other web server
by running it in 'tandem' with medusa [i.e., on another port].
If someone desperately needs the CGI support, I can explain how to
integrate it with Medusa - the code is not very different from the
module included with the Python library.
==================================================
Medusa is provided free of charge for non-commercial use. Commercial
use requires a license (US$200). Source code is included (it's called
"The Documentation"), and users are encouraged to develop and
distribute their own extensions under their own terms.
Note that the core of Medusa is an asynchronous socket library that is
distributed under a traditional Python copyright, so unless you're
plugging directly into medusa's higher-level servers, and doing so for
commercial purposes, you needn't worry about me getting Run Over By a
Bus.
More information is available from:
http://www.nightmare.com/medusa/
Enjoy!
-Sam Rushing
rushing@nightmare.com
Medusa Installation.
---------------------------------------------------------------------------
Medusa is distributed as Python source code. Before using Medusa, you
will need to install Python on your machine.
The Python interpreter, source, documentation, etc... may be obtained
from
http://www.python.org/
Versions for many different operating systems are available, including
Unix, 32-bit Windows (Win95 & NT), Macintosh, VMS, etc... Medusa has
been tested on Unix and Windows, though it may very well work on other
operating systems.
You don't need to learn Python In order to use Medusa. However, if
you are interested in extending Medusa, you should spend the few
minutes that it will take you to go through the Python Tutorial:
http://www.python.org/doc/tut/
Python is remarkably easy to learn, and I guarantee that it will be
worth your while. After only about thirty minutes, you should know
enough about Python to be able to start customizing and extending
Medusa.
---------------------------------------------------------------------------
Once you have installed Python, you are ready to configure Medusa.
Medusa does not use configuration files per se, or even command-line
arguments. It is configured via a 'startup script', written in
Python. A sample is provided in 'start_medusa.py'. You should make
a copy of this.
The sample startup script is heavily commented. Many (though not all)
of Medusa's features are made available in the startup script. You may
modify this script by commenting out portions, adding or changing
parameters, etc...
Here is a section from the front of 'start_medusa.py'
| if len(sys.argv) > 1:
| # process a few convenient arguments
| [HOSTNAME, IP_ADDRESS, PUBLISHING_ROOT] = sys.argv[1:]
| else:
| HOSTNAME = 'www.nightmare.com'
| # This is the IP address of the network interface you want
| # your servers to be visible from. This can be changed to ''
| # to listen on all interfaces.
| IP_ADDRESS = '205.160.176.5'
|
| # Root of the http and ftp server's published filesystems.
| PUBLISHING_ROOT = '/home/www'
|
| HTTP_PORT = 8080 # The standard port is 80
| FTP_PORT = 8021 # The standard port is 21
| CHAT_PORT = 8888
| MONITOR_PORT = 9999
If you are familiar with the process of configuring a web or ftp
server, then these parameters should be fairly obvious: You will
need to change the hostname, IP address, and port numbers for the
server that you wish to run.
---------------------------------------------------------------------------
A Medusa configuration does not need to be this complex -
start_medusa.py is bloated somewhat by its attempt to include most of
the available features. Another example startup script is available
in the 'demo' subdirectory.
---------------------------------------------------------------------------
Once you have made your own startup script, you may simply invoke
the Python interpreter on it:
[unix]
$ python start_medusa.py &
[win32]
d:\medusa\> start python start_medusa.py
Medusa (V3.8) started at Sat Jan 24 01:43:21 1998
Hostname: ziggurat.nightmare.com
Port:8080
<Unix User Directory Handler at 080e9c08 [~user/public_html, 0 filesystems loaded]>
FTP server started at Sat Jan 24 01:43:21 1998
Authorizer:<test_authorizer instance at 80e8938>
Hostname: ziggurat.nightmare.com
Port: 21
192.168.200.40:1450 - - [24/Jan/1998:07:43:23 -0500] "GET /status HTTP/1.0" 200 1638
192.168.200.40:1451 - - [24/Jan/1998:07:43:23 -0500] "GET /status/medusa.gif HTTP/1.0" 200 1084
---------------------------------------------------------------------------
Documentation for specific Medusa servers is somewhat lacking, mostly
because development continues to move rapidly. The best place to go
to understand Medusa and how it works is to dive into the source code.
Many of the more interesting features, especially the latest, are
described only in the source code.
Some notes on data flow in Medusa are available in
'docs/data_flow.html'
I encourage you to examine and experiment with Medusa. You may
develop your own extensions, handlers, etc... I appreciate feedback
from users and developers on desired features, and of course
descriptions of your most splendid hacks.
Medusa's design is somewhat novel compared to most other network
servers. In fact, the asynchronous i/o capability seems to have
attracted the majority of paying customers, who are often more
interested in harnessing the i/o framework than the actual web and ftp
servers.
-Sam Rushing
rushing@nightmare.com
Nightmare Software,
January 1998
# -*- Mode: Makefile; tab-width: 4 -*-
all: dist
dist: clean
python util/name_dist.py
clean:
find ./ -name '*.pyc' -exec rm {} \;
find ./ -name '*~' -exec rm {} \;
# Make medusa into a package
__version__='$Revision: 1.7 $'[11:-2]
This diff is collapsed.
This diff is collapsed.
# -*- Mode: Python; tab-width: 4 -*-
#
# Author: Sam Rushing <rushing@nightmare.com>
# Copyright 1996-2000 by Sam Rushing
# All Rights Reserved.
#
RCS_ID = '$Id: auth_handler.py,v 1.3 2001/05/01 11:44:48 andreas Exp $'
# support for 'basic' authenticaion.
import base64
import md5
import re
import string
import time
import counter
import default_handler
get_header = default_handler.get_header
import http_server
import producers
# This is a 'handler' that wraps an authorization method
# around access to the resources normally served up by
# another handler.
# does anyone support digest authentication? (rfc2069)
class auth_handler:
def __init__ (self, dict, handler, realm='default'):
self.authorizer = dictionary_authorizer (dict)
self.handler = handler
self.realm = realm
self.pass_count = counter.counter()
self.fail_count = counter.counter()
def match (self, request):
# by default, use the given handler's matcher
return self.handler.match (request)
def handle_request (self, request):
# authorize a request before handling it...
scheme = get_header (AUTHORIZATION, request.header)
if scheme:
scheme = string.lower (scheme)
if scheme == 'basic':
cookie = AUTHORIZATION.group(2)
try:
decoded = base64.decodestring (cookie)
except:
print 'malformed authorization info <%s>' % cookie
request.error (400)
return
auth_info = string.split (decoded, ':')
if self.authorizer.authorize (auth_info):
self.pass_count.increment()
request.auth_info = auth_info
self.handler.handle_request (request)
else:
self.handle_unauthorized (request)
#elif scheme == 'digest':
# print 'digest: ',AUTHORIZATION.group(2)
else:
print 'unknown/unsupported auth method: %s' % scheme
self.handle_unauthorized()
else:
# list both? prefer one or the other?
# you could also use a 'nonce' here. [see below]
#auth = 'Basic realm="%s" Digest realm="%s"' % (self.realm, self.realm)
#nonce = self.make_nonce (request)
#auth = 'Digest realm="%s" nonce="%s"' % (self.realm, nonce)
#request['WWW-Authenticate'] = auth
#print 'sending header: %s' % request['WWW-Authenticate']
self.handle_unauthorized (request)
def handle_unauthorized (self, request):
# We are now going to receive data that we want to ignore.
# to ignore the file data we're not interested in.
self.fail_count.increment()
request.channel.set_terminator (None)
request['Connection'] = 'close'
request['WWW-Authenticate'] = 'Basic realm="%s"' % self.realm
request.error (401)
def make_nonce (self, request):
"A digest-authentication <nonce>, constructed as suggested in RFC 2069"
ip = request.channel.server.ip
now = str (long (time.time()))[:-1]
private_key = str (id (self))
nonce = string.join ([ip, now, private_key], ':')
return self.apply_hash (nonce)
def apply_hash (self, s):
"Apply MD5 to a string <s>, then wrap it in base64 encoding."
m = md5.new()
m.update (s)
d = m.digest()
# base64.encodestring tacks on an extra linefeed.
return base64.encodestring (d)[:-1]
def status (self):
# Thanks to mwm@contessa.phone.net (Mike Meyer)
r = [
producers.simple_producer (
'<li>Authorization Extension : '
'<b>Unauthorized requests:</b> %s<ul>' % self.fail_count
)
]
if hasattr (self.handler, 'status'):
r.append (self.handler.status())
r.append (
producers.simple_producer ('</ul>')
)
return producers.composite_producer (
http_server.fifo (r)
)
class dictionary_authorizer:
def __init__ (self, dict):
self.dict = dict
def authorize (self, auth_info):
[username, password] = auth_info
if (self.dict.has_key (username)) and (self.dict[username] == password):
return 1
else:
return 0
AUTHORIZATION = re.compile (
# scheme challenge
'Authorization: ([^ ]+) (.*)',
re.IGNORECASE
)
# -*- Mode: Python; tab-width: 4 -*-
#
# Author: Sam Rushing <rushing@nightmare.com>
# Copyright 1997-2000 by Sam Rushing
# All Rights Reserved.
#
RCS_ID = '$Id: chat_server.py,v 1.3 2001/05/01 11:44:48 andreas Exp $'
import string
VERSION = string.split(RCS_ID)[2]
import socket
import asyncore
import asynchat
import status_handler
class chat_channel (asynchat.async_chat):
def __init__ (self, server, sock, addr):
asynchat.async_chat.__init__ (self, sock)
self.server = server
self.addr = addr
self.set_terminator ('\r\n')
self.data = ''
self.nick = None
self.push ('nickname?: ')
def collect_incoming_data (self, data):
self.data = self.data + data
def found_terminator (self):
line = self.data
self.data = ''
if self.nick is None:
self.nick = string.split (line)[0]
if not self.nick:
self.nick = None
self.push ('huh? gimmee a nickname: ')
else:
self.greet()
else:
if not line:
pass
elif line[0] != '/':
self.server.push_line (self, line)
else:
self.handle_command (line)
def greet (self):
self.push ('Hello, %s\r\n' % self.nick)
num_channels = len(self.server.channels)-1
if num_channels == 0:
self.push ('[Kinda lonely in here... you\'re the only caller!]\r\n')
else:
self.push ('[There are %d other callers]\r\n' % (len(self.server.channels)-1))
nicks = map (lambda x: x.get_nick(), self.server.channels.keys())
self.push (string.join (nicks, '\r\n ') + '\r\n')
self.server.push_line (self, '[joined]')
def handle_command (self, command):
import types
command_line = string.split(command)
name = 'cmd_%s' % command_line[0][1:]
if hasattr (self, name):
# make sure it's a method...
method = getattr (self, name)
if type(method) == type(self.handle_command):
method (command_line[1:])
else:
self.push ('unknown command: %s' % command_line[0])
def cmd_quit (self, args):
self.server.push_line (self, '[left]')
self.push ('Goodbye!\r\n')
self.close_when_done()
# alias for '/quit' - '/q'
cmd_q = cmd_quit
def push_line (self, nick, line):
self.push ('%s: %s\r\n' % (nick, line))
def handle_close (self):
self.close()
def close (self):
del self.server.channels[self]
asynchat.async_chat.close (self)
def get_nick (self):
if self.nick is not None:
return self.nick
else:
return 'Unknown'
class chat_server (asyncore.dispatcher):
SERVER_IDENT = 'Chat Server (V%s)' % VERSION
channel_class = chat_channel
spy = 1
def __init__ (self, ip='', port=8518):
self.port = port
self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
self.bind ((ip, port))
print '%s started on port %d' % (self.SERVER_IDENT, port)
self.listen (5)
self.channels = {}
self.count = 0
def handle_accept (self):
conn, addr = self.accept()
self.count = self.count + 1
print 'client #%d - %s:%d' % (self.count, addr[0], addr[1])
self.channels[self.channel_class (self, conn, addr)] = 1
def push_line (self, from_channel, line):
nick = from_channel.get_nick()
if self.spy:
print '%s: %s' % (nick, line)
for c in self.channels.keys():
if c is not from_channel:
c.push ('%s: %s\r\n' % (nick, line))
def status (self):
lines = [
'<h2>%s</h2>' % self.SERVER_IDENT,
'<br>Listening on Port: %d' % self.port,
'<br><b>Total Sessions:</b> %d' % self.count,
'<br><b>Current Sessions:</b> %d' % (len(self.channels))
]
return status_handler.lines_producer (lines)
def writable (self):
return 0
if __name__ == '__main__':
import sys
if len(sys.argv) > 1:
port = string.atoi (sys.argv[1])
else:
port = 8518
s = chat_server ('', port)
asyncore.loop()
# -*- Mode: Python; tab-width: 4 -*-
# [ based on async_lib/consumer.py:function_chain.py ]
class continuation:
'Package up a continuation as an object.'
'Also a convenient place to store state.'
def __init__ (self, fun, *args):
self.funs = [(fun, args)]
def __call__ (self, *args):
fun, init_args = self.funs[0]
self.funs = self.funs[1:]
if self.funs:
apply (fun, (self,)+ init_args + args)
else:
apply (fun, init_args + args)
def chain (self, fun, *args):
self.funs.insert (0, (fun, args))
return self
def abort (self, *args):
fun, init_args = self.funs[-1]
apply (fun, init_args + args)
This diff is collapsed.
# -*- Mode: Python; tab-width: 4 -*-
import time
def main (env, stdin, stdout):
# write out the response
stdout.write ("HTTP/1.0 200 OK\r\n")
# write out a header
stdout.write ("Content-Type: text/html\r\n")
stdout.write ("\r\n")
stdout.write ("<html><body>")
for i in range (10,0,-1):
stdout.write ("<br> <b>tick</b> %d\r\n" % i)
stdout.flush()
time.sleep (3)
stdout.write ("</body></html>\r\n")
# -*- Mode: Python; tab-width: 4 -*-
# It is tempting to add an __int__ method to this class, but it's not
# a good idea. This class tries to gracefully handle integer
# overflow, and to hide this detail from both the programmer and the
# user. Note that the __str__ method can be relied on for printing out
# the value of a counter:
#
# >>> print 'Total Client: %s' % self.total_clients
#
# If you need to do arithmetic with the value, then use the 'as_long'
# method, the use of long arithmetic is a reminder that the counter
# will overflow.
class counter:
"general-purpose counter"
def __init__ (self, initial_value=0):
self.value = initial_value
def increment (self, delta=1):
result = self.value
try:
self.value = self.value + delta
except OverflowError:
self.value = long(self.value) + delta
return result
def decrement (self, delta=1):
result = self.value
try:
self.value = self.value - delta
except OverflowError:
self.value = long(self.value) - delta
return result
def as_long (self):
return long(self.value)
def __nonzero__ (self):
return self.value != 0
def __repr__ (self):
return '<counter value=%s at %x>' % (self.value, id(self))
def __str__ (self):
return str(long(self.value))[:-1]
This diff is collapsed.
# -*- Mode: Python; tab-width: 4 -*-
# Demonstrates use of the auth and put handlers to support publishing
# web pages via HTTP. This is supported by Netscape Communicator and
# probably the Internet Exploder.
# It is also possible to set up the ftp server to do essentially the
# same thing.
# Security Note: Using HTTP with the 'Basic' authentication scheme is
# only slightly more secure than using FTP: both techniques involve
# sending a unencrypted password of the network (http basic auth
# base64-encodes the username and password). The 'Digest' scheme is
# much more secure, but not widely supported yet. <sigh>
import asyncore
import default_handler
import http_server
import put_handler
import auth_handler
import filesys
# For this demo, we'll just use a dictionary of usernames/passwords.
# You can of course use anything that supports the mapping interface,
# and it would be pretty easy to set this up to use the crypt module
# on unix.
users = { 'mozart' : 'jupiter', 'beethoven' : 'pastoral' }
# The filesystem we will be giving access to
fs = filesys.os_filesystem ('/home/medusa')
# The 'default' handler - delivers files for the HTTP GET method.
dh = default_handler.default_handler (fs)
# Supports the HTTP PUT method...
ph = put_handler.put_handler (fs, '/.*')
# ... but be sure to wrap it with an auth handler:
ah = auth_handler.auth_handler (users, ph)
# Create a Web Server
hs = http_server.http_server (ip='', port=8080)
# install the handlers we created:
hs.install_handler (dh) # for GET
hs.install_handler (ah) # for PUT
asyncore.loop()
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
# -*- Mode: Python -*-
# the medusa icon as a python source file.
width = 97
height = 61
data = 'GIF89aa\000=\000\204\000\000\000\000\000\255\255\255\245\245\245ssskkkccc111)))\326\326\326!!!\316\316\316\300\300\300\204\204\000\224\224\224\214\214\214\200\200\200RRR\377\377\377JJJ\367\367\367BBB\347\347\347\000\204\000\020\020\020\265\265\265\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000!\371\004\001\000\000\021\000,\000\000\000\000a\000=\000\000\005\376`$\216di\236h\252\256l\353\276p,\317tm\337x\256\357|m\001@\240E\305\000\364\2164\206R)$\005\201\214\007r\012{X\255\312a\004\260\\>\026\3240\353)\224n\001W+X\334\373\231~\344.\303b\216\024\027x<\273\307\255G,rJiWN\014{S}k"?ti\013EdPQ\207G@_%\000\026yy\\\201\202\227\224<\221Fs$pOjWz\241<r@vO\236\231\233k\247M\2544\203F\177\235\236L#\247\256Z\270,\266BxJ[\276\256A]iE\304\305\262\273E\313\201\275i#\\\303\321\'h\203V\\\177\326\276\216\220P~\335\230_\264\013\342\275\344KF\233\360Q\212\352\246\000\367\274s\361\236\334\347T\341;\341\246\2202\177\3142\211`\242o\325@S\202\264\031\252\207\260\323\256\205\311\036\236\270\002\'\013\302\177\274H\010\324X\002\0176\212\037\376\321\360\032\226\207\244\2674(+^\202\346r\205J\0211\375\241Y#\256f\0127\315>\272\002\325\307g\012(\007\205\312#j\317(\012A\200\224.\241\003\346GS\247\033\245\344\264\366\015L\'PXQl]\266\263\243\232\260?\245\316\371\362\225\035\332\243J\273\332Q\263\357-D\241T\327\270\265\013W&\330\010u\371b\322IW0\214\261]\003\033Va\365Z#\207\213a\030k\2647\262\014p\354\024[n\321N\363\346\317\003\037P\000\235C\302\000\3228(\244\363YaA\005\022\255_\237@\260\000A\212\326\256qbp\321\332\266\011\334=T\023\010"!B\005\003A\010\224\020\220 H\002\337#\020 O\276E\357h\221\327\003\\\000b@v\004\351A.h\365\354\342B\002\011\257\025\\ \220\340\301\353\006\000\024\214\200pA\300\353\012\364\241k/\340\033C\202\003\000\310fZ\011\003V\240R\005\007\354\376\026A\000\000\360\'\202\177\024\004\210\003\000\305\215\360\000\000\015\220\240\332\203\027@\'\202\004\025VpA\000%\210x\321\206\032J\341\316\010\262\211H"l\333\341\200\200>"]P\002\212\011\010`\002\0066FP\200\001\'\024p]\004\027(8B\221\306]\000\201w>\002iB\001\007\340\260"v7J1\343(\257\020\251\243\011\242i\263\017\215\337\035\220\200\221\365m4d\015\016D\251\341iN\354\346Ng\253\200I\240\031\35609\245\2057\311I\302\2007t\231"&`\314\310\244\011e\226(\236\010w\212\300\234\011\012HX(\214\253\311@\001\233^\222pg{% \340\035\224&H\000\246\201\362\215`@\001"L\340\004\030\234\022\250\'\015(V:\302\235\030\240q\337\205\224\212h@\177\006\000\250\210\004\007\310\207\337\005\257-P\346\257\367]p\353\203\271\256:\203\236\211F\340\247\010\3329g\244\010\307*=A\000\203\260y\012\304s#\014\007D\207,N\007\304\265\027\021C\233\207%B\366[m\353\006\006\034j\360\306+\357\274a\204\000\000;'
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
counter.py http_date.py resolver.py medusa_gif.py test_logger.py http_server.py logger.py monitor.py asynchat.py status_handler.py mime_type_table.py monitor_client.py monitor_client_win32.py filesys.py select_trigger.py default_handler.py max_sockets.py __init__.py m_syslog.py producers.py ftp_server.py
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
# -*- Mode: text; tab-width:4 -*-
#
# $Id: README,v 1.2 2001/04/25 19:09:54 andreas Exp $
#
Preliminary support for asynchronous sendfile() on linux and freebsd.
Not heavily tested yet.
-Sam
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment