Advertisement
Guest User

Python Epoll Non-Blocking Server Example

a guest
Dec 6th, 2011
1,549
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.05 KB | None | 0 0
  1. import sys
  2. import socket, select
  3. import fcntl
  4. import email.parser
  5. import StringIO
  6. import datetime
  7.  
  8.  
  9. """
  10. See:
  11. http://docs.python.org/library/socket.html
  12. """
  13.  
  14. __author__ = ['Caleb Burns', 'Ben DeMott']
  15.  
  16. def main(argv=None):
  17.     EOL1 = '\n\n'
  18.     EOL2 = '\n\r\n'
  19.     response  = 'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'
  20.     response += 'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'
  21.     response += 'Hello, world!'
  22.     serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  23.     # Tell the server socket file descriptor to destroy itself when this program ends.
  24.     socketFlags = fcntl.fcntl(serversocket.fileno(), fcntl.F_GETFD)
  25.     socketFlags |= fcntl.FD_CLOEXEC
  26.     fcntl.fcntl(serversocket.fileno(), fcntl.F_SETFD, socketFlags)
  27.  
  28.     serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  29.     serversocket.bind(('0.0.0.0', 8888))
  30.     serversocket.listen(1)
  31.     # Use asynchronous sockets.
  32.     serversocket.setblocking(0)
  33.     # Allow a queue of up to 128 requests (connections).
  34.     serversocket.listen(128)
  35.     # Listen to socket events on the server socket defined by the above bind() call.
  36.     epoll = select.epoll()
  37.     epoll.register(serversocket.fileno(), select.EPOLLIN)
  38.     print "Epoll Server Started..."
  39.  
  40.     try:
  41.         #The connection dictionary maps file descriptors (integers) to their corresponding network connection objects.
  42.         connections = {}
  43.         requests = {}
  44.         responses = {}
  45.         while True:
  46.             # Ask epoll if any sockets have events and wait up to 1 second if no events are present.
  47.             events = epoll.poll(1)
  48.             # fileno is a file desctiptor.
  49.             # event is the event code (type).
  50.             for fileno, event in events:
  51.                 # Check for a read event on the socket because a new connection may be present.
  52.                 if fileno == serversocket.fileno():
  53.                     # connection is a new socket object.
  54.                     # address is client IP address. The format of address depends on the address family of the socket (i.e., AF_INET).
  55.                     connection, address = serversocket.accept()
  56.                     # Set new socket-connection to non-blocking mode.
  57.                     connection.setblocking(0)
  58.                     # Listen for read events on the new socket-connection.
  59.                     epoll.register(connection.fileno(), select.EPOLLIN)
  60.                     connections[connection.fileno()] = connection
  61.                     requests[connection.fileno()] = b''
  62.                     responses[connection.fileno()] = response
  63.                 # If a read event occured, then read the new data sent from the client.
  64.                 elif event & select.EPOLLIN:
  65.                     requests[fileno] += connections[fileno].recv(1024)
  66.                     # Once we're done reading, stop listening for read events and start listening for EPOLLOUT events (this will tell us when we can start sending data back to the client).
  67.                     if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
  68.                         epoll.modify(fileno, select.EPOLLOUT)
  69.                         # Print request data to the console.
  70.                         epoll.modify(fileno, select.EPOLLOUT)
  71.  
  72.                         data = requests[fileno]
  73.                         eol = data.find("\r\n") #this is the end of the FIRST line
  74.                         start_line = data[:eol] #get the contents of the first line (which is the protocol information)
  75.                         # method is POST|GET, etc
  76.                         method, uri, http_version = start_line.split(" ")
  77.                         # re-used facebooks httputil library (works well to normalize and parse headers)
  78.                         headers = HTTPHeaders.parse(data[eol:])
  79.                         print "\nCLIENT: FD:%s  %s: '%s'  %s" % (fileno, method, uri, datetime.datetime.now())
  80.  
  81.  
  82.                 # If the client is ready to receive data, sent it out response.
  83.                 elif event & select.EPOLLOUT:
  84.                     # Send response a single bit at a time until the complete response is sent.
  85.                     # NOTE: This is where we are going to use sendfile().
  86.                     byteswritten = connections[fileno].send(responses[fileno])
  87.                     responses[fileno] = responses[fileno][byteswritten:]
  88.                     if len(responses[fileno]) == 0:
  89.                         # Tell the socket we are no longer interested in read/write events.
  90.                         epoll.modify(fileno, 0)
  91.                         # Tell the client we are done sending data and it can close the connection. (good form)
  92.                         connections[fileno].shutdown(socket.SHUT_RDWR)
  93.                 # EPOLLHUP (hang-up) events mean the client has disconnected so clean-up/close the socket.
  94.                 elif event & select.EPOLLHUP:
  95.                     epoll.unregister(fileno)
  96.                     connections[fileno].close()
  97.                     del connections[fileno]
  98.     finally:
  99.         # Close remaining open socket upon program completion.
  100.         epoll.unregister(serversocket.fileno())
  101.         epoll.close()
  102.         serversocket.close()
  103.    
  104.    
  105. #!/usr/bin/env python
  106. #
  107. # Copyright 2009 Facebook
  108. #
  109. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  110. # not use this file except in compliance with the License. You may obtain
  111. # a copy of the License at
  112. #
  113. #     http://www.apache.org/licenses/LICENSE-2.0
  114. #
  115. # Unless required by applicable law or agreed to in writing, software
  116. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  117. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  118. # License for the specific language governing permissions and limitations
  119. # under the License.
  120.  
  121. """HTTP utility code shared by clients and servers."""
  122.  
  123. class HTTPHeaders(dict):
  124.     """A dictionary that maintains Http-Header-Case for all keys.
  125.  
  126.    Supports multiple values per key via a pair of new methods,
  127.    add() and get_list().  The regular dictionary interface returns a single
  128.    value per key, with multiple values joined by a comma.
  129.  
  130.    >>> h = HTTPHeaders({"content-type": "text/html"})
  131.    >>> h.keys()
  132.    ['Content-Type']
  133.    >>> h["Content-Type"]
  134.    'text/html'
  135.  
  136.    >>> h.add("Set-Cookie", "A=B")
  137.    >>> h.add("Set-Cookie", "C=D")
  138.    >>> h["set-cookie"]
  139.    'A=B,C=D'
  140.    >>> h.get_list("set-cookie")
  141.    ['A=B', 'C=D']
  142.  
  143.    >>> for (k,v) in sorted(h.get_all()):
  144.    ...    print '%s: %s' % (k,v)
  145.    ...
  146.    Content-Type: text/html
  147.    Set-Cookie: A=B
  148.    Set-Cookie: C=D
  149.    """
  150.     def __init__(self, *args, **kwargs):
  151.         # Don't pass args or kwargs to dict.__init__, as it will bypass
  152.         # our __setitem__
  153.         dict.__init__(self)
  154.         self._as_list = {}
  155.         self.update(*args, **kwargs)
  156.  
  157.     # new public methods
  158.  
  159.     def add(self, name, value):
  160.         """Adds a new value for the given key."""
  161.         norm_name = HTTPHeaders._normalize_name(name)
  162.         if norm_name in self:
  163.             # bypass our override of __setitem__ since it modifies _as_list
  164.             dict.__setitem__(self, norm_name, self[norm_name] + ',' + value)
  165.             self._as_list[norm_name].append(value)
  166.         else:
  167.             self[norm_name] = value
  168.  
  169.     def get_list(self, name):
  170.         """Returns all values for the given header as a list."""
  171.         norm_name = HTTPHeaders._normalize_name(name)
  172.         return self._as_list.get(norm_name, [])
  173.  
  174.     def get_all(self):
  175.         """Returns an iterable of all (name, value) pairs.
  176.  
  177.        If a header has multiple values, multiple pairs will be
  178.        returned with the same name.
  179.        """
  180.         for name, list in self._as_list.iteritems():
  181.             for value in list:
  182.                 yield (name, value)
  183.  
  184.  
  185.     def items(self):
  186.         return [{key: value[0]} for key, value in self._as_list.iteritems()]
  187.  
  188.     def get_content_type(self):
  189.         return dict.get(self, HTTPHeaders._normalize_name('content-type'), None)
  190.  
  191.     def parse_line(self, line):
  192.         """Updates the dictionary with a single header line.
  193.  
  194.        >>> h = HTTPHeaders()
  195.        >>> h.parse_line("Content-Type: text/html")
  196.        >>> h.get('content-type')
  197.        'text/html'
  198.        """
  199.         name, value = line.split(":", 1)
  200.         self.add(name, value.strip())
  201.  
  202.     @classmethod
  203.     def parse(cls, headers):
  204.         """Returns a dictionary from HTTP header text.
  205.  
  206.        >>> h = HTTPHeaders.parse("Content-Type: text/html\\r\\nContent-Length: 42\\r\\n")
  207.        >>> sorted(h.iteritems())
  208.        [('Content-Length', '42'), ('Content-Type', 'text/html')]
  209.        """
  210.         h = cls()
  211.         for line in headers.splitlines():
  212.             if line:
  213.                 h.parse_line(line)
  214.         return h
  215.  
  216.     # dict implementation overrides
  217.  
  218.     def __setitem__(self, name, value):
  219.         norm_name = HTTPHeaders._normalize_name(name)
  220.         dict.__setitem__(self, norm_name, value)
  221.         self._as_list[norm_name] = [value]
  222.  
  223.     def __getitem__(self, name):
  224.         return dict.__getitem__(self, HTTPHeaders._normalize_name(name))
  225.  
  226.     def __delitem__(self, name):
  227.         norm_name = HTTPHeaders._normalize_name(name)
  228.         dict.__delitem__(self, norm_name)
  229.         del self._as_list[norm_name]
  230.  
  231.     def get(self, name, default=None):
  232.         return dict.get(self, HTTPHeaders._normalize_name(name), default)
  233.  
  234.     def update(self, *args, **kwargs):
  235.         # dict.update bypasses our __setitem__
  236.         for k, v in dict(*args, **kwargs).iteritems():
  237.             self[k] = v
  238.  
  239.     @staticmethod
  240.     def _normalize_name(name):
  241.         """Converts a name to Http-Header-Case.
  242.  
  243.        >>> HTTPHeaders._normalize_name("coNtent-TYPE")
  244.        'Content-Type'
  245.        """
  246.         return "-".join([w.capitalize() for w in name.split("-")])
  247.  
  248.  
  249. if(__name__ == '__main__'):
  250.     sys.exit(main(sys.argv))
  251.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement