Added dispatch pattern that shows how to use GreenPiles. Feedparser examples in the examples dir.
This commit is contained in:
@@ -5,7 +5,7 @@ Design Patterns
|
||||
|
||||
There are a bunch of basic patterns that Eventlet usage falls into. Here are a few examples that show their basic structure.
|
||||
|
||||
Client-side pattern
|
||||
Client Pattern
|
||||
--------------------
|
||||
|
||||
The canonical client-side example is a web crawler. This use case is given a list of urls and wants to retrieve their bodies for later processing. Here is a very simple example::
|
||||
@@ -33,8 +33,10 @@ There is a slightly more complex version of this in the :ref:`web crawler exampl
|
||||
|
||||
``for body in pool.imap(fetch, urls):`` iterates over the results of calling the fetch function in parallel. :meth:`imap <eventlet.greenpool.GreenPool.imap>` makes the function calls in parallel, and the results are returned in the order that they were executed.
|
||||
|
||||
The key aspect of the client pattern is that it involves collecting the results of each function call; the fact that each fetch is done concurrently is essentially an invisible optimization. Note also that imap is memory-bounded and won't consume gigabytes of memory if the list of urls grows to the tens of thousands (yes, we had that problem in production once!).
|
||||
|
||||
Server-side pattern
|
||||
|
||||
Server Pattern
|
||||
--------------------
|
||||
|
||||
Here's a simple server-side example, a simple echo server::
|
||||
@@ -64,3 +66,52 @@ The file :ref:`echo server example <echo_server_example>` contains a somewhat mo
|
||||
|
||||
``pool.spawn_n(handle, new_sock)`` launches a green thread to handle the new client. The accept loop doesn't care about the return value of the ``handle`` function, so it uses :meth:`spawn_n <eventlet.greenpool.GreenPool.spawn_n>`, instead of :meth:`spawn <eventlet.greenpool.GreenPool.spawn>`.
|
||||
|
||||
The difference between the server and the client patterns boils down to the fact that the server has a ``while`` loop calling ``accept()`` repeatedly, and that it hands off the client socket completely to the handle() method, rather than collecting the results.
|
||||
|
||||
Dispatch Pattern
|
||||
-------------------
|
||||
|
||||
One common use case that Linden Lab runs into all the time is a "dispatch" design pattern. This is a server that is also a client of some other services. Proxies, aggregators, job workers, and so on are all terms that apply here. This is the use case that the :class:`GreenPile <eventlet.greenpool.GreenPile>` was designed for.
|
||||
|
||||
Here's a somewhat contrived example: a server that receives POSTs from clients that contain a list of urls of RSS feeds. The server fetches all the feeds concurrently and responds with a list of their titles to the client. It's easy to imagine it doing something more complex than this, and this could be easily modified to become a Reader-style application::
|
||||
|
||||
import eventlet
|
||||
from eventlet import patcher
|
||||
feedparser = patcher.import_patched('feedparser')
|
||||
|
||||
pool = eventlet.GreenPool()
|
||||
|
||||
def fetch_title(url):
|
||||
d = feedparser.parse(url)
|
||||
return d.feed.get('title', '')
|
||||
|
||||
def app(environ, start_response):
|
||||
pile = eventlet.GreenPile(pool)
|
||||
for url in environ['wsgi.input'].readlines():
|
||||
pile.spawn(fetch_title, url)
|
||||
titles = '\n'.join(pile)
|
||||
start_response('200 OK', [('Content-type', 'text/plain')])
|
||||
return [titles]
|
||||
|
||||
The full version of this example is in the :ref:`feed_scraper_example`, which includes code to start the WSGI server on a particular port.
|
||||
|
||||
This example uses a global (gasp) :class:`GreenPool <eventlet.greenpool.GreenPool>` to control concurrency. If we didn't have a global limit on the number of outgoing requests, then a client could cause the server to open tens of thousands of concurrent connections to external servers, thereby getting feedparser's IP banned, or various other accidental-or-on-purpose bad behavior. The pool isn't a complete DoS protection, but it's the bare minimum.
|
||||
|
||||
.. highlight:: python
|
||||
:linenothreshold: 1
|
||||
|
||||
The interesting lines are in the app function::
|
||||
|
||||
pile = eventlet.GreenPile(pool)
|
||||
for url in environ['wsgi.input'].readlines():
|
||||
pile.spawn(fetch_title, url)
|
||||
titles = '\n'.join(pile)
|
||||
|
||||
.. highlight:: python
|
||||
:linenothreshold: 1000
|
||||
|
||||
Note that in line 1, the Pile is constructed using the global pool as its argument. That ties the Pile's concurrency to the global's. If there are already 1000 concurrent fetches from other clients of feedscraper, this one will block until some of those complete. Limitations are good!
|
||||
|
||||
Line 3 is just a spawn, but note that we don't store any return value from it. This is because the return value is kept in the Pile itself. This becomes evident in the next line...
|
||||
|
||||
Line 4 is where we use the fact that the Pile is an iterator. Each element in the iterator is one of the return values from the fetch_title function, which are strings. We can use a normal Python idiom (:func:`join`) to concatenate these incrementally as they happen.
|
@@ -7,6 +7,7 @@ Here are a bunch of small example programs that use Eventlet. All of these exam
|
||||
|
||||
Web Crawler
|
||||
------------
|
||||
``examples/webcrawler.py``
|
||||
|
||||
.. literalinclude:: ../examples/webcrawler.py
|
||||
|
||||
@@ -14,6 +15,7 @@ Web Crawler
|
||||
|
||||
WSGI Server
|
||||
------------
|
||||
``examples/wsgi.py``
|
||||
|
||||
.. literalinclude:: ../examples/wsgi.py
|
||||
|
||||
@@ -21,6 +23,7 @@ WSGI Server
|
||||
|
||||
Echo Server
|
||||
-----------
|
||||
``examples/echoserver.py``
|
||||
|
||||
.. literalinclude:: ../examples/echoserver.py
|
||||
|
||||
@@ -28,6 +31,7 @@ Echo Server
|
||||
|
||||
Socket Connect
|
||||
--------------
|
||||
``examples/connect.py``
|
||||
|
||||
.. literalinclude:: ../examples/connect.py
|
||||
|
||||
@@ -35,8 +39,19 @@ Socket Connect
|
||||
|
||||
Multi-User Chat Server
|
||||
-----------------------
|
||||
``examples/chat_server.py``
|
||||
|
||||
This is a little different from the echo server, in that it broadcasts the
|
||||
messages to all participants, not just the sender.
|
||||
|
||||
.. literalinclude:: ../examples/chat_server.py
|
||||
.. literalinclude:: ../examples/chat_server.py
|
||||
|
||||
.. _feed_scraper_example:
|
||||
|
||||
Feed Scraper
|
||||
-----------------------
|
||||
``examples/feedscraper.py``
|
||||
|
||||
This example requires `Feedparser <http://www.feedparser.org/>`_ to be installed or on the PYTHONPATH.
|
||||
|
||||
.. literalinclude:: ../examples/feedscraper.py
|
@@ -1,14 +1,7 @@
|
||||
Eventlet
|
||||
Eventlet Documentation
|
||||
====================================
|
||||
|
||||
Eventlet is a networking library written in Python. It achieves high scalability by using `non-blocking io <http://en.wikipedia.org/wiki/Asynchronous_I/O#Select.28.2Fpoll.29_loops>`_ while at the same time retaining high programmer usability by using `coroutines <http://en.wikipedia.org/wiki/Coroutine>`_ to make the non-blocking io operations appear blocking at the source code level.
|
||||
|
||||
Eventlet is different from other event-based frameworks out there because it doesn't require you to restructure your code to use it. You don't have to rewrite your code to use callbacks, and you don't have to replace your main() method with some sort of dispatch method. You can just sprinkle eventlet on top.
|
||||
|
||||
Simple Example
|
||||
-------------------
|
||||
|
||||
This is a simple web crawler that fetches a bunch of urls concurrently::
|
||||
Code talks! This is a simple web crawler that fetches a bunch of urls concurrently::
|
||||
|
||||
urls = ["http://www.google.com/intl/en_ALL/images/logo.gif",
|
||||
"https://wiki.secondlife.com/w/images/secondlife.jpg",
|
||||
@@ -24,7 +17,6 @@ This is a simple web crawler that fetches a bunch of urls concurrently::
|
||||
for body in pool.imap(fetch, urls):
|
||||
print "got body", len(body)
|
||||
|
||||
|
||||
Contents
|
||||
=========
|
||||
|
||||
|
@@ -20,9 +20,15 @@
|
||||
<div class="section" id="eventlet">
|
||||
<h1>Eventlet</h1>
|
||||
|
||||
<p>Eventlet is a better way to write high-performance network applications. It uses epoll or libevent as an event engine to implement <a class="reference external" href="http://en.wikipedia.org/wiki/Asynchronous_I/O#Select.28.2Fpoll.29_loops">non-blocking I/O</a>, and layers <a class="reference external" href="http://en.wikipedia.org/wiki/Coroutine">scheduled coroutines ("green threads")</a> on top. The result is highly-scalable network operations that do not require applications to be written in an "inversion of control" style.</p>
|
||||
<p>Eventlet is a concurrent networking library for Python that allows you to change how you run your code, not how you write it.</p>
|
||||
|
||||
<p>It's easy to get started using Eventlet, and easy to convert existing applications to start using it. Start off by looking at <a href="doc/examples.html">examples</a>, <a href="doc/design_patterns.html">common design patterns</a>, and the list of the <a href="doc/basic_usage.html">basic API primitives</a>.</p>
|
||||
<ul>
|
||||
<li>It uses epoll or libevent for <a class="reference external" href="http://en.wikipedia.org/wiki/Asynchronous_I/O#Select.28.2Fpoll.29_loops">highly scalable non-blocking I/O</a>.</li>
|
||||
<li><a class="reference external" href="http://en.wikipedia.org/wiki/Coroutine">Coroutines</a> ensure that the developer uses a blocking style of programming that is similar to threading, but provide the benefits of non-blocking I/O.</li>
|
||||
<li>The event dispatch is implicit, which means you can easily use Eventlet from the Python interpreter, or as a small part of a larger application.</li>
|
||||
</ul>
|
||||
|
||||
<p>It's easy to get started using Eventlet, and easy to convert existing applications to use it. Start off by looking at <a href="doc/examples.html">examples</a>, <a href="doc/design_patterns.html">common design patterns</a>, and the list of the <a href="doc/basic_usage.html">basic API primitives</a>.</p>
|
||||
|
||||
<h3><a href="doc/">API Documentation</a></h3>
|
||||
|
||||
|
25
examples/feedscraper-testclient.py
Normal file
25
examples/feedscraper-testclient.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from eventlet.green import urllib2
|
||||
|
||||
big_list_of_feeds = """
|
||||
http://blog.eventlet.net/feed/
|
||||
http://rss.slashdot.org/Slashdot/slashdot
|
||||
http://feeds.boingboing.net/boingboing/iBag
|
||||
http://feeds.feedburner.com/RockPaperShotgun
|
||||
http://feeds.penny-arcade.com/pa-mainsite
|
||||
http://achewood.com/rss.php
|
||||
http://raysmuckles.blogspot.com/atom.xml
|
||||
http://rbeef.blogspot.com/atom.xml
|
||||
http://journeyintoreason.blogspot.com/atom.xml
|
||||
http://orezscu.blogspot.com/atom.xml
|
||||
http://feeds2.feedburner.com/AskMetafilter
|
||||
http://feeds2.feedburner.com/Metafilter
|
||||
http://stackoverflow.com/feeds
|
||||
http://feeds.feedburner.com/codinghorror
|
||||
http://www.tbray.org/ongoing/ongoing.atom
|
||||
http://www.zeldman.com/feed/
|
||||
http://ln.hixie.ch/rss/html
|
||||
"""
|
||||
|
||||
url = 'http://localhost:9010/'
|
||||
result = urllib2.urlopen(url, big_list_of_feeds)
|
||||
print result.read()
|
41
examples/feedscraper.py
Normal file
41
examples/feedscraper.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""A simple web server that accepts POSTS containing a list of feed urls,
|
||||
and returns the titles of those feeds.
|
||||
"""
|
||||
import eventlet
|
||||
from eventlet import patcher
|
||||
feedparser = patcher.import_patched('feedparser')
|
||||
|
||||
# the pool provides a safety limit on our concurrency
|
||||
pool = eventlet.GreenPool()
|
||||
|
||||
def fetch_title(url):
|
||||
d = feedparser.parse(url)
|
||||
return d.feed.get('title', '')
|
||||
|
||||
def app(environ, start_response):
|
||||
if environ['REQUEST_METHOD'] != 'POST':
|
||||
start_response('403 Forbidden', [])
|
||||
return []
|
||||
|
||||
# the pile collects the result of a concurrent operation -- in this case,
|
||||
# the collection of feed titles
|
||||
pile = eventlet.GreenPile(pool)
|
||||
for line in environ['wsgi.input'].readlines():
|
||||
url = line.strip()
|
||||
if url:
|
||||
pile.spawn(fetch_title, url)
|
||||
# since the pile is an iterator over the results,
|
||||
# you can use it in all sorts of great Pythonic ways
|
||||
titles = '\n'.join(pile)
|
||||
start_response('200 OK', [('Content-type', 'text/plain')])
|
||||
return [titles]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from eventlet import wsgi
|
||||
from eventlet.green import socket
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, 1)
|
||||
sock.bind(('localhost', 9010))
|
||||
sock.listen(50)
|
||||
wsgi.server(sock, app)
|
Reference in New Issue
Block a user