Пример #1
0
class Task:
    """Represents the task of serving a single HTTP Request.

    The httpy._zope.server machinery instantiates this once for each HTTP
    request, and calls service in its own thread.

    """

    implements(ITask)

    def __init__(self, channel, request):
        """Takes an IServerChannel (httpy._zope) and an IRequest (httpy).
        """

        try:
            # Set these early in case we fail.
            self.channel = channel
            self.server_config = self.channel.server.config
            self.dev_mode = os.environ['HTTPY_MODE'] == 'development'

            # This is where we are likely to fail.
            self.request = Request(request)
            self.app = self.find_app()

            if int(os.environ.get('HTTPY_VERBOSITY', 0)) >= 99:
                request_lines = request.raw.splitlines()
                raw_request = os.linesep.join(request_lines)
                log(99, raw_request)

        except:
            self.fail()

    def find_app(self):
        """Find an app with which to serve the requested URI.
        """

        app = None
        for _app in self.server_config.apps:
            if self.request.path.startswith(_app.uri_root):
                app = _app
                break
        if app is None:
            # This catches, e.g., ../../../../../../../../../etc/master.passwd
            raise Exception("Unable to find an application to serve " +
                            "%s." % self.request.path)

        log(98, "Using %s for this request" % app)
        return app

    # ITask contracts
    # ===============
    # service, cancel, defer

    def service(self):

        try:
            self.respond()
        except Response, response:
            self.deliver(response)
        except:
Пример #2
0
class SyslogLogger(m_syslog.syslog_client):
    """syslog is a line-oriented log protocol - this class would be
       appropriate for FTP or HTTP logs, but not for dumping stderr
       to.

       TODO: a simple safety wrapper that will ensure that the line
       sent to syslog is reasonable.

       TODO: async version of syslog_client: now, log entries use
       blocking send()
    """

    implements(IMessageLogger)

    svc_name = 'httpy._zope'
    pid_str = str(os.getpid())

    def __init__(self, address, facility='user'):
        m_syslog.syslog_client.__init__(self, address)
        self.facility = m_syslog.facility_names[facility]
        self.address = address

    def __repr__(self):
        return '<syslog logger address=%s>' % (repr(self.address))

    def logMessage(self, message):
        'See IMessageLogger'
        m_syslog.syslog_client.log(self,
                                   '%s[%s]: %s' %
                                   (self.svc_name, self.pid_str, message),
                                   facility=self.facility,
                                   priority=m_syslog.LOG_INFO)
Пример #3
0
class Responder:
    """This default responder infers a site from your filesystem.
    """

    implements(IBaseResponder)

    root = ''

    def __init__(self, root=None):

        # Set paths.
        # ==========

        root = root or os.getcwd()
        if not os.path.isdir(root):
            raise ValueError("root '%s' does not point to a directory" % root)
        self.root = os.path.realpath(root)
        self.__ = os.path.join(self.root, '__')
        if not os.path.exists(self.__):
            self.__ = None


        # Find findables.
        # ===============

        self.framework = self.get_framework()
        self.responders = self.get_responders()



    # Contracts
    # =========

    def respond(self, request):
        """Given a Request, return an IResponse.

        This is where we insert our framework hooks.

        """

        # Inbound hooks
        # =============

        if hasattr(self.framework, 'get_responder'):
            responder = self.framework.get_responder(request)
        else:
            responder = self.get_responder(request)

        if hasattr(self.framework, 'wrap_request'):
            request = self.framework.wrap_request(responder, request)


        # Get a response
        # ==============

        try:
            response = responder.respond(request)
        except Response, response:
            response = response
        except:
Пример #4
0
class ResolvingLogger(object):
    """Feed (ip, message) combinations into this logger to get a
    resolved hostname in front of the message.  The message will not
    be logged until the PTR request finishes (or fails)."""

    implements(IRequestLogger)

    def __init__(self, resolver, logger):
        self.resolver = resolver
        # logger is an IMessageLogger
        self.logger = logger

    class logger_thunk(object):
        def __init__(self, message, logger):
            self.message = message
            self.logger = logger

        def __call__(self, host, ttl, answer):
            if not answer:
                answer = host
            self.logger.logMessage('%s%s' % (answer, self.message))

    def logRequest(self, ip, message):
        'See IRequestLogger'
        self.resolver.resolve_ptr(ip, self.logger_thunk(message, self.logger))
Пример #5
0
class Application:
    """This is httpy's default request respondor.
    """

    implements(IApplication)

    def __init__(self):
        """Takes a ApplicationConfig object.
        """
        self.dev_mode = os.environ.get("HTTPY_MODE") == 'development'
        from httpy import utils  # dodge circular import
        self.uri_to_fs = utils.uri_to_fs

    def __repr__(self):
        return "<default httpy app>"

    __str__ = __repr__

    def respond(self, request):
        """Given an httpy.Request, raise an httpy.Response.
        """
        self.request = request
        fs_path = self.uri_to_fs(self.site_root,
                                 self.fs_root,
                                 self.uri_root,
                                 request.path,
                                 defaults=('index.html', 'index.htm'))
        self.serve_static(fs_path)

    def serve_static(self, fs_path):
        """Given a filesystem path to a static resource, serve it.
        """

        # Get basic info from the filesystem and start building a response.
        # =================================================================

        mtime = os.stat(fs_path)[stat.ST_MTIME]
        content_type = mimetypes.guess_type(fs_path)[0] or 'text/plain'
        response = Response(200)

        # Support 304s, but only in deployment mode.
        # ==========================================

        if not self.dev_mode:
            ims = self.request.message.get('If-Modified-Since')
            if ims:
                mod_since = rfc822.parsedate(ims)
                last_modified = time.gmtime(mtime)
                if last_modified[:6] <= mod_since[:6]:
                    response.code = 304

        # Finish building the response and raise it.
        # ========================================

        response.headers['Last-Modified'] = rfc822.formatdate(mtime)
        response.headers['Content-Type'] = content_type
        if response.code != 304:
            response.body = file(fs_path, 'rb').read()
        raise response
Пример #6
0
class UnresolvingLogger(object):
    """Just in case you don't want to resolve"""

    implements(IRequestLogger)

    def __init__(self, logger):
        self.logger = logger

    def logRequest(self, ip, message):
        'See IRequestLogger'
        self.logger.logMessage('%s%s' % (ip, message))
Пример #7
0
class SleepingTask(object):

    implements(ITask)

    def service(self):
        sleep(0.2)

    def cancel(self):
        pass

    def defer(self):
        pass
Пример #8
0
class Transaction:
    """This is httpy's default request processor.
    """

    implements(ITransaction)

    def __init__(self, config):
        """Takes a TransactionConfig object.
        """
        self.config = config

    def process(self, request):
        """Given an httpy.Request, raise an httpy.Response.
        """

        self.request = request
        fs_path = uri_to_fs(config=self.config,
                            resource_uri_path=request.path,
                            defaults=('index.html', 'index.htm'))
        self.serve_static(fs_path)

    def serve_static(self, fs_path):
        """Given a filesystem path to a static resource, serve it.
        """

        # Get basic info from the filesystem and start building a response.
        # =================================================================

        mtime = os.stat(fs_path)[stat.ST_MTIME]
        content_type = mimetypes.guess_type(fs_path)[0] or 'text/plain'
        response = Response(200)

        # Support 304s, but only in deployment mode.
        # ==========================================

        if self.config.mode == 'deployment':
            ims = self.request.message.get('If-Modified-Since')
            if ims:
                mod_since = rfc822.parsedate(ims)
                last_modified = time.gmtime(mtime)
                if last_modified[:6] <= mod_since[:6]:
                    response.code = 304

        # Finish building the response and raise it.
        # ========================================

        response.headers['Last-Modified'] = rfc822.formatdate(mtime)
        response.headers['Content-Type'] = content_type
        if response.code != 304:
            response.body = file(fs_path, 'rb').read()
        raise response
Пример #9
0
class FileLogger(object):
    """Simple File Logger
    """

    implements(IMessageLogger)

    def __init__(self, file, flush=1, mode='a'):
        """pass this either a path or a file object."""
        if type(file) is StringType:
            if (file == '-'):
                import sys
                self.file = sys.stdout
            else:
                self.file = open(file, mode)
        else:
            self.file = file
        self.do_flush = flush

    def __repr__(self):
        return '<file logger: %s>' % self.file

    def write(self, data):
        self.file.write(data)
        self.maybe_flush()

    def writeline(self, line):
        self.file.writeline(line)
        self.maybe_flush()

    def writelines(self, lines):
        self.file.writelines(lines)
        self.maybe_flush()

    def maybe_flush(self):
        if self.do_flush:
            self.file.flush()

    def flush(self):
        self.file.flush()

    def softspace(self, *args):
        pass

    def logMessage(self, message):
        'See IMessageLogger'
        if message[-1] not in ('\r', '\n'):
            self.write(message + '\n')
        else:
            self.write(message)
Пример #10
0
class TailLogger(object):
    """Keep track of the last <size> log messages"""

    implements(IMessageLogger)

    def __init__(self, logger, size=500):
        self.size = size
        self.logger = logger
        self.messages = []

    def logMessage(self, message):
        'See IMessageLogger'
        self.messages.append(strip_eol(message))
        if len(self.messages) > self.size:
            del self.messages[0]
        self.logger.logMessage(message)
Пример #11
0
class LineTask(object):
    """This is a generic task that can be used with command line
       protocols to handle commands in a separate thread.
    """
    implements(ITask)

    def __init__(self, channel, command, m_name):
        self.channel = channel
        self.m_name = m_name
        self.args = command.args

        self.close_on_finish = 0

    def service(self):
        """Called to execute the task.
        """
        try:
            try:
                self.start()
                getattr(self.channel, self.m_name)(self.args)
                self.finish()
            except socket.error:
                self.close_on_finish = 1
                if self.channel.adj.log_socket_errors:
                    raise
            except:
                self.channel.exception()
        finally:
            if self.close_on_finish:
                self.channel.close_when_done()

    def cancel(self):
        'See ITask'
        self.channel.close_when_done()

    def defer(self):
        'See ITask'
        pass

    def start(self):
        now = time.time()
        self.start_time = now

    def finish(self):
        hit_log = self.channel.server.hit_log
        if hit_log is not None:
            hit_log.log(self)
Пример #12
0
class LineCommandParser(object):
    """Line Command parser. Arguments are left alone for now."""

    implements(IStreamConsumer)

    # See IStreamConsumer
    completed = 0
    inbuf = ''
    cmd = ''
    args = ''
    empty = 0

    max_line_length = 1024  # Not a hard limit

    def __init__(self, adj):
        """
        adj is an Adjustments object.
        """
        self.adj = adj

    def received(self, data):
        'See IStreamConsumer'
        if self.completed:
            return 0  # Can't consume any more.
        pos = data.find('\n')
        datalen = len(data)
        if pos < 0:
            self.inbuf = self.inbuf + data
            if len(self.inbuf) > self.max_line_length:
                # Don't accept any more.
                self.completed = 1
            return datalen
        else:
            # Line finished.
            s = data[:pos + 1]
            self.inbuf = self.inbuf + s
            self.completed = 1
            line = self.inbuf.strip()
            self.parseLine(line)
            return len(s)

    def parseLine(self, line):
        parts = line.split(' ', 1)
        if len(parts) == 2:
            self.cmd, self.args = parts
        else:
            self.cmd = parts[0]
Пример #13
0
class PythonLogger(object):
    """Proxy for Python's logging module"""

    implements(IMessageLogger)

    def __init__(self, name=None, level=logging.INFO):
        self.name = name
        self.level = level
        self.logger = logging.getLogger(name)

    def __repr__(self):
        return '<python logger: %s %s>' % (self.name,
                    logging.getLevelName(self.level))

    def logMessage(self, message):
        """See IMessageLogger"""
        self.logger.log(self.level, message.rstrip())
Пример #14
0
class Task:
    """Represents the task of serving a single HTTP Request.

    The httpy._zope.server machinery instantiates this once for each HTTP
    request, and calls service in its own thread.

    """

    implements(ITask)

    def __init__(self, channel, request):
        """Takes an IServerChannel (httpy._zope) and an IRequest (httpy).
        """

        self.channel = channel
        self.server = self.channel.server
        self.responder = self.channel.server.responder
        self.request = request
        self.deliver = make_deliver(self.make_request, self.responder,
                                    self.channel)

        if 0:  # turned off cause it eats CPU even when not printed
            request_lines = request.raw.splitlines()
            raw_request = os.linesep.join(request_lines)
            logger.debug(raw_request)

    def make_request(self, request):
        return Request(request)

    # ITask contracts
    # ===============
    # service, cancel, defer

    def service(self):
        self.deliver(self.request)
        self.channel.close_when_done()

    def cancel(self):
        self.channel.close_when_done()

    def defer(self):
        pass
Пример #15
0
class DemoFileSystemAccess(object):
    __doc__ = IFileSystemAccess.__doc__

    implements(IFileSystemAccess)

    def __init__(self, files, users):
        self.files = files
        self.users = users

    def authenticate(self, credentials):
        "See httpy._zope.server.interfaces.ftp.IFileSystemAccess"
        user, password = credentials
        if user != 'anonymous':
            if self.users.get(user) != password:
                raise Unauthorized
        return user

    def open(self, credentials):
        "See httpy._zope.server.interfaces.ftp.IFileSystemAccess"
        user = self.authenticate(credentials)
        return DemoFileSystem(self.files, user)
Пример #16
0
class Task:
    """Represents the task of serving a single HTTP Request.

    The httpy._zope.server machinery instantiates this once for each HTTP
    request, and calls service in its own thread.

    """

    implements(ITask)


    def __init__(self, channel, request):
        """Takes an IServerChannel (httpy._zope) and an IRequest (httpy).
        """

        self.channel    = channel
        self.server     = self.channel.server
        self.responder  = self.channel.server.responder
        self.request    = request
        self.deliver    = Deliver( Request
                                 , self.responder
                                 , self.channel
                                  )


    # ITask contracts
    # ===============
    # service, cancel, defer

    def service(self):
        self.deliver(self.request)
        self.channel.close_when_done()

    def cancel(self):
        self.channel.close_when_done()

    def defer(self):
        pass
Пример #17
0
class SocketLogger(asynchat.async_chat):
    """Log to a stream socket, asynchronously."""

    implements(IMessageLogger)

    def __init__(self, address):
        if type(address) == type(''):
            self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
        else:
            self.create_socket(socket.AF_INET, socket.SOCK_STREAM)

        self.connect(address)
        self.address = address

    def __repr__(self):
        return '<socket logger: address=%s>' % (self.address)

    def logMessage(self, message):
        'See IMessageLogger'
        if message[-2:] != '\r\n':
            self.socket.push(message + '\r\n')
        else:
            self.socket.push(message)
Пример #18
0
class DemoFileSystem(object):
    __doc__ = IFileSystem.__doc__

    implements(IFileSystem)

    File = File
    Directory = Directory

    def __init__(self, files, user=''):
        self.files = files
        self.user = user

    def get(self, path, default=None):

        while path.startswith('/'):
            path = path[1:]

        d = self.files
        if path:
            for name in path.split('/'):
                if d.type is not 'd':
                    return default
                if not d.accessable(self.user):
                    raise Unauthorized
                d = d.get(name)
                if d is None:
                    break

        return d

    def getany(self, path):
        d = self.get(path)
        if d is None:
            raise OSError("No such file or directory:", path)
        return d

    def getdir(self, path):
        d = self.getany(path)
        if d.type != 'd':
            raise OSError("Not a directory:", path)
        return d

    def getfile(self, path):
        d = self.getany(path)
        if d.type != 'f':
            raise OSError("Not a file:", path)
        return d

    def getwdir(self, path):
        d = self.getdir(path)
        if not d.accessable(self.user, write):
            raise OSError("Permission denied")
        return d

    def type(self, path):
        "See httpy._zope.server.interfaces.ftp.IFileSystem"
        f = self.get(path)
        return getattr(f, 'type', None)

    def names(self, path, filter=None):
        "See httpy._zope.server.interfaces.ftp.IFileSystem"
        f = list(self.getdir(path))
        if filter is not None:
            f = [name for name in f if filter(name)]

        return f

    def _lsinfo(self, name, file):
        info = {
            'type': file.type,
            'name': name,
            'group_read': file.accessable(self.user, read),
            'group_write': file.accessable(self.user, write),
            }
        if file.type == 'f':
            info['size'] = len(file.data)
        if file.modified is not None:
            info['mtime'] = file.modified

        return info

    def ls(self, path, filter=None):
        "See httpy._zope.server.interfaces.ftp.IFileSystem"
        f = self.getdir(path)
        if filter is None:
            return [self._lsinfo(name, f.files[name])
                    for name in f
                    ]

        return [self._lsinfo(name, f.files[name])
                for name in f
                if filter(name)]

    def readfile(self, path, outstream, start=0, end=None):
        "See httpy._zope.server.interfaces.ftp.IFileSystem"
        f = self.getfile(path)

        data = f.data
        if end is not None:
            data = data[:end]
        if start:
            data = data[start:]

        outstream.write(data)

    def lsinfo(self, path):
        "See httpy._zope.server.interfaces.ftp.IFileSystem"
        f = self.getany(path)
        return self._lsinfo(posixpath.split(path)[1], f)

    def mtime(self, path):
        "See httpy._zope.server.interfaces.ftp.IFileSystem"
        f = self.getany(path)
        return f.modified

    def size(self, path):
        "See httpy._zope.server.interfaces.ftp.IFileSystem"
        f = self.getany(path)
        return len(getattr(f, 'data', ''))

    def mkdir(self, path):
        "See httpy._zope.server.interfaces.ftp.IFileSystem"
        path, name = posixpath.split(path)
        d = self.getwdir(path)
        if name in d.files:
            raise OSError("Already exists:", name)
        newdir = self.Directory()
        newdir.grant(self.user, read | write)
        d.files[name] = newdir

    def remove(self, path):
        "See httpy._zope.server.interfaces.ftp.IFileSystem"
        path, name = posixpath.split(path)
        d = self.getwdir(path)
        if name not in d.files:
            raise OSError("Not exists:", name)
        f = d.files[name]
        if f.type == 'd':
            raise OSError('Is a directory:', name)
        del d.files[name]

    def rmdir(self, path):
        "See httpy._zope.server.interfaces.ftp.IFileSystem"
        path, name = posixpath.split(path)
        d = self.getwdir(path)
        if name not in d.files:
            raise OSError("Not exists:", name)
        f = d.files[name]
        if f.type != 'd':
            raise OSError('Is not a directory:', name)
        del d.files[name]

    def rename(self, old, new):
        "See httpy._zope.server.interfaces.ftp.IFileSystem"
        oldpath, oldname = posixpath.split(old)
        newpath, newname = posixpath.split(new)

        olddir = self.getwdir(oldpath)
        newdir = self.getwdir(newpath)

        if oldname not in olddir.files:
            raise OSError("Not exists:", oldname)
        if newname in newdir.files:
            raise OSError("Already exists:", newname)

        newdir.files[newname] = olddir.files[oldname]
        del olddir.files[oldname]

    def writefile(self, path, instream, start=None, end=None, append=False):
        "See httpy._zope.server.interfaces.ftp.IFileSystem"
        path, name = posixpath.split(path)
        d = self.getdir(path)
        f = d.files.get(name)
        if f is None:
            d = self.getwdir(path)
            f = d.files[name] = self.File()
            f.grant(self.user, read | write)
        elif f.type != 'f':
            raise OSError("Can't overwrite a directory")

        if not f.accessable(self.user, write):
            raise OSError("Permission denied")

        if append:
            f.data += instream.read()
        else:

            if start:
                if start < 0:
                    raise ValueError("Negative starting file position")
                prefix = f.data[:start]
                if len(prefix) < start:
                    prefix += '\0' * (start - len(prefix))
            else:
                prefix = ''
                start=0

            if end:
                if end < 0:
                    raise ValueError("Negative ending file position")
                l = end - start
                newdata = instream.read(l)

                f.data = prefix+newdata+f.data[start+len(newdata):]
            else:
                f.data = prefix + instream.read()

    def writable(self, path):
        "See httpy._zope.server.interfaces.ftp.IFileSystem"
        path, name = posixpath.split(path)
        try:
            d = self.getdir(path)
        except OSError:
            return False
        if name not in d:
            return d.accessable(self.user, write)
        f = d[name]
        return f.type == 'f' and f.accessable(self.user, write)
Пример #19
0
class ServerChannelBase(DualModeChannel, object):
    """Base class for a high-performance, mixed-mode server-side channel."""

    implements(IServerChannel, ITask)

    # See httpy._zope.server.interfaces.IServerChannel
    parser_class = None  # Subclasses must provide a parser class
    task_class = None  # ... and a task class.

    active_channels = {}  # Class-specific channel tracker
    next_channel_cleanup = [0]  # Class-specific cleanup time
    proto_request = None  # A request parser instance
    last_activity = 0  # Time of last activity
    tasks = None  # List of channel-related tasks to execute
    running_tasks = False  # True when another thread is running tasks

    #
    # ASYNCHRONOUS METHODS (including __init__)
    #

    def __init__(self, server, conn, addr, adj=None):
        """See async.dispatcher"""
        DualModeChannel.__init__(self, conn, addr, adj)
        self.server = server
        self.last_activity = t = self.creation_time
        self.check_maintenance(t)

    def add_channel(self, map=None):
        """See async.dispatcher

        This hook keeps track of opened channels.
        """
        DualModeChannel.add_channel(self, map)
        self.__class__.active_channels[self._fileno] = self

    def del_channel(self, map=None):
        """See async.dispatcher

        This hook keeps track of closed channels.
        """
        DualModeChannel.del_channel(self, map)
        ac = self.__class__.active_channels
        fd = self._fileno
        if fd in ac:
            del ac[fd]

    def check_maintenance(self, now):
        """See async.dispatcher

        Performs maintenance if necessary.
        """
        ncc = self.__class__.next_channel_cleanup
        if now < ncc[0]:
            return
        ncc[0] = now + self.adj.cleanup_interval
        self.maintenance()

    def maintenance(self):
        """See async.dispatcher

        Kills off dead connections.
        """
        self.kill_zombies()

    def kill_zombies(self):
        """See async.dispatcher

        Closes connections that have not had any activity in a while.

        The timeout is configured through adj.channel_timeout (seconds).
        """
        now = time.time()
        cutoff = now - self.adj.channel_timeout
        for channel in self.active_channels.values():
            if (channel is not self and not channel.running_tasks
                    and channel.last_activity < cutoff):
                channel.close()

    def received(self, data):
        """See async.dispatcher

        Receives input asynchronously and send requests to
        handle_request().
        """
        preq = self.proto_request
        while data:
            if preq is None:
                preq = self.parser_class(self.adj)
            n = preq.received(data)
            if preq.completed:
                # The request is ready to use.
                self.proto_request = None
                if not preq.empty:
                    self.handle_request(preq)
                preq = None
            else:
                self.proto_request = preq
            if n >= len(data):
                break
            data = data[n:]

    def handle_request(self, req):
        """Creates and queues a task for processing a request.

        Subclasses may override this method to handle some requests
        immediately in the main async thread.
        """
        task = self.task_class(self, req)
        self.queue_task(task)

    def handle_error(self):
        """See async.dispatcher

        Handles program errors (not communication errors)
        """
        t, v = sys.exc_info()[:2]
        if t is SystemExit or t is KeyboardInterrupt:
            raise t, v
        asyncore.dispatcher.handle_error(self)

    def handle_comm_error(self):
        """See async.dispatcher

        Handles communication errors (not program errors)
        """
        if self.adj.log_socket_errors:
            self.handle_error()
        else:
            # Ignore socket errors.
            self.close()

    #
    # BOTH MODES
    #

    def queue_task(self, task):
        """Queue a channel-related task to be executed in another thread."""
        start = False
        task_lock.acquire()
        try:
            if self.tasks is None:
                self.tasks = []
            self.tasks.append(task)
            if not self.running_tasks:
                self.running_tasks = True
                start = True
        finally:
            task_lock.release()
        if start:
            self.set_sync()
            self.server.addTask(self)

    #
    # ITask implementation.  Delegates to the queued tasks.
    #

    def service(self):
        """Execute all pending tasks"""
        while True:
            task = None
            task_lock.acquire()
            try:
                if self.tasks:
                    task = self.tasks.pop(0)
                else:
                    # No more tasks
                    self.running_tasks = False
                    self.set_async()
                    break
            finally:
                task_lock.release()
            try:
                task.service()
            except:
                # propagate the exception, but keep executing tasks
                self.server.addTask(self)
                raise

    def cancel(self):
        """Cancels all pending tasks"""
        task_lock.acquire()
        try:
            if self.tasks:
                old = self.tasks[:]
            else:
                old = []
            self.tasks = []
            self.running_tasks = False
        finally:
            task_lock.release()
        try:
            for task in old:
                task.cancel()
        finally:
            self.set_async()

    def defer(self):
        pass
Пример #20
0
class ServerBase(asyncore.dispatcher, object):
    """Async. server base for launching derivatives of ServerChannelBase."""

    implements(IServer)

    # See httpy._zope.server.interfaces.IServer
    channel_class = None  # Override with a channel class.
    SERVER_IDENT = 'httpy._zope.server.serverbase'  # Override.

    def __init__(self,
                 ip,
                 port,
                 task_dispatcher=None,
                 adj=None,
                 start=1,
                 hit_log=None,
                 verbose=0):
        if adj is None:
            adj = default_adj
        self.adj = adj
        asyncore.dispatcher.__init__(self)
        self.port = port
        self.task_dispatcher = task_dispatcher
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind((ip, port))
        self.verbose = verbose
        self.hit_log = hit_log
        self.logger = logging.getLogger(self.__class__.__name__)
        self.server_name = self.computeServerName(ip)

        if start:
            self.accept_connections()

    def log(self, message):
        """See httpy._zope.server.interfaces.IDispatcherLogging"""
        # Override asyncore's default log()
        self.logger.info(message)

    level_mapping = {
        'info': logging.INFO,
        'error': logging.ERROR,
        'warning': logging.WARN,
    }

    def log_info(self, message, type='info'):
        """See httpy._zope.server.interfaces.IDispatcherLogging"""
        self.logger.log(self.level_mapping.get(type, logging.INFO), message)

    def computeServerName(self, ip=''):
        """Given an IP, try to determine the server name."""
        if ip:
            server_name = str(ip)
        else:
            server_name = str(socket.gethostname())
        # Convert to a host name if necessary.
        is_hostname = 0
        for c in server_name:
            if c != '.' and not c.isdigit():
                is_hostname = 1
                break
        if not is_hostname:
            if self.verbose:
                self.log_info('Computing hostname', 'info')
            try:
                server_name = socket.gethostbyaddr(server_name)[0]
            except socket.error:
                if self.verbose:
                    self.log_info('Cannot do reverse lookup', 'info')
        return server_name

    def accept_connections(self):
        self.accepting = 1
        self.socket.listen(self.adj.backlog)  # Circumvent asyncore's NT limit
        if self.verbose:
            self.log_info('%s started.\n'
                          '\tHostname: %s\n\tPort: %d' %
                          (self.SERVER_IDENT, self.server_name, self.port))

    def addTask(self, task):
        """See httpy._zope.server.interfaces.ITaskDispatcher"""
        td = self.task_dispatcher
        if td is not None:
            td.addTask(task)
        else:
            task.service()

    def readable(self):
        """See httpy._zope.server.interfaces.IDispatcher"""
        return (self.accepting
                and len(asyncore.socket_map) < self.adj.connection_limit)

    def writable(self):
        """See httpy._zope.server.interfaces.IDispatcher"""
        return 0

    def handle_read(self):
        """See httpy._zope.server.interfaces.IDispatcherEventHandler"""
        pass

    def handle_connect(self):
        """See httpy._zope.server.interfaces.IDispatcherEventHandler"""
        pass

    def handle_accept(self):
        """See httpy._zope.server.interfaces.IDispatcherEventHandler"""
        try:
            v = self.accept()
            if v is None:
                return
            conn, addr = v
        except socket.error:
            # Linux: On rare occasions we get a bogus socket back from
            # accept.  socketmodule.c:makesockaddr complains that the
            # address family is unknown.  We don't want the whole server
            # to shut down because of this.
            if self.adj.log_socket_errors:
                self.log_info('warning: server accept() threw an exception',
                              'warning')
            return
        for (level, optname, value) in self.adj.socket_options:
            conn.setsockopt(level, optname, value)
        self.channel_class(self, conn, addr, self.adj)
Пример #21
0
        class Foo:
             implements(IFoo)

             def foo(self):
                 pass
Пример #22
0
class ChunkedReceiver(object):

    implements(IStreamConsumer)

    chunk_remainder = 0
    control_line = ''
    all_chunks_received = 0
    trailer = ''
    completed = 0

    # max_control_line = 1024
    # max_trailer = 65536

    def __init__(self, buf):
        self.buf = buf

    def received(self, s):
        # Returns the number of bytes consumed.
        if self.completed:
            return 0
        orig_size = len(s)
        while s:
            rm = self.chunk_remainder
            if rm > 0:
                # Receive the remainder of a chunk.
                to_write = s[:rm]
                self.buf.append(to_write)
                written = len(to_write)
                s = s[written:]
                self.chunk_remainder -= written
            elif not self.all_chunks_received:
                # Receive a control line.
                s = self.control_line + s
                pos = s.find('\n')
                if pos < 0:
                    # Control line not finished.
                    self.control_line = s
                    s = ''
                else:
                    # Control line finished.
                    line = s[:pos]
                    s = s[pos + 1:]
                    self.control_line = ''
                    line = line.strip()
                    if line:
                        # Begin a new chunk.
                        semi = line.find(';')
                        if semi >= 0:
                            # discard extension info.
                            line = line[:semi]
                        sz = int(line.strip(), 16)  # hexadecimal
                        if sz > 0:
                            # Start a new chunk.
                            self.chunk_remainder = sz
                        else:
                            # Finished chunks.
                            self.all_chunks_received = 1
                    # else expect a control line.
            else:
                # Receive the trailer.
                trailer = self.trailer + s
                if trailer.startswith('\r\n'):
                    # No trailer.
                    self.completed = 1
                    return orig_size - (len(trailer) - 2)
                elif trailer.startswith('\n'):
                    # No trailer.
                    self.completed = 1
                    return orig_size - (len(trailer) - 1)
                pos = find_double_newline(trailer)
                if pos < 0:
                    # Trailer not finished.
                    self.trailer = trailer
                    s = ''
                else:
                    # Finished the trailer.
                    self.completed = 1
                    self.trailer = trailer[:pos]
                    return orig_size - (len(trailer) - pos)
        return orig_size

    def getfile(self):
        return self.buf.getfile()
Пример #23
0
class Application:
    """This is httpy's default request respondor.
    """

    implements(IApplication)

    name = 'default httpy app'

    def __init__(self):
        from httpy import utils  # dodge circular import -- still a problem?
        self.uri_to_fs = utils.uri_to_fs

    def __repr__(self):
        return "<%s>" % self.name

    __str__ = __repr__

    def respond(self, request):
        """Given an httpy.Request, raise an httpy.Response.
        """
        fs_path = self.uri_to_fs(self.site_root,
                                 self.fs_root,
                                 self.uri_root,
                                 request.path,
                                 defaults=('index.html', 'index.htm'))
        ims = request.message.get('If-Modified-Since', '')
        self.serve_static(fs_path, ims)

    def serve_static(self, fs_path, ims):
        """Given a filesystem path to a static resource, serve it.

        This is factored out for easier reuse.

        """

        # Get basic info from the filesystem and start building a response.
        # =================================================================

        mtime = os.stat(fs_path)[stat.ST_MTIME]
        content_type = mimetypes.guess_type(fs_path)[0] or 'text/plain'
        response = Response(200)

        # Support 304s, but only in deployment mode.
        # ==========================================

        if self.deploy_mode:
            if ims:
                mod_since = rfc822.parsedate(ims)
                last_modified = time.gmtime(mtime)
                if last_modified[:6] <= mod_since[:6]:
                    response.code = 304

        # Finish building the response and raise it.
        # ========================================

        response.headers['Last-Modified'] = rfc822.formatdate(mtime)
        response.headers['Content-Type'] = content_type
        if response.code != 304:
            response.body = file(fs_path, 'rb').read()
        raise response

    def close(self):
        logger.debug("closing DefaultApp")
Пример #24
0
class Task:
    """Represents the task of serving a single HTTP Request.

    The httpy._zope.server machinery instantiates this once for each HTTP
    request, and calls service in its own thread.

    """

    implements(ITask)

    def __init__(self, channel, request):
        """Takes an IServerChannel (httpy._zope) and an IRequest (httpy).
        """

        try:
            # Set these early in case we fail.
            self.channel = channel
            self.server = self.channel.server
            self.framework = self.channel.server.framework

            # This is where we are likely to fail.
            self.request = Request(request)
            self.app = self.get_app()

            if 0:  # turned off cause it eats CPU even when not printed
                request_lines = request.raw.splitlines()
                raw_request = os.linesep.join(request_lines)
                logger.debug(raw_request)

        except:
            self.fail()

    def get_app(self):
        """Get an application with which to serve the requested URI.
        """

        app = None
        for _app in self.server.apps:
            if self.request.path.startswith(_app.uri_root):
                app = _app
                break
        if app is None:
            # This catches, e.g., ../../../../../../../../../etc/master.passwd
            raise StandardError("Unable to find an application to serve " +
                                "%s." % self.request.path)

        logger.debug("Using %s for this request." % app)
        return app

    # ITask contracts
    # ===============
    # service, cancel, defer

    def service(self):

        try:
            self.respond()
        except ResponseFlag, response:
            try:
                # outbound framework hook
                response = self.framework.unwrap_response(self.app, response)
                self.deliver(response)
            except:
                self.fail()
        except:
Пример #25
0
class CGIRequest:
    """Reconstruct an httpy.Request from the environment and stdin.
    """

    implements(IRequest)

    def __init__(self, environ, stdin):
        """Takes a dictionary and a buffer.
        """

        # Request-Line
        # ============

        method = environ['REQUEST_METHOD']

        path = environ.get('PATH_INFO', '/')
        query = environ.get('QUERY_STRING', '')
        if query:
            query = '?' + query
        raw_uri = environ.get('SCRIPT_NAME', '') + path + query
        uri = urlparse.urlparse(raw_uri)
        keys = ( 'scheme'
               , 'netloc'
               , 'path'
               , 'parameters'
               , 'query'
               , 'fragment'
                )
        _uri = {}
        for i in range(len(uri)):
            k = keys[i]
            v = uri[i]
            _uri[k] = v
        uri = _uri

        http_version = environ['SERVER_PROTOCOL']

        raw_line = "%s %s %s\r\n" % (method, raw_uri, http_version)


        # Headers
        # =======

        headers = []
        WANT = ('content_type', 'content_length')
        for k, v in environ.iteritems():
            want = False
            k = k.lower()
            if k.startswith('http_'):
                k = k[5:]
                want = True
            if want or (k in WANT):
                as_string = "%s: %s" % (k.replace('_','-'), v)
                headers.append(as_string)
        raw_headers = '\r\n'.join(headers)
        raw_headers += '\r\n'
        headers = message_from_string(raw_headers)


        # Body
        # ====

        raw_body = stdin.read()


        # Save the API we want.
        # =====================

        raw = raw_line + raw_headers + raw_body

        self.raw = raw
        self.raw_line = raw_line
        self.raw_headers = raw_headers
        self.raw_body = raw_body

        self.method = method
        self.uri = uri
        self.path = path
        self.headers = headers
Пример #26
0
class ZopeRequest:
    """An implementation of IRequest that works with httpy._zope.server.
    """

    implements(IStreamConsumer, IRequest)

    raw = ''
    raw_line = ''
    raw_headers = ''
    raw_body = ''

    method = ''
    uri = {}
    path = ''
    headers = Message()

    def __init__(self, adj=None):
        """Takes an Adjustments object.
        """

        if adj is None:
            adj = default_adj
        self.adj = adj

        self._receiver = None
        self._tmp = ''
        self._raw = []

        # initialize mutable defaults
        self.uri = {}
        self.headers = Message()

    # Fulfill our IStreamConsumer contracts.
    # ======================================

    def received(self, block):
        """This takes a block of an incoming HTTP stream.

        We store anything leftover from the last block in raw string form in
        self._tmp.

        """

        self._raw.append(block)

        try:
            if not self.raw_line:
                self.get_line(block)
            elif not self.raw_headers:
                self.get_headers(block)
            elif not self.raw_body:
                self.get_body(block)

        except Complete:
            self.raw = ''.join(self._raw)
            self.completed = True

        return len(block)

    completed = False

    empty = False

    # Request-Line
    # ============

    def get_line(self, block):
        """
        """

        block = self._tmp + block
        if '\r' in block or '\n' in block:
            lines = block.splitlines(True)
            i = 0
            for line in lines:
                if not line.strip():  #could be a blank line (like from IE)
                    pass
                elif (line.endswith('\r')
                      or line.endswith('\n')):  #could be the whole thing
                    self.raw_line = line.strip()
                    self._tmp = ''.join(lines[i + 1:])
                    break
                else:  #or else it is a partial line
                    self._tmp = ''.join(lines[i:])
                    break
                i += 1
        else:
            self._tmp = block
        if self.raw_line:
            self.parse_line()

            # Decide if we have any headers.
            line_ending = lines[0][len(self.raw_line):]
            if self._tmp == line_ending:
                #self._raw.append(self._tmp) -- why is this in here?
                raise Complete

            if self._tmp:
                self.get_headers('')

    def parse_line(self):
        """Parse and validate the Request-Line.
        """

        # Tokenize the first line as a Request-Line.
        # ==========================================

        tokens = self.raw_line.strip().split()
        if len(tokens) == 3:
            method, uri, version = tokens
        elif len(tokens) == 2:
            method, uri = tokens
            version = 'HTTP/0.9'
        else:
            raise Response(
                400, "The Request-Line `%s' " % self.raw_line +
                "appears to be malformed because it has " +
                "neither two nor three tokens.")

        # Validate the URI.
        # =================
        # We build a mapping using the urlparse naming convention for the keys.
        # Then we do a little validation and cleanup.

        uri = urlparse.urlparse(uri)
        keys = ('scheme', 'netloc', 'path', 'parameters', 'query', 'fragment')
        _uri = {}
        for i in range(len(uri)):
            k = keys[i]
            v = uri[i]
            _uri[k] = v
        uri = _uri

        if not uri['path']:
            # this catches, e.g., '//foo'
            raise Response(400)
        if '%' in uri['path']:
            uri['path'] = urllib.unquote(uri['path'])

        # Validate the version.
        # =====================
        # Consider raising Response(505) here ... for 0.9? 2.0?

        m = HTTP_VERSION.match(version)
        if not m:
            raise Response(
                400, "The HTTP-Version `%s' appears to " % version +
                "be malformed because it does not match the " +
                "pattern `^HTTP/\d+\.\d+$'.")

        # Save a few absolutely basic things for application use.
        # =======================================================

        self.method = method
        self.uri = uri
        self.path = uri['path']

    # Headers
    # =======

    def get_headers(self, block):
        """The tricky part here is dealing with different line delimiters.

        Technically MIME messages are \r\n delimited, but we can't assume that.
        We want to split on the first double line break we get, regardless of
        the control characters used.

        """
        block = self._tmp + block
        found_splitter = False
        for double_line_break in ('\n\r\n', '\n\n'):
            if double_line_break in block:
                found_splitter = True
                self.raw_headers, self._tmp = block.split(double_line_break, 1)
                self.raw_headers = self.raw_headers.strip()
                break
        if not found_splitter:
            self._tmp = block
        self.headers = message_from_string(self.raw_headers)
        if self.raw_headers:
            self.get_body('')

    # Body
    # ====

    def set_receiver(self):
        """Once the headers have been read, decide how to read the body.

        We use the standard library's email package to parse the headers. The
        receiver is stored on self so it persists across calls to our own
        self.received, since it actually holds data between such calls. Later on
        we will add the request body to self.message.

        """

        content_length = int(self.headers.get('Content-Length', 0))
        transfer_encoding = self.headers.get('Transfer-Encoding', '').lower()

        if not content_length and not transfer_encoding:
            self._receiver = None

        elif content_length and not transfer_encoding:
            buffer_ = OverflowableBuffer(self.adj.inbuf_overflow)
            self._receiver = FixedStreamReceiver(content_length, buffer_)

        elif transfer_encoding == 'chunked':
            buffer_ = OverflowableBuffer(self.adj.inbuf_overflow)
            self._receiver = ChunkedReceiver(buffer_)

        else:
            self._receiver = None

    def get_body(self, block):
        """Given a block from an HTTP stream, build a message body.

        We guarantee that the entire block is part of an HTTP request message.

        """

        # Decide which receiver to use.
        # =============================
        # If we don't have a receiver, it means there is no body.

        if self._receiver is None:
            self.set_receiver()
        if self._receiver is None:
            self.raw_body = ''
            raise Complete

        # Get all of the message body in one block.
        # =========================================

        if self._tmp:
            block = self._tmp + block
            self._tmp = ''
        if not self._receiver.completed:
            self._receiver.received(block)
            if not self._receiver.completed:
                return  # I'll be back!

        # Store on self and get out of here.
        # ==================================
        # We toss the body away if the requested method doesn't allow for it. I
        # read RFC 2616, Section 4.3 to call for this behavior rather than,
        # e.g., returning a 400. I suppose the concern is that the input stream
        # be properly positioned for multi-request connections.
        #
        # If anyone ever wants httpy to support extension-methods that take a
        # body we will either have to add them here or make allowances in
        # configuration.

        if self.method in ('POST', 'POST'):
            self.raw_body = self._receiver.getfile().read()
        else:
            self.raw_body = ''

        raise Complete
Пример #27
0
        class Bar:
            implements(IBar)

            foo = 1
Пример #28
0
class HTTPRequestParser(object):
    """A structure that collects the HTTP request.

    Once the stream is completed, the instance is passed to
    a server task constructor.
    """

    implements(IStreamConsumer)

    completed = 0  # Set once request is completed.
    empty = 0  # Set if no request was made.
    header_plus = ''
    chunked = 0
    content_length = 0
    body_rcv = None

    # Other attributes: first_line, header, headers, command, uri, version,
    # path, query, fragment

    # headers is a mapping containing keys translated to uppercase
    # with dashes turned into underscores.

    def __init__(self, adj):
        """
        adj is an Adjustments object.
        """
        self.headers = {}
        self.adj = adj

    def received(self, data):
        """
        Receives the HTTP stream for one request.
        Returns the number of bytes consumed.
        Sets the completed flag once both the header and the
        body have been received.
        """
        if self.completed:
            return 0  # Can't consume any more.
        datalen = len(data)
        br = self.body_rcv
        if br is None:
            # In header.
            s = self.header_plus + data
            index = find_double_newline(s)
            if index >= 0:
                # Header finished.
                header_plus = s[:index]
                consumed = len(data) - (len(s) - index)
                self.in_header = 0
                # Remove preceeding blank lines.
                header_plus = header_plus.lstrip()
                if not header_plus:
                    self.empty = 1
                    self.completed = 1
                else:
                    self.parse_header(header_plus)
                    if self.body_rcv is None:
                        self.completed = 1
                return consumed
            else:
                # Header not finished yet.
                self.header_plus = s
                return datalen
        else:
            # In body.
            consumed = br.received(data)
            if br.completed:
                self.completed = 1
            return consumed

    def parse_header(self, header_plus):
        """
        Parses the header_plus block of text (the headers plus the
        first line of the request).
        """
        index = header_plus.find('\n')
        if index >= 0:
            first_line = header_plus[:index].rstrip()
            header = header_plus[index + 1:]
        else:
            first_line = header_plus.rstrip()
            header = ''
        self.first_line = first_line
        self.header = header

        lines = self.get_header_lines()
        headers = self.headers
        for line in lines:
            index = line.find(':')
            if index > 0:
                key = line[:index]
                value = line[index + 1:].strip()
                key1 = key.upper().replace('-', '_')
                # If a header already exists, we append subsequent values
                # seperated by a comma. Applications already need to handle
                # the comma seperated values, as HTTP front ends might do
                # the concatenation for you (behavior specified in RFC2616).
                try:
                    headers[key1] += ', %s' % value
                except KeyError:
                    headers[key1] = value
            # else there's garbage in the headers?

        command, uri, version = self.crack_first_line()
        self.command = str(command)
        self.uri = str(uri)
        self.version = version
        self.split_uri()

        if version == '1.1':
            te = headers.get('TRANSFER_ENCODING', '')
            if te == 'chunked':
                from httpy._zope.server.http.chunking import ChunkedReceiver
                self.chunked = 1
                buf = OverflowableBuffer(self.adj.inbuf_overflow)
                self.body_rcv = ChunkedReceiver(buf)
        if not self.chunked:
            try:
                cl = int(headers.get('CONTENT_LENGTH', 0))
            except ValueError:
                cl = 0
            self.content_length = cl
            if cl > 0:
                buf = OverflowableBuffer(self.adj.inbuf_overflow)
                self.body_rcv = FixedStreamReceiver(cl, buf)

    def get_header_lines(self):
        """
        Splits the header into lines, putting multi-line headers together.
        """
        r = []
        lines = self.header.split('\n')
        for line in lines:
            if line and line[0] in ' \t':
                r[-1] = r[-1] + line[1:]
            else:
                r.append(line)
        return r

    first_line_re = re.compile(
        '([^ ]+) (?:[^ :?#]+://[^ ?#/]*)?([^ ]+)(( HTTP/([0-9.]+))$|$)')

    def crack_first_line(self):
        r = self.first_line
        m = self.first_line_re.match(r)
        if m is not None and m.end() == len(r):
            if m.group(3):
                version = m.group(5)
            else:
                version = None
            return m.group(1).upper(), m.group(2), version
        else:
            return None, None, None

    path_regex = re.compile(
        #     path    query     fragment
        r'([^?#]*)(\?[^#]*)?(#.*)?')

    def split_uri(self):
        m = self.path_regex.match(self.uri)
        if m.end() != len(self.uri):
            raise ValueError, "Broken URI"
        else:
            path, query, self.fragment = m.groups()
            if path and '%' in path:
                path = unquote(path)
            self.path = path
            if query:
                query = query[1:]
            self.query = query

    def getBodyStream(self):
        body_rcv = self.body_rcv
        if body_rcv is not None:
            return body_rcv.getfile()
        else:
            return StringIO('')
Пример #29
0
 class C(object):
     implements(I)
Пример #30
0
        class C(object):
            def f(self, b): pass

            implements(I)