📄 dibbler.py
字号:
(or a `socketMap` argument for pure asyncore listeners). The incoming socket will be prepended to this list, and passed as the first argument. See `HTTPServer` for an example. o socketMap: Optional. The asyncore socket map to use. If you're using a `Dibbler.Context`, pass context._map. See `HTTPServer` for an example `Listener` - it's a good deal smaller than this description!""" asyncore.dispatcher.__init__(self, map=socketMap) self.socketMap = socketMap self.factory = factory self.factoryArgs = factoryArgs s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setblocking(False) self.set_socket(s, self.socketMap) self.set_reuse_addr() if type(port) != type(()): port = ('', port) self.bind(port) self.listen(5) def handle_accept(self): """Asyncore override.""" # If an incoming connection is instantly reset, eg. by following a # link in the web interface then instantly following another one or # hitting stop, handle_accept() will be triggered but accept() will # return None. result = self.accept() if result: clientSocket, clientAddress = result args = [clientSocket] + list(self.factoryArgs) self.factory(*args)class HTTPServer(Listener): """A web server with which you can register `HTTPPlugin`s to serve up your content - see `HTTPPlugin` for detailed documentation and examples. `port` specifies the TCP/IP (address, port) on which to run, defaulting to ('', 80). `context` optionally specifies a `Dibbler.Context` for the server. """ NO_AUTHENTICATION = "None" BASIC_AUTHENTICATION = "Basic" DIGEST_AUTHENTICATION = "Digest" def __init__(self, port=('', 80), context=_defaultContext): """Create an `HTTPServer` for the given port.""" Listener.__init__(self, port, _HTTPHandler, (self, context), context._map) self._plugins = [] try: context._HTTPPort = port[1] except TypeError: context._HTTPPort = port def register(self, *plugins): """Registers one or more `HTTPPlugin`-derived objects with the server.""" for plugin in plugins: self._plugins.append(plugin) def requestAuthenticationMode(self): """Override: HTTP Authentication. It should return a value among NO_AUTHENTICATION, BASIC_AUTHENTICATION and DIGEST_AUTHENTICATION. The two last values will force HTTP authentication respectively through Base64 and MD5 encodings.""" return self.NO_AUTHENTICATION def isValidUser(self, name, password): """Override: Return True for authorized logins.""" return True def getPasswordForUser(self, name): """Override: Return the password associated to the specified user name.""" return '' def getRealm(self): """Override: Specify the HTTP authentication realm.""" return "Dibbler application server" def getCancelMessage(self): """Override: Specify the cancel message for an HTTP Authentication.""" return "You must log in."class _HTTPHandler(BrighterAsyncChat): """This is a helper for the HTTP server class - one of these is created for each incoming request, and does the job of decoding the HTTP traffic and driving the plugins.""" # RE to extract option="value" fields from # digest auth login field _login_splitter = re.compile('([a-zA-Z]+)=(".*?"|.*?),?') def __init__(self, clientSocket, server, context): # Grumble: asynchat.__init__ doesn't take a 'map' argument, # hence the two-stage construction. BrighterAsyncChat.__init__(self, map=context._map) BrighterAsyncChat.set_socket(self, clientSocket, context._map) self._context = context self._server = server self._request = '' self.set_terminator('\r\n\r\n') # Because a methlet is likely to call `writeOKHeaders` before doing # anything else, an unexpected exception won't send back a 500, which # is poor. So we buffer any sent headers until either a plain `write` # happens or the methlet returns. self._bufferedHeaders = [] self._headersWritten = False # Tell the plugins about the connection, letting them veto it. for plugin in self._server._plugins: if not plugin.onIncomingConnection(clientSocket): self.close() def collect_incoming_data(self, data): """Asynchat override.""" self._request = self._request + data def found_terminator(self): """Asynchat override.""" # Parse the HTTP request. requestLine, headers = (self._request+'\r\n').split('\r\n', 1) try: method, url, version = requestLine.strip().split() except ValueError: self.writeError(400, "Malformed request: '%s'" % requestLine) self.close_when_done() return # Parse the URL, and deal with POST vs. GET requests. method = method.upper() unused, unused, path, unused, query, unused = urlparse.urlparse(url) cgiParams = cgi.parse_qs(query, keep_blank_values=True) if self.get_terminator() == '\r\n\r\n' and method == 'POST': # We need to read the body - set a numeric async_chat terminator # equal to the Content-Length. match = re.search(r'(?i)content-length:\s*(\d+)', headers) contentLength = int(match.group(1)) if contentLength > 0: self.set_terminator(contentLength) self._request = self._request + '\r\n\r\n' return # Have we just read the body of a POSTed request? Decode the body, # which will contain parameters and possibly uploaded files. if type(self.get_terminator()) is type(1): self.set_terminator('\r\n\r\n') body = self._request.split('\r\n\r\n', 1)[1] match = re.search(r'(?i)content-type:\s*([^\r\n]+)', headers) contentTypeHeader = match.group(1) contentType, pdict = cgi.parse_header(contentTypeHeader) if contentType == 'multipart/form-data': # multipart/form-data - probably a file upload. bodyFile = StringIO.StringIO(body) cgiParams.update(cgi.parse_multipart(bodyFile, pdict)) else: # A normal x-www-form-urlencoded. cgiParams.update(cgi.parse_qs(body, keep_blank_values=True)) # Convert the cgi params into a simple dictionary. params = {} for name, value in cgiParams.iteritems(): params[name] = value[0] # Parse the headers. headersRegex = re.compile('([^:]*):\s*(.*)') headersDict = dict([headersRegex.match(line).groups(2) for line in headers.split('\r\n') if headersRegex.match(line)]) # HTTP Basic/Digest Authentication support. serverAuthMode = self._server.requestAuthenticationMode() if serverAuthMode != HTTPServer.NO_AUTHENTICATION: # The server wants us to authenticate the user. authResult = False authHeader = headersDict.get('Authorization') if authHeader: authMatch = re.search('(\w+)\s+(.*)', authHeader) authenticationMode, login = authMatch.groups() if authenticationMode == HTTPServer.BASIC_AUTHENTICATION: authResult = self._basicAuthentication(login) elif authenticationMode == HTTPServer.DIGEST_AUTHENTICATION: authResult = self._digestAuthentication(login, method) else: print >>sys.stdout, "Unknown mode: %s" % authenticationMode if not authResult: self.writeUnauthorizedAccess(serverAuthMode) # Find and call the methlet. '/eggs.gif' becomes 'onEggsGif'. if path == '/': path = '/Home' pieces = path[1:].split('.') name = 'on' + ''.join([piece.capitalize() for piece in pieces]) for plugin in self._server._plugins: if hasattr(plugin, name): # The plugin's APIs (`write`, etc) reflect back to us via # `plugin._handler`. plugin._handler = self try: # Call the methlet. getattr(plugin, name)(**params) if self._bufferedHeaders: # The methlet returned without writing anything other # than headers. This isn't unreasonable - it might # have written a 302 or something. Flush the buffered # headers self.write(None) except: # The methlet raised an exception - send the traceback to # the browser, unless it's SystemExit in which case we let # it go. eType, eValue, eTrace = sys.exc_info() if eType == SystemExit: # Close all the listeners so that no further incoming # connections appear. contextMap = self._context._map for dispatcher in contextMap.values(): if isinstance(dispatcher, Listener): dispatcher.close() # Let any existing connections close down first. This # has happened when all we have left are _HTTPHandlers # (this one plus any others that are using keep-alive; # none of the others can be actually doing any work # because *we're* the one doing the work). def isProtected(dispatcher): return not isinstance(dispatcher, _HTTPHandler) while len(filter(isProtected, contextMap.values())) > 0: asyncore.poll(timeout=1, map=contextMap) raise SystemExit message = """<h3>500 Server error</h3><pre>%s</pre>""" details = traceback.format_exception(eType, eValue, eTrace) details = '\n'.join(details) self.writeError(500, message % cgi.escape(details)) plugin._handler = None break else: self.onUnknown(path, params) # `close_when_done` and `Connection: close` ensure that we don't # support keep-alives or pipelining. There are problems with some # browsers, for instance with extra characters being appended after # the body of a POSTed request. self.close_when_done() def onUnknown(self, path, params): """Handler for unknown URLs. Returns a 404 page.""" self.writeError(404, "Not found: '%s'" % path) def writeOKHeaders(self, contentType, extraHeaders={}): """Reflected from `HTTPPlugin`s.""" # Buffer the headers until there's a `write`, in case an error occurs.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -