BoboMailHTTPD.py
上传用户:gyjinxi
上传日期:2007-01-04
资源大小:159k
文件大小:18k
源码类别:

WEB邮件程序

开发平台:

Python

  1. #!/usr/bin/env python
  2. ##############################################################################
  3. # Zope Public License (ZPL) Version 0.9.5
  4. # ---------------------------------------
  5. # Copyright (c) Digital Creations.  All rights reserved.
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions are
  8. # met:
  9. # 1. Redistributions in source code must retain the above copyright
  10. #    notice, this list of conditions, and the following disclaimer.
  11. # 2. Redistributions in binary form must reproduce the above copyright
  12. #    notice, this list of conditions, and the following disclaimer in
  13. #    the documentation and/or other materials provided with the
  14. #    distribution.
  15. # 3. Any use, including use of the Zope software to operate a website,
  16. #    must either comply with the terms described below under
  17. #    "Attribution" or alternatively secure a separate license from
  18. #    Digital Creations.  Digital Creations will not unreasonably
  19. #    deny such a separate license in the event that the request
  20. #    explains in detail a valid reason for withholding attribution.
  21. # 4. All advertising materials and documentation mentioning
  22. #    features derived from or use of this software must display
  23. #    the following acknowledgement:
  24. #      "This product includes software developed by Digital Creations
  25. #      for use in the Z Object Publishing Environment
  26. #      (http://www.zope.org/)."
  27. #    In the event that the product being advertised includes an
  28. #    intact Zope distribution (with copyright and license included)
  29. #    then this clause is waived.
  30. # 5. Names associated with Zope or Digital Creations must not be used to
  31. #    endorse or promote products derived from this software without
  32. #    prior written permission from Digital Creations.
  33. # 6. Modified redistributions of any form whatsoever must retain
  34. #    the following acknowledgment:
  35. #      "This product includes software developed by Digital Creations
  36. #      for use in the Z Object Publishing Environment
  37. #      (http://www.zope.org/)."
  38. #    Intact (re-)distributions of any official Zope release do not
  39. #    require an external acknowledgement.
  40. # 7. Modifications are encouraged but must be packaged separately as
  41. #    patches to official Zope releases.  Distributions that do not
  42. #    clearly separate the patches from the original work must be clearly
  43. #    labeled as unofficial distributions.  Modifications which do not
  44. #    carry the name Zope may be packaged in any form, as long as they
  45. #    conform to all of the clauses above.
  46. # Disclaimer
  47. #   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
  48. #   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  49. #   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  50. #   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
  51. #   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  52. #   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  53. #   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  54. #   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  55. #   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  56. #   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  57. #   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  58. #   SUCH DAMAGE.
  59. # Attribution
  60. #   Individuals or organizations using this software as a web site must
  61. #   provide attribution by placing the accompanying "button" and a link
  62. #   to the accompanying "credits page" on the website's main entry
  63. #   point.  In cases where this placement of attribution is not
  64. #   feasible, a separate arrangment must be concluded with Digital
  65. #   Creations.  Those using the software for purposes other than web
  66. #   sites must provide a corresponding attribution in locations that
  67. #   include a copyright using a manner best suited to the application
  68. #   environment.  Where attribution is not possible, or is considered
  69. #   to be onerous for some other reason, a request should be made to
  70. #   Digital Creations to waive this requirement in writing.  As stated
  71. #   above, for valid requests, Digital Creations will not unreasonably
  72. #   deny such requests.
  73. # This software consists of contributions made by Digital Creations and
  74. # many individuals on behalf of Digital Creations.  Specific
  75. # attributions are listed in the accompanying credits file.
  76. ##############################################################################
  77. """Simple Bobo HTTP Server (based on CGIHTTPServer.py)
  78. What's Bobo?
  79.   An open source collection of tools for developing
  80.   world-wide web applications in Python. Find out more at
  81.   http://www.digicool.com/releases/bobo/
  82. What's Zope?
  83.   Zope is the result of the merging of Bobo and Principia.
  84.   For more info visit the Zope web site at
  85.   http://www.zope.org/
  86. What does ZopeHTTPServer.py do?
  87.   It is a very simple web server that published a module 
  88.   with Bobo. ZopeHTTPServer.py is probably the easiest way
  89.   to publish a module with Bobo. It does not require anything
  90.   besides Python and Bobo to be installed. It is fast because
  91.   it publishes a module as a long running process.
  92. Why not use CGI or PCGI or Medusa?
  93.   ZopeHTTPServer is much simpler to use than other Bobo publishers.
  94.   ZopeHTTPServer does not require any configuration. It has a 
  95.   friendly license. It also offers excellent performance, threaded
  96.   publishing, and streaming response, not to mention all the Bobo
  97.   basics like authenication, control of response content-type,
  98.   file uploads, etc.
  99. Features:
  100.   -Only publishes one module at a time
  101.   -Cannot reload a module
  102.   -Does not serve files or CGI programs, only Bobo
  103.   -Single-threaded or multi-threaded publishing
  104.   -Unbuffered output so response can stream
  105.   -Works under Python 1.5 and Python 1.4 (mostly) 
  106.   -At least it's easy to use
  107. Basic Useage:
  108.   ZopeHTTPServer.py <path to module>
  109.   This publishes your module on the web.
  110. Usage:
  111.   ZopeHTTPServer.py [-p <port>] [-t] [-h <host address>] <path to module> [var=value,]
  112.   Starts a web server which publishes the specified module.
  113.   -p <port>: Run the server on the specified port. The default
  114.   port is 9673.
  115.   -t: Use a multi-threaded HTTP server. The default is a
  116.   single-threaded server. Note, your platform must support
  117.   threads to use the multi-threaded server.
  118.   -h: Specifies the host address. Python normally supplies this by default. 
  119.   -P <pid file>: Specifies a file in which the server will write its PID
  120.   -s <script_name>: Specify a virtual script name.
  121.   Specifying additional arguments of the form var=value sets environment
  122.   variables. You can use this facility to set things like BOBO_DEBUG_MODE=1
  123.   and the like. These setting override default environment settings, so you
  124.   can do things like change the SCRIPT_NAME or other weird stuff. NOTE:
  125.   var=value settings can come before or after the path to module.
  126.   You can also use the server from Python like this::
  127.     import ZopeHTTPServer
  128.     ZopeHTTPServer.main(<args>)
  129.   The form the args take is exactly the same as the command line arguments.
  130.   For example::
  131.     ZopeHTTPServer.main("-t","-p 80","/home/amos/Test.py","BOBO_DEBUG_MODE=1")
  132. Using ZopeHTTPServer behind Apache 
  133.   ZopeHTTPServer doesn't have many fancy features, but it's easy to 
  134.   use. Andreas Kostyrka suggests using ZopeHTTPServer with an Apache
  135.   proxy to get some of Apache's cool features like SSL.
  136.   
  137.   Run ZopeHTTPServer on a high port and an IP address which is behind
  138.   a firewall. For example 127.0.0.2, port 5000::
  139.   
  140.     ZopeHTTPServer -p 5000 -h 127.0.0.2 /home/amos/MyModule.py SCRIPT_NAME=/intern
  141.   
  142.   Then use Apache's proxy module to map requests to ZopeHTTPServer::
  143.   
  144.     RewriteEngine on
  145.     RewriteRule ^/intern/(.*) http://127.0.0.2:5000/$1 [P]
  146.   This rule maps request beginning with '/intern/' to ZopeHTTPServer.
  147.   Notice that we set the ZopeHTTPServer SCRIPT_NAME to '/intern' so that
  148.   it knows how it's being accessed. You can change other aspects of
  149.   ZopeHTTPServer's environment to match the proxying environment. For example
  150.   if you are using SSL, you should set HTTPS=on.
  151. Known bugs:
  152.   PUT doesn't work very well at all.
  153.   
  154.   REQUEST.write never closes the connection under Python 1.4
  155.   There is a problem with mutipart/form-data POST requests
  156.   under win32 and python 1.5.2a2. I think the problem is
  157.   with cgi.py
  158.   Under win32 interupted HTTP requests can raise exceptions,
  159.   but they don't seem to cause any problems.
  160. """
  161. __version__='$Revision: 1.15 $'[11:-2]
  162. import SocketServer
  163. import SimpleHTTPServer
  164. import BaseHTTPServer
  165. import os
  166. import sys
  167. import urllib
  168. import string
  169. import tempfile
  170. import socket
  171. try: from cStringIO import StringIO
  172. except ImportError: from StringIO import StringIO
  173. class ResponseWriter:
  174.     """Logs response and reorders it so status header is
  175.     first. After status header has been written, the rest
  176.     of the response can stream."""
  177.     
  178.     def __init__(self,handler):
  179.         self.handler=handler
  180.         self.data=""
  181.         self.latch=None
  182.         
  183.     def write(self,data):
  184.         if self.latch:
  185.             self.handler.wfile.write(data)
  186.         else:
  187.             self.data=self.data+data
  188.             start=string.find(self.data,"Status: ")
  189.             if start != -1:
  190.                 end=string.find(self.data,"n",start)
  191.                 status=self.data[start+8:end]
  192.                 code, message=tuple(string.split(status," ",1))
  193.                 self.handler.send_response(string.atoi(code),message)
  194.                 self.handler.wfile.write(self.data[:start]+
  195.                     self.data[end+1:])
  196.                 self.latch=1
  197.     def flush(self):
  198.         pass
  199.     
  200. class BoboRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
  201.     """Handler for GET, HEAD and POST. Only publishes one
  202.     Bobo module at a time. All URLs go to the published module.
  203.     Does not serve files or CGI programs.
  204.     """
  205.     
  206.     server_version = "ZopeHTTP/" + __version__
  207.     buffer_size = 8192
  208.     env_override={}
  209.     script=''
  210.     def setup(self):
  211.         """overrides defaults to set larger buffer for rfile
  212.         otherwise large POST requests seem to take forever."""
  213.         self.connection = self.request
  214.         self.rfile = self.connection.makefile('rb', self.buffer_size)
  215.         self.wfile = self.connection.makefile('wb', 0)
  216.     def hack_si(self):
  217.         #cgi.py doesn't like to parse file uploads unless we do this
  218.         si_len=string.atoi(self.headers.getheader('content-length'))
  219.         if si_len < 1048576:
  220.             si=StringIO()
  221.             si.write(self.rfile.read(si_len))
  222.         else:
  223.             bufsize=self.buffer_size
  224.             si=tempfile.TemporaryFile()
  225.             while si_len > 0:
  226.                 if si_len < bufsize:
  227.                     bufsize=si_len
  228.                 data=self.rfile.read(bufsize)
  229.                 si_len=si_len - len(data)
  230.                 si.write(data)
  231.         si.seek(0)
  232.         self.rfile=si
  233.     def do_POST(self):
  234.         if string.find(self.headers.getheader('content-type'),
  235.             'multipart/form-data') != -1:
  236.             self.hack_si()
  237.         self.publish_module()
  238.     def do_GET(self):
  239.         self.publish_module()
  240.     def do_HEAD(self):
  241.         #is this necessary?
  242.         self.publish_module()
  243.     
  244.     def do_PUT(self):
  245.         #doesn't work very well yet
  246.         self.env_override['REQUEST_METHOD']='PUT'
  247.         self.env_override['CONTENT_TYPE']='application/data'
  248.         self.hack_si()
  249.         self.publish_module()
  250.     def publish_module(self):
  251.         publish_module(
  252.             self.module_name,
  253.             stdin=self.rfile,
  254.             stdout=ResponseWriter(self),
  255.             stderr=sys.stderr,
  256.             environ=self.get_environment())
  257.     def get_environment(self):
  258.         #Partially derived from CGIHTTPServer
  259.         env={}
  260.         env['SERVER_SOFTWARE'] = self.version_string()
  261.         env['SERVER_NAME'] = self.server.server_name
  262.         env['SERVER_PORT'] = str(self.server.server_port)
  263.         env['SERVER_PROTOCOL'] = self.protocol_version
  264.         env['GATEWAY_INTERFACE'] = 'CGI/1.1'
  265.         rest = self.path
  266.         i = string.rfind(rest, '?')
  267.         if i >= 0:
  268.             rest, query = rest[:i], rest[i+1:]
  269.         else:
  270.             query = ''
  271.         uqrest = urllib.unquote(rest)
  272. script=self.script
  273. if script:
  274.     env['SCRIPT_NAME'] = script[:-1]
  275.     if uqrest[:len(script)]==script:
  276. uqrest=uqrest[len(script)-1:]
  277. else: env['SCRIPT_NAME'] = ''
  278.         env['PATH_INFO'] = uqrest
  279.         env['REQUEST_METHOD'] = self.command
  280.         env['PATH_TRANSLATED'] = self.translate_path(uqrest)
  281.         if query:
  282.             env['QUERY_STRING'] = query
  283.         host = self.address_string()
  284.         if host != self.client_address[0]:
  285.             env['REMOTE_HOST'] = host
  286.         env['REMOTE_ADDR'] = self.client_address[0]
  287.         env['CONTENT_TYPE'] = self.headers.getheader('content-type')
  288.         length = self.headers.getheader('content-length')
  289.         if length:
  290.             env['CONTENT_LENGTH'] = length
  291.         # handle the rest of the environment
  292.         for k,v in self.headers.items():
  293.             k=string.upper(string.join(string.split(k,"-"),"_"))
  294.             if not env.has_key(k) and v:
  295.                 env['HTTP_'+k]=v
  296.         if self.env_override:
  297.             for k,v in self.env_override.items():
  298.                 env[k]=v
  299.         return env
  300. class NonThreadingHTTPServer(BaseHTTPServer.HTTPServer):
  301.     "The normal HTTPServer with some socket tweaks"
  302.     
  303.     def server_bind(self):
  304.         # Modified version of server_bind that allows unconnected
  305.         # win32 boxes to run the server without errors.
  306.         self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
  307.         SocketServer.TCPServer.server_bind(self)
  308.         host, port = self.socket.getsockname()
  309.         hostname=host
  310.         if not host or host == '0.0.0.0':
  311.             host = socket.gethostname()
  312.         try:
  313.             hostname, hostnames, hostaddrs = socket.gethostbyaddr(host)
  314.             if '.' not in hostname:
  315.                 for host in hostnames:
  316.                     if '.' in host:
  317.                         hostname = host
  318.                         break
  319.         except:
  320.             pass
  321.         self.server_name = hostname
  322.         self.server_port = port
  323.     def handle_request(self):
  324.         """Handle one request, possibly blocking."""
  325.         request, client_address = self.get_request()
  326.         if self.verify_request(request, client_address):
  327.             try:
  328.                 self.process_request(request, client_address)
  329.             except SystemExit:
  330.                 self.handle_error(request, client_address)
  331.                 sys.exit(0)
  332.             except:
  333.                 self.handle_error(request, client_address)
  334.                 
  335. class ThreadingHTTPServer(SocketServer.ThreadingMixIn,
  336.     NonThreadingHTTPServer):
  337.     "A threading HTTPServer with some socket tweaks"
  338.     pass
  339. def try_to_become_nobody():
  340.     # from CGIHTTPServer
  341.     try: import pwd
  342.     except: return
  343.     try:
  344.         nobody = pwd.getpwnam('nobody')[2]
  345.     except pwd.error:
  346.         nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
  347.     try: os.setuid(nobody)
  348.     except os.error: pass    
  349. def set_published_module(file,klass,env=None):
  350.     dir,file=os.path.split(file)
  351.     name,ext=os.path.splitext(file)
  352.     klass.module_name=name
  353.     klass.module_dir=dir
  354.     cdir=os.path.join(dir,'Components')
  355.     sys.path[0:0]=[dir,cdir,os.path.join(cdir,sys.platform)]
  356.     
  357.     if env is not None:
  358.         klass.env_override=env
  359.     
  360.     global publish_module
  361.     try:
  362.         import ZPublisher
  363.         publish_module=ZPublisher.publish_module
  364.     except:
  365.         import cgi_module_publisher
  366.         publish_module=cgi_module_publisher.publish_module
  367.         
  368.     __import__(name) # to catch problem modules right away
  369.     print "Publishing module %s" % name
  370. def start(module_file, host='', port=8080, threading=None,env=None):
  371.     set_published_module(module_file,BoboRequestHandler,env)
  372.     server_address = (host, port)
  373.     if threading:
  374.         try:
  375.             import thread
  376.             httpd = ThreadingHTTPServer(server_address,
  377.                 BoboRequestHandler)
  378.             print "Using threading server"
  379.         except ImportError:
  380.             httpd = NonThreadingHTTPServer(server_address,
  381.                 BoboRequestHandler)
  382.     else:
  383.         httpd = NonThreadingHTTPServer(server_address,
  384.             BoboRequestHandler)
  385.     print "Serving HTTP on port", port, "..."
  386.     try_to_become_nobody()
  387.     try:
  388.         httpd.serve_forever()
  389.     except:
  390.         httpd.socket.close()
  391.         sys.exit(1)
  392. def die(message=''):
  393.     if message: print 'nError: %sn' % message
  394.     print __doc__
  395.     sys.exit(1)
  396. def main(args=None):
  397.     args=args or sys.argv[1:]
  398.     import getopt
  399.     optlist, args=getopt.getopt(args,"tp:h:P:s:")
  400.     if len(args) < 1: die()
  401.     env={}
  402.     module_file=''
  403.     for a in args:
  404.         a=string.split(a,'=')
  405.         if len(a)==1:
  406.             if module_file: die('Unrecognized argument: %s' % a[0])
  407.             module_file=a[0]
  408.         elif len(a)==0: continue
  409.         else:
  410.             k, v = a[0], string.join(a[1:],'=')
  411.             os.environ[k]=v
  412.             env[k]=v
  413.     port=9673
  414.     threading=None
  415.     host=''
  416.     for k,v in optlist:
  417.         if k=="-p":
  418.             port=string.atoi(v)
  419.         elif k=="-t":
  420.             threading=1
  421.         elif k=="-h":
  422.             host=v
  423.         elif k=="-P":
  424.             open(v,'w').write(str(os.getpid()))
  425. elif k=='-s':
  426.     while v[:1]=='/': v=v[1:]
  427.     while v[-1:]=='/': v=v[:-1]
  428.     BoboRequestHandler.script="/%s/" % v
  429.     start(module_file,host,port,threading,env)
  430. if __name__=="__main__": 
  431.     if os.environ.has_key("QUERY_STRING"):
  432. print "Content-type: text/plainn"
  433.     sys.path.insert(0, os.path.dirname(sys.argv[0]))
  434.     import bobomailrc
  435.     sys.path.insert(0, bobomailrc.app_dir)
  436.     
  437.     bobomailrc.image_dir = "GetFile?images"
  438.     if "-d" in sys.argv:
  439. sys.stdout = sys.stderr = open(bobomailrc.access_log, "a")
  440. sys.argv.remove("-d")
  441.     #remove "-t" if your Python-interpreter doesn't support threads
  442.     main(["-t", "-p", str(bobomailrc.httpd_port), "main"])