Anda di halaman 1dari 200

Plan

5min - Introductions 7min - Network 7min - reactor intro 15min - uppercase 10min - proxy1 10min - deferred 10min - proxy2 15m - BREAK

Saturday, 18 June 2011

Twisted
an introductory training

Saturday, 18 June 2011

About me
http://orestis.gr - @orestis

Saturday, 18 June 2011

About you
Raise your hand if...

Saturday, 18 June 2011

Some setup
http://twisted-talk-url TODO

Saturday, 18 June 2011

Test
>>> from twisted.internet import reactor >>> from twisted import version >>> version Version('twisted', 11, 0, 0) >>>

Saturday, 18 June 2011

Network programming

Server listens on a port

Saturday, 18 June 2011

Network programming
Client connects to port Server listens on a port

Saturday, 18 June 2011

Service request
Client reads/ writes data Server reads/ writes data

Saturday, 18 June 2011

While servicing...
Client reads/ writes data Server reads/ writes data

Saturday, 18 June 2011

While servicing...
Client reads/ writes data Server reads/ writes data Client connects to port

Saturday, 18 June 2011

Timeout!
Client reads/ writes data Server reads/ writes data Client connects to port

Saturday, 18 June 2011

Timeout!
Client reads/ writes data Server reads/ writes data

Reje cted

Client connects to port

Saturday, 18 June 2011

Must not block!

Server listens on a port

Saturday, 18 June 2011

Must not block!


Client connects to port Server listens on a port

Saturday, 18 June 2011

Handle request elsewhere


Client connects to port Server listens on a port

Saturday, 18 June 2011

Handle request elsewhere


Client reads/ writes data Server listens on a port

Saturday, 18 June 2011

Handle request elsewhere


Client reads/ writes data Server listens on a port

Saturday, 18 June 2011

Handle request elsewhere

Fork a new process Have worker threads

Saturday, 18 June 2011

Saturday, 18 June 2011

Saturday, 18 June 2011

Doesnt scale!
Saturday, 18 June 2011

Twisted?
Twisted is a networking engine written in Python, supporting numerous protocols. It contains a web server, numerous chat clients, chat servers, mail servers, and more.

Saturday, 18 June 2011

Twisted!

Text

Saturday, 18 June 2011

Twisted!

Saturday, 18 June 2011

Twisted
Twisted is a networking engine written in Python, supporting numerous protocols. It contains a web server, numerous chat clients, chat servers, mail servers, and more.

Saturday, 18 June 2011

twisted.internet
Asynchronous I/O and Events.

Saturday, 18 June 2011

twisted.internet !!!

Saturday, 18 June 2011

twisted.internet !!!
defer endpoints error protocol reactor task

Saturday, 18 June 2011

twisted.internet reactor
the loop which drives applications using Twisted

Saturday, 18 June 2011

Saturday, 18 June 2011

Most of the time we are waiting

Saturday, 18 June 2011

Saturday, 18 June 2011

What if we could...

Saturday, 18 June 2011

Eliminate Blocking?

Saturday, 18 June 2011

Eliminate Blocking?

Reactor loop

Callback functions

Saturday, 18 June 2011

Reactor loop
Event happens

Callback is called

Saturday, 18 June 2011

Twisted reactor loop


from twisted.internet import reactor

reactor.run()

Run this!
Saturday, 18 June 2011

Listen on a port, then do something


from twisted.internet import reactor

reactor.listenTCP(8000, ?????????) reactor.run()

Saturday, 18 June 2011

Listen at a port, then do something


from twisted.internet import reactor, protocol factory = protocol.ServerFactory() factory.protocol = protocol.Protocol reactor.listenTCP(8000, factory) reactor.run()

Run this!
Saturday, 18 June 2011

Handle request elsewhere

factory listens on a port for each connection, a protocol instance is created


Saturday, 18 June 2011

Handle request elsewhere

factory listens on a port for each connection, a protocol instance is created


Saturday, 18 June 2011

Twisted Uppercase Server


Server that returns the data, uppercased

Saturday, 18 June 2011

Twisted Uppercase Server


from twisted.internet import reactor, protocol class UpperProtocol(protocol.Protocol): def connectionMade(self): self.transport.write('Hi! Send me text to convert to uppercase\n') def connectionLost(self, reason): pass def dataReceived(self, data): self.transport.write(data.upper()) self.transport.loseConnection() factory = protocol.ServerFactory() factory.protocol = UpperProtocol reactor.listenTCP(8000, factory) reactor.run()

upperserver.py
Saturday, 18 June 2011

Run this!

Twisted Uppercase Server


from twisted.internet import reactor, protocol class UpperProtocol(protocol.Protocol): def connectionMade(self): self.transport.write('Hi! Send me text to convert to uppercase\n') def connectionLost(self, reason): pass def dataReceived(self, data): self.transport.write(data.upper()) self.transport.loseConnection() factory = protocol.ServerFactory() factory.protocol = UpperProtocol reactor.listenTCP(8000, factory) reactor.run()

Run this!
Saturday, 18 June 2011

Twisted Uppercase Server


$ telnet localhost 8000 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Hi! Send me text to convert to uppercase twisted is cool TWISTED IS COOL Connection closed by foreign host. $

Saturday, 18 June 2011

A better client
import socket

multiclient.py

def make_connection(host, port, data_to_send): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) s.send(data_to_send) s.send('\r\n') b = [] while True: data = s.recv(1024) if data: b.append(data) else: break return ''.join(b) if __name__ == '__main__': import sys host, port = sys.argv[1].split(':') data_to_send = sys.argv[2:] for d in data_to_send: print make_connection(host, int(port), d)

Run this!

Saturday, 18 June 2011

A better client (output)


$ python multiclient.py 127.0.0.1:8000 a b c d sending a Hi! Send me text to convert to uppercase A sending b Hi! Send me text to convert to uppercase B sending c Hi! Send me text to convert to uppercase C sending d Hi! Send me text to convert to uppercase D

Saturday, 18 June 2011

Questions so far?
Exercise coming up!

Saturday, 18 June 2011

Exercise 1

Count connected clients Announce number of connected clients when connecting HINT: Protocols have a factory instance attribute

Saturday, 18 June 2011

Counting uppercase server


from twisted.internet import reactor, protocol class UpperProtocol(protocol.Protocol): def connectionMade(self): self.factory.count += 1 self.transport.write('Hi! There are %d clients\n' % self.factory.count) def connectionLost(self, reason): self.factory.count -= 1 def dataReceived(self, data): self.transport.write(data.upper()) self.transport.loseConnection() class CountingFactory(protocol.ServerFactory): protocol = UpperProtocol count = 0 reactor.listenTCP(8000, CountingFactory()) reactor.run()

upperserver_ex.py
Saturday, 18 June 2011

Run this!

An even better client


import threading from multiclient import make_connection def t_connection(host, port, d): print 'sending', d print make_connection(host, port, d)

if __name__ == '__main__': import sys host, port = sys.argv[1].split(':') data_to_send = sys.argv[2:] threads = [] for d in data_to_send: t = threading.Thread(target=t_connection, args=(host, int(port), d)) t.start() threads.append(t) for t in threads: t.join() print 'finished'

threadedclient.py
Saturday, 18 June 2011

Run this!

A brief recap
reactor runs forever a factory instance is tied to a specific port protocol instances are created for each client implement specific methods to add functionality

Saturday, 18 June 2011

Twisted Proxy Server (v1)

Client sends an URL followed by a newline The server returns the contents of that URL Connection is closed

Saturday, 18 June 2011

followed by a newline
from twisted.internet import protocol class MyProtocol(protocol.Protocol): def connectionMade(self): self.buffer = [] def dataReceived(self, data): self.buffer.append(data) if '\n' in data: line, rest = ''.join(self.buffer).split('\n') self.buffer = [rest] print line

from twisted.protocols import basic class MyProtocol(basic.LineReceiver): def lineReceived(self, line): print line

Saturday, 18 June 2011

twisted.protocols
amp basic dict nger ftp gps htb ident loopback memcache mice pcp policies portforward postx shoutcast sip socks stateful telnet tls wire

Saturday, 18 June 2011

twisted.protocols.basic
NetstringReceiver LineOnlyReceiver LineReceiver IntNStringReceiver Int32StringReceiver Int16StringReceiver Int8StringReceiver StatefulStringProtocol FileSender

Saturday, 18 June 2011

twisted.protocols
Dont reinvent the wheel!

Saturday, 18 June 2011

Twisted Proxy Server (v1)


from twisted.internet import reactor, protocol from twisted.protocols import basic import urllib2 import time class ProxyProtocol(basic.LineReceiver): def lineReceived(self, line): if not line.startswith('http://'): return start = time.time() print 'fetching', line data = urllib2.urlopen(line).read() print 'fetched', line self.transport.write(data) self.transport.loseConnection() print 'took', time.time() - start factory = protocol.ServerFactory() factory.protocol = ProxyProtocol reactor.listenTCP(8000, factory) reactor.run()

proxy1.py
Saturday, 18 June 2011

Run this!

Lets time it!


import threading from multiclient import make_connection import time def t_connection(host, port, d): start = time.time() make_connection(host, port, d) print d, 'took', time.time() - start if __name__ == '__main__': import sys host, port = sys.argv[1].split(':') data_to_send = sys.argv[2:] threads = [] overallstart = time.time() for d in data_to_send: t = threading.Thread(target=t_connection, args=(host, int(port), d)) t.start() threads.append(t) for t in threads: t.join() print 'finished in', time.time() - overallstart

timingclient.py
Saturday, 18 June 2011

Run this!

Lets time it! (output)


$ python timingclient.py 127.0.0.1:8000 [...] http://orestis.gr took 2.02559900284 http://amazon.com took 2.02640080452 http://apple.com took 3.81526899338 http://google.com took 3.81563591957 finished in 3.81683182716

Client

$ python proxy1.py fetched http://orestis.gr took 0.566583156586

Server

fetched http://amazon.com took 1.45738196373 fetched http://apple.com took 1.01898193359 fetched http://google.com took 0.770982027054

Saturday, 18 June 2011

Somethings wrong!
Individual requests 0.770982027054 0.566583156586 1.45738196373 1.01898193359 3.81392908096 Threaded client 3.81683182716
Saturday, 18 June 2011

Eliminate Blocking?

Saturday, 18 June 2011

But in this case...

Waiting!

Saturday, 18 June 2011

The culprit
print 'fetching', line data = urllib2.urlopen(line).read() print 'fetched', line

Saturday, 18 June 2011

The culprit
print 'fetching', line

data = urllib2.urlopen(line).read() print 'fetched', line

Forb idde n

You didnt think itd be that easy, right?


Saturday, 18 June 2011

The callbacks must be cooperative

Saturday, 18 June 2011

The callbacks must be cooperative


When accessing the network, return control back to the loop The loop will call your code when the network is ready

Saturday, 18 June 2011

Network programming

Server listens on a port

Saturday, 18 June 2011

Network programming
Client connects to port Server listens on a port

Saturday, 18 June 2011

Network programming
asks for a Client connects Customer teportnini ato d pa to s

Server listens stall aits at the Cook w a port on

Saturday, 18 June 2011

High-level panini stall


import stall panini = stall.order_panini(spec) eat(panini)

data = urllib2.urlopen(line).read()

Saturday, 18 June 2011

Panini stall
Wait my turn Place order Wait for panini Eat panini

Network
Make connection Send request Read data Use data

Saturday, 18 June 2011

Low-level panini stall


import stall import time stall.enter_queue() while not stall.is_my_turn(): time.sleep(0.1) stall.place_order(spec) while not stall.order_is_ready(): time.sleep(0.1) panini = stall.get_panini() eat(panini)

Saturday, 18 June 2011

Low-level panini stall


import stall import time stall.enter_queue() while not stall.is_my_turn(): time.sleep(0.1) stall.place_order(spec) while not stall.order_is_ready(): time.sleep(0.1) panini = stall.get_panini() eat(panini)

Saturday, 18 June 2011

Waste of time!

We are idling the CPU! Nothing else can run! How selfish of us!

Saturday, 18 June 2011

Solution: Callbacks!

Saturday, 18 June 2011

Callbacks, you know...


$.ajax({ type: "POST", url: "some.php", data: "name=John&location=Boston", success: function(msg){ alert( "Data Saved: " + msg ); } });

Saturday, 18 June 2011

Callbacks, you know...


$.ajax({ type: "POST", url: "some.php", data: "name=John&location=Boston", success: function(msg){ alert( "Data Saved: " + msg ); } });

Saturday, 18 June 2011

High-level panini stall


import stall panini = stall.order_panini(spec) eat(panini)

import stall stall.order_panini(spec, when_ready=eat)

Saturday, 18 June 2011

Callbacks can be messy


Add error handling? Pass the result around? Cancel the original request? Consistent API?

Saturday, 18 June 2011

Introducing Deferred
twisted.internet.defer

Saturday, 18 June 2011

A Deferred is...
A promise of a result... A result that will appear in the future... A result you can pass around... Something you can attach callbacks to.

Saturday, 18 June 2011

Deferred Panini
import stall def eat(panini): print YUM! Ive just eated a, panini deferred = stall.order_panini(spec) deferred.addCallback(eat)

Saturday, 18 June 2011

Deferreds are Everywhere


Get used to them!

Saturday, 18 June 2011

Twisted has reimplementations of most of the stdlib.


They had to do it - not a case of NIH!

Saturday, 18 June 2011

So....
import urllib2 data = urllib2.urlopen(url).read() print data

from twisted.web.client import getPage def got_page(data): print data deferred = getPage(url) deferred.addCallback(got_page)

Saturday, 18 June 2011

In context...
def lineReceived(self, line): if not line.startswith('http://'): return start = time.time() print 'fetching', line def gotData(data): print 'fetched', line self.transport.write(data) self.transport.loseConnection() print 'took', time.time() - start deferredData = getPage(line) deferredData.addCallback(gotData)

def lineReceived(self, line): if not line.startswith('http://'): return start = time.time() print 'fetching', line data = urllib2.urlopen(line).read() print 'fetched', line self.transport.write(data) self.transport.loseConnection() print 'took', time.time() - start

Saturday, 18 June 2011

Python reminder:
def lineReceived(self, line): if not line.startswith('http://'): return start = time.time() print 'fetching', line def gotData(data): print 'fetched', line self.transport.write(data) self.transport.loseConnection() print 'took', time.time() - start deferredData = getPage(line) deferredData.addCallback(gotData)

Saturday, 18 June 2011

Python reminder:
def lineReceived(self, line): if not line.startswith('http://'): return start = time.time() print 'fetching', line def gotData(data): print 'fetched', line self.transport.write(data) self.transport.loseConnection() print 'took', time.time() - start deferredData = getPage(line) deferredData.addCallback(gotData)

Saturday, 18 June 2011

A tidier way
def writeDataAndLoseConnection(data, url, transport, starttime): print 'fetched', url transport.write(data) transport.loseConnection() print 'took', time.time() - starttime class ProxyProtocol(basic.LineReceiver): def lineReceived(self, line): if not line.startswith('http://'): return start = time.time() print 'fetching', line deferredData = getPage(line) deferredData.addCallback(writeDataAndLoseConnection, line, self.transport, start)

Saturday, 18 June 2011

Twisted Proxy Server (v2)


from twisted.web.client import getPage from twisted.internet import reactor, protocol from twisted.protocols import basic import time def writeDataAndLoseConnection(data, url, transport, starttime): print 'fetched', url, transport.write(data) transport.loseConnection() print 'took', time.time() - starttime class ProxyProtocol(basic.LineReceiver): def lineReceived(self, line): if not line.startswith('http://'): return start = time.time() print 'fetching', line deferredData = getPage(line) deferredData.addCallback(writeDataAndLoseConnection, line, self.transport, start) factory = protocol.ServerFactory() factory.protocol = ProxyProtocol reactor.listenTCP(8000, factory) reactor.run()
Saturday, 18 June 2011

proxy2.py
Run this!

Lets time it! (output)


$ python timingclient.py 127.0.0.1:8000 [...] http://orestis.gr took 0.487771987915 http://apple.com took 0.853055000305 http://google.com took 1.00087499619 http://amazon.com took 1.58436584473 finished in 1.5850892067

Client

$ python proxy2.py fetching http://orestis.gr fetching http://amazon.com fetching http://google.com fetching http://apple.com fetched http://orestis.gr took 0.486361026764 fetched http://apple.com took 0.850247859955 fetched http://google.com took 0.998661994934 fetched http://amazon.com took 1.58235692978

Server

Saturday, 18 June 2011

Much better!
Individual requests 0.486361026764 0.850247859955 0.998661994934 1.58235692978 3.917627811433 Threaded client 1.5850892067
Saturday, 18 June 2011

The callbacks must be cooperative


When accessing the network, return control back to the loop The loop will call your code when the network is ready

Saturday, 18 June 2011

never call blocking functions


return control to the loop

Saturday, 18 June 2011

Exercise 2a
Implement a caching proxy server! Save response data in plain dict Lookup response data QUESTION: Where should you store the dict?

Saturday, 18 June 2011

Caching Proxy Server (v1)


from twisted.web.client import getPage from twisted.internet import reactor, protocol from twisted.protocols import basic

proxy2_ex1.py

class CachingProxyProtocol(basic.LineReceiver): def lineReceived(self, line): if not line.startswith('http://'): return try: data = self.factory.cache[line] self.transport.write(data) self.transport.loseConnection() except KeyError: def gotData(data): self.factory.cache[line] = data self.transport.write(data) self.transport.loseConnection() deferredData = getPage(line) deferredData.addCallback(gotData) class CachingProxyFactory(protocol.ServerFactory): protocol = CachingProxyProtocol cache = {} reactor.listenTCP(8000, CachingProxyFactory()) reactor.run()

Run this!

Saturday, 18 June 2011

Cool Deferred Features: Chaining


from twisted.web.client import getPage def uppercase(s): return s.upper() def write(s): print s d = getPage("http://www.google.com") d.addCallback(uppercase) d.addCallback(write)

Google Search uppercase GOOGLE SEARCH write None

Saturday, 18 June 2011

Cool Deferred Features: Deferring


from twisted.web.client import getPage def gotPage(data): newURL = getRedirect(data) if newURL: return getPage(newURL) else: return data

304: google.it gotPage Deferred


Saturday, 18 June 2011

200: google.com gotPage Google Search

Chaining and Deferring


from twisted.web.client import getPage def gotPage(data): newURL = getRedirect(data) if newURL: return getPage(newURL) else: return data def uppercase(s): return s.upper() def write(s): print s d = getPage("http://www.google.com") d.addCallback(gotPage) d.addCallback(uppercase) d.addCallback(write)

Saturday, 18 June 2011

Chaining and Deferring


Google Search gotData Google Search uppercase GOOGLE SEARCH write
Saturday, 18 June 2011

Chaining and Deferring


Google Search gotData Deferred uppercase CERCA CON GOOGLE write
Saturday, 18 June 2011

Cerca con Google

Exercise 2b
Put those cool features into use! One callback to get a page One callback to store it to cache One callback to write to transport HINT: Use defer.succeed(data) to return a ready Deferred

Saturday, 18 June 2011

Caching Proxy Server (v2)


from twisted.web.client import getPage from twisted.internet import reactor, protocol, defer from twisted.protocols import basic class CachingProxyProtocol(basic.LineReceiver): def _getPage(self, url, cache): try: data = self.factory.cache[url] return defer.succeed(data) except KeyError: d = getPage(url) d.addCallback(self._storeInCache, url, cache) return d def _storeInCache(self, data, url, cache): cache[url] = data return data def lineReceived(self, line): if not line.startswith('http://'): return def gotData(data): self.transport.write(data) self.transport.loseConnection() deferredData = self._getPage(line, self.factory.cache) deferredData.addCallback(gotData) class CachingProxyFactory(protocol.ServerFactory): protocol = CachingProxyProtocol cache = {} reactor.listenTCP(8000, CachingProxyFactory()) reactor.run()
Saturday, 18 June 2011

proxy2_ex2.py
Run this!

BREAK
Write questions on whiteboard

Saturday, 18 June 2011

Writing clients
the Twisted way

Saturday, 18 June 2011

Remember this?
import socket def make_connection(host, port, data_to_send): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) s.send(data_to_send) s.send('\r\n') b = [] while True: data = s.recv(1024) if data: b.append(data) else: break return ''.join(b) if __name__ == '__main__': import sys host, port = sys.argv[1].split(':') data_to_send = sys.argv[2:] for d in data_to_send: print make_connection(host, int(port), d)

Saturday, 18 June 2011

Remember this?
import socket def make_connection(host, port, data_to_send): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) s.send(data_to_send) s.send('\r\n') b = [] while True: data = s.recv(1024) if data: b.append(data) else: break return ''.join(b) if __name__ == '__main__': import sys host, port = sys.argv[1].split(':') data_to_send = sys.argv[2:] for d in data_to_send: print make_connection(host, int(port), d)

Saturday, 18 June 2011

Remember this?
import socket def make_connection(host, port, data_to_send): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) s.send(data_to_send) s.send('\r\n') b = [] while True: data = s.recv(1024) if data: b.append(data) else: break return ''.join(b)

Forb idde n

if __name__ == '__main__': import sys host, port = sys.argv[1].split(':') data_to_send = sys.argv[2:] for d in data_to_send: print make_connection(host, int(port), d)

Saturday, 18 June 2011

Return control to the loop


In clients, too

Saturday, 18 June 2011

Twisted reactor loop


from twisted.internet import reactor

reactor.run()

Saturday, 18 June 2011

Connect to a host, then do something


from twisted.internet import reactor, protocol

reactor.connectTCP(127.0.0.1, 8000, ?????) reactor.run()

Saturday, 18 June 2011

Connect to a host, then do something


from twisted.internet import reactor, protocol factory = protocol.ClientFactory() factory.protocol = protocol.Protocol reactor.connectTCP(127.0.0.1, 8000, factory) reactor.run()

Run this!
Saturday, 18 June 2011

Lets write a twisted client


Run the uppercase server

Saturday, 18 June 2011

Twisted Simple Client (v1)


from twisted.internet import reactor, protocol class UppercaseClientProtocol(protocol.Protocol): def connectionMade(self): self.transport.write(self.factory.text) self.transport.write('\r\n') def dataReceived(self, data): print data if __name__ == '__main__': import sys host, port = sys.argv[1].split(':') data_to_send = sys.argv[2:] for d in data_to_send: print 'sending', d factory = protocol.ClientFactory() factory.protocol = UppercaseClientProtocol factory.text = d reactor.connectTCP(host, int(port), factory) reactor.run()

simpleclient.py
Saturday, 18 June 2011

Run this!

Simple Client v1 (output)


$ python simpleclient.py 127.0.0.1:8000 a b c d sending a sending b sending c sending d Hi! Send me text to convert to uppercase D Hi! Send me text to convert to uppercase A Hi! Send me text to convert to uppercase B Hi! Send me text to convert to uppercase C ^C $

Saturday, 18 June 2011

Observations
Data returns with random order We cannot access the returned data Loop never stops Performance?

Saturday, 18 June 2011

We need to gather results


hmm, what should we use?

Saturday, 18 June 2011

Twisted Simple Client (v2)


from twisted.internet import reactor, protocol, defer class UppercaseClientProtocol(protocol.Protocol): def connectionMade(self): self.transport.write(self.factory.text) self.transport.write('\r\n') self.buffer = [] def dataReceived(self, data): self.buffer.append(data) def connectionLost(self, reason): alldata = ''.join(self.buffer) self.factory.deferred.callback(alldata) def gotData(data, request): print 'received response for', request print data if __name__ == '__main__': import sys host, port = sys.argv[1].split(':') data_to_send = sys.argv[2:] for data in data_to_send: print 'sending', data d = defer.Deferred() d.addCallback(gotData, data) factory = protocol.ClientFactory() factory.protocol = UppercaseClientProtocol factory.text = data factory.deferred = d reactor.connectTCP(host, int(port), factory)

simpleclient2.py
Saturday, 18 June 2011

reactor.run()

Run this!

Simple Client v2 (output)


$ python simpleclient2.py 127.0.0.1:8000 a b c d sending a sending b sending c sending d received response for b Hi! Send me text to convert to uppercase B received response for a Hi! Send me text to convert to uppercase A received response for c Hi! Send me text to convert to uppercase C received response for d Hi! Send me text to convert to uppercase D

Saturday, 18 June 2011

Observations
Data returns with random order We cannot access the returned data Loop never stops Performance?

Saturday, 18 June 2011

Stop the loop when everything is finished


wait until all Deferreds have fired

Saturday, 18 June 2011

Introducing DeferredList

A list of Deferreds! You create it with a list of Deferreds When all the Deferreds have finished, its callback fires.

Saturday, 18 June 2011

Introducing DefferedList
from twisted.internet import defer from twisted.web.client import getPage pages = ['http://www.google.com', 'http://www.orestis.gr', ...] all_deferreds = [] for page in pages: d = getPage(page) d.addCallback(gotPage) all_deferreds.append(d) deferredList = defer.DeferredList(all_deferreds) def all_finished(results): print "ALL PAGES FINISHED" deferredList.addCallback(all_finished)

Saturday, 18 June 2011

Twisted Simple Client (v3)


from twisted.internet import reactor, protocol, defer from simpleclient2 import gotData, UppercaseClientProtocol if __name__ == '__main__': import sys host, port = sys.argv[1].split(':') data_to_send = sys.argv[2:] all_deferreds = [] for data in data_to_send: print 'sending', data d = defer.Deferred() d.addCallback(gotData, data) factory = protocol.ClientFactory() factory.protocol = UppercaseClientProtocol factory.text = data factory.deferred = d all_deferreds.append(d) reactor.connectTCP(host, int(port), factory) deferredList = defer.DeferredList(all_deferreds) def all_done(results): reactor.stop() deferredList.addCallback(all_done) reactor.run()

simpleclient3.py
Saturday, 18 June 2011

Run this!

Simple Client v3 (output)


$ python simpleclient2.py 127.0.0.1:8000 a b c d sending a sending b sending c sending d received response for b Hi! Send me text to convert to uppercase B received response for a Hi! Send me text to convert to uppercase A received response for c Hi! Send me text to convert to uppercase C received response for d Hi! Send me text to convert to uppercase D $

Saturday, 18 June 2011

Observations
Data returns with random order We cannot access the returned data Loop never stops Performance?

Saturday, 18 June 2011

Twisted Simple Client (v4)


from twisted.internet import reactor, protocol, defer import time from simpleclient2 import UppercaseClientProtocol def gotData(data, request, starttime): print 'request', request, 'took', time.time() - starttime if __name__ == '__main__': import sys host, port = sys.argv[1].split(':') data_to_send = sys.argv[2:] overallstart = time.time() all_deferreds = [] for data in data_to_send: print 'sending', data d = defer.Deferred() d.addCallback(gotData, data, time.time()) factory = protocol.ClientFactory() factory.protocol = UppercaseClientProtocol factory.text = data factory.deferred = d all_deferreds.append(d) reactor.connectTCP(host, int(port), factory) deferredList = defer.DeferredList(all_deferreds) def all_done(results): reactor.stop() deferredList.addCallback(all_done) reactor.run() print 'finished, took', time.time() - overallstart

simpleclient4.py
Saturday, 18 June 2011

Run this!

Simple Client v4 (output)


$ python simpleclient4.py 127.0.0.1:8000 [...] sending http://orestis.gr sending http://amazon.com sending http://google.com sending http://apple.com request http://google.com took 0.596433877945 request http://amazon.com took 1.22381305695 request http://apple.com took 1.58467292786 request http://orestis.gr took 2.00057697296 finished, took 2.00096821785

against the proxy server


fetched fetched fetched fetched http://google.com took 0.593424081802 http://amazon.com took 1.22066617012 http://apple.com took 1.57956194878 http://orestis.gr took 1.99814605713

Saturday, 18 June 2011

Single threaded performance!


As good as threaded performance, without the complexity

Saturday, 18 June 2011

Exercise 3
Write a memcached SET command-line script

Send: Receive: Usage: Test:

set <key> 0 0 <data length>\r\n <data block>\r\n STORED\r\n

$ python memset.py host:port <key> <string>

must wait to store and return!


$ telnet host port ... get <key> VALUE <key> 0 <length> .... END

Saturday, 18 June 2011

Memcached SET client


from twisted.internet import reactor, protocol, defer from twisted.protocols import basic class MemsetProtocol(basic.LineReceiver): def connectionMade(self): value = self.factory.value self.transport.write('set %s 0 0 %d\r\n' % (self.factory.key, len(value))) self.transport.write(value) self.transport.write('\r\n') def lineReceived(self, line): if line == 'STORED': self.factory.deferred.callback(self.factory.key) if __name__ == '__main__': import sys host, port = sys.argv[1].split(':') key, value = sys.argv[2:] f = protocol.ClientFactory() f.protocol = MemsetProtocol f.key = key f.value = value f.deferred = defer.Deferred() reactor.connectTCP(host, int(port), f) f.deferred.addCallback(lambda _: reactor.stop()) reactor.run()

Run this!

Saturday, 18 June 2011

Exercise 4
Make this reusable!
def memset(host, port, key, value): """Return a deferred that fires on successful store"""

Saturday, 18 June 2011

Exercise 4
from twisted.internet import reactor, protocol, defer from memset import MemsetProtocol def memset(host, port, key, value): f = protocol.ClientFactory() f.protocol = MemsetProtocol f.key = key f.value = value f.deferred = defer.Deferred() reactor.connectTCP(host, int(port), f) return f.deferred

if __name__ == '__main__': import sys host, port = sys.argv[1].split(':') key, value = sys.argv[2:] d = memset(host, port, key, value) d.addCallback(lambda _: reactor.stop()) reactor.run()

Saturday, 18 June 2011

Exercise 5
Write an HTTP GET command-line script

Saturday, 18 June 2011

Exercise 5
from twisted.internet import reactor, protocol, defer class HTTPGETProtocol(protocol.Protocol): def connectionMade(self): self.buffer = [] self.transport.write('GET %s HTTP/1.1\r\n' % self.factory.path) self.transport.write('User-Agent: europython/2011\r\n') self.transport.write('Host: %s\r\n' % self.factory.host) self.transport.write('Connection: close\r\n') self.transport.write('\r\n') def dataReceived(self, data): self.buffer.append(data) def connectionLost(self, reason): self.factory.deferred.callback(''.join(self.buffer)) def get(address, host, path): f = protocol.ClientFactory() f.protocol = HTTPGETProtocol f.path = path f.host = host f.deferred = defer.Deferred() reactor.connectTCP(address, 80, f) return f.deferred

Saturday, 18 June 2011

A brief recap on writing clients


Figure out the protocol Write the protocol Create a factory, set instance variables Access the variables from the protocol Connect the factory to a host & socket

Saturday, 18 June 2011

Thats tedious!

Sharing state between protocols can be useful Many times we dont need it Theres another way to do this.

Saturday, 18 June 2011

t.i.protocol.ClientCreator
Change your protocol to have __init__ Create it with a protocol class and args Connect it to a host:port Attach a callback When the protocol is instantiated, callback is fired

Saturday, 18 June 2011

ClientCreator
from twisted.internet import reactor, protocol, defer from twisted.protocols import basic class MemsetProtocol(basic.LineReceiver): def __init__(self, key, value): self.key = key self.value = value self.deferred = defer.Deferred() def connectionMade(self): self.transport.write('set %s 0 0 %d\r\n' % (self.key, len(self.value))) self.transport.write(self.value) self.transport.write('\r\n') def lineReceived(self, line): if line == 'STORED': self.deferred.callback(self.key)

if __name__ == '__main__': import sys host, port = sys.argv[1].split(':') key, value = sys.argv[2:] client = protocol.ClientCreator(reactor, MemsetProtocol, key, value) d = client.connectTCP(host, int(port)) def got_protocol(protocol): return protocol.deferred d.addCallback(got_protocol) d.addCallback(lambda _: reactor.stop()) reactor.run()

Saturday, 18 June 2011

Exercise 6a

Rewrite the HTTP GET protocol to work with t.i.p.ClientCreator

Saturday, 18 June 2011

Exercise 6b
Rewrite the HTTP GET protocol to work with t.i.p.ClientCreator ...and the plain factory way HINT: Implement Factory.buildProtocol to customise the way...

Saturday, 18 June 2011

twisted.web
the early days

Saturday, 18 June 2011

twisted.web NOP
from twisted.web.resource import Resource from twisted.web.server import Site from twisted.internet import reactor from twisted.python import log import sys log.startLogging(sys.stdout) root = Resource() factory = Site(root) reactor.listenTCP(8000, factory) reactor.run()

Run this!
Saturday, 18 June 2011

twisted.web
from twisted.web.resource import Resource from twisted.web.server import Site from twisted.internet import reactor from twisted.python import log import sys log.startLogging(sys.stdout) class Index(Resource): def render_GET(self, request): return "HELLO" class Page(Resource): def render_GET(self, request): return 'A PAGE' root = Resource() root.putChild('', Index()) root.putChild('page', Page()) factory = Site(root) reactor.listenTCP(8000, factory) reactor.run()

Run this!
Saturday, 18 June 2011

Lets build something cool


Its going to be easy

Saturday, 18 June 2011

Website status dashboard


Monitor a list of websites Shows their status and response time Notify people via email when a site becomes slow Notify people via email when a site goes offline and comes back up

Saturday, 18 June 2011

How should we go about it?


Discuss

Saturday, 18 June 2011

curl -N http://localhost:8000
from twisted.web.resource import Resource from twisted.web.server import Site, NOT_DONE_YET from twisted.names.client import getHostByName from twisted.internet import reactor, defer from twisted.python import log import sys log.startLogging(sys.stdout) from httpget import get from sites import SITES class Index(Resource): def got_site(self, data, site, request): request.write('GOT %s (%d)\r\n' % (site, len(data))) def render_GET(self, request): dl = [] for site in SITES[:10]: d = getHostByName(site) d.addCallback(get, site, '/') d.addCallback(self.got_site, site, request) dl.append(d) dl = defer.DeferredList(dl) def finished(results): request.finish() dl.addCallback(finished) return NOT_DONE_YET

root = Resource() root.putChild('', Index()) factory = Site(root) reactor.listenTCP(8000, factory) reactor.run()

monitor1.py
Saturday, 18 June 2011

Run this!

Timeouts!
Abort the attempt after 30 seconds

delayedCall = reactor.callLater(10, func, *args, **kwargs) if delayedCall.active(): delayedCall.cancel()

How should we specify that? An exception?

Saturday, 18 June 2011

Introducing errbacks

Like callbacks, but for error condition Called explicitly - d.errback(reason) Called implicitly, when a callback function raises

Saturday, 18 June 2011

Errback example (v1)


from twisted.internet import reactor, defer def on_success(msg): print 'SUCCESS', msg def on_error(f): print 'ERROR', f.getErrorMessage() d1 = defer.Deferred() d1.addCallback(on_success) d1.addErrback(on_error) reactor.callLater(1, d1.callback, 'NEAT') d2 = defer.Deferred() d2.addCallback(on_success) d2.addErrback(on_error) reactor.callLater(2, d2.errback, Exception('BUMMER')) reactor.run()

errback.py
Saturday, 18 June 2011

Run this!

Errback example (v2)


from twisted.internet import reactor, defer def on_success(msg): print 'SUCCESS', msg def on_error(f): print 'ERROR', f.getErrorMessage() d1 = defer.Deferred() d1.addCallback(on_success) d1.addErrback(on_error) reactor.callLater(1, d1.callback, 'NEAT') reactor.callLater(2, d1.errback, Exception('BUMMER')) reactor.run()

errback_wrong.py
Saturday, 18 June 2011

Run this!

defer.setDebugging(True)

Saturday, 18 June 2011

There can only be one call


either one callback or one errback

Saturday, 18 June 2011

Saturday, 18 June 2011

Saturday, 18 June 2011

Calling code

Saturday, 18 June 2011

Calling code

Saturday, 18 June 2011

Relax, its easier than it looks

Saturday, 18 June 2011

Calling code

def s_function(result): if result == "NO": raise Exception(result) else: return result.upper() def a_function(result, d): if result == "NO": d.errback(Exception(result)) else: d.callback(result)

Saturday, 18 June 2011

try:

result = s_function(something) print result except: print "OH NOES" def on_success(r): print r def on_error(_): print "OH NOES" d = defer.Deferred() d.addCallbacks(on_success, on_error) a_function(something, d)

Saturday, 18 June 2011

addCallbacks?
d = defer.Deferred() d.addCallback(on_success) d.addErrback(on_error) a_function(something, d)

Saturday, 18 June 2011

def NOP(x): return x

d.addCallback(on_success) d.addErrback(on_error)

d.addCallbacks(on_success, NOP) d.addCallbacks(NOP, on_error)

Saturday, 18 June 2011

Scenario: Download Google Doodle Alt Text


getPage(http://www.google.com) On error, print ERROR: Google down Try to find doodle text On error, print ERROR: No doodle found Finally, print doodle text

Saturday, 18 June 2011

Scenario: Download Google Doodle Alt Text


try: html = urllib2.openurl('http://www.google.com').read() try: doodle_text = find_doodle_text(html) print doodle_text except: print "ERROR: No doodle found" except: print "ERROR: Google down"

Saturday, 18 June 2011

Scenario: Download Google Doodle Alt Text


d = getPage('http://www.google.com') def on_google_down(e): return "ERROR: Google down" def on_page(html): try: return find_doodle_text(html) except: return "ERROR: No doodle found" def print_result(r): print r d.addCallbacks(on_page, on_google_down) d.addCallback(print_result)

Saturday, 18 June 2011

getPage
Google Search on_html Doodle or ERROR: No doodle found 500 Error on_google_down ERROR: Google Down print_result

Saturday, 18 June 2011

Scenario: Download Google Doodle Alt Text


d = getPage('http://www.google.com') def on_google_down(e): return "ERROR: Google down" def on_no_doodle(e): return "ERROR: No doodle found" def print_result(r): print r d.addCallbacks(find_doodle_text, on_google_down) d.addErrback(on_no_doodle) d.addCallback(print_result)

Saturday, 18 June 2011

getPage

nd_doodle_text

on_google_down

on_no_doodle

print_result
Saturday, 18 June 2011

getPage
Google nd_doodle_text Doodle on_no_doodle Doodle print_result
Saturday, 18 June 2011

on_google_down

getPage
Google nd_doodle_text on_google_down Exception on_no_doodle
ERROR: No doodle found

print_result
Saturday, 18 June 2011

getPage
500 Error nd_doodle_text on_google_down

ERROR: Google Down

on_no_doodle

print_result
Saturday, 18 June 2011

GET with timeout


class HTTPGETProtocol(protocol.Protocol): def connectionMade(self): self.buffer = [] self.timeoutCall = None self.timedOut = False self.transport.write('GET %s HTTP/1.1\r\n' % self.factory.path) self.transport.write('User-Agent: europython/2011\r\n') self.transport.write('Host: %s\r\n' % self.factory.host) self.transport.write('Connection: close\r\n') self.transport.write('\r\n') self.timeoutCall = reactor.callLater(10, self.timeout) def dataReceived(self, data): self.buffer.append(data) def timeout(self): self.timedOut = True self.factory.deferred.errback(error.TimeoutError()) def connectionLost(self, reason): if not self.timedOut: if self.timeoutCall and self.timeoutCall.active(): self.timeoutCall.cancel() self.factory.deferred.callback(''.join(self.buffer))

httpget2.py

sudo python slowserver.py


Saturday, 18 June 2011

curl -N http://localhost:8000
from httpget2 import get SITES = [ 'aaaaaaa.nonexistantsiteprettysure.com', 'apple.com', 'orestis.gr', 'localhost', ] #from sites import SITES class Index(Resource): def got_site(self, data, site, request): request.write('GOT %s (%d)\r\n' % (site, len(data))) def got_error(self, failure, site, request): request.write('ERROR %s (%s)\r\n' % (site, failure.getErrorMessage())) def render_GET(self, request): dl = [] for site in SITES[:10]: d = getHostByName(site) d.addCallback(get, site, '/') d.addCallbacks(self.got_site, self.got_error, callbackArgs=(site, request), errbackArgs=(site, request), ) dl.append(d) dl = defer.DeferredList(dl) def finished(results): request.finish() dl.addCallback(finished) return NOT_DONE_YET

monitor2.py
Saturday, 18 June 2011

Run this!

Exercise 7
Differentiate between different errors: twisted.names.dns.DomainError twisted.error.TimeoutError Everything else Have a final callback that writes results to request HINT: Use Failure.trap(error_class)

Saturday, 18 June 2011

curl -N http://localhost:8000
def got_site(self, data, site): return 'GOT %s (%d)\r\n' % (site, len(data)) def got_get_error(self, failure, site): failure.trap(error.TimeoutError) return 'GET ERROR %s (%s)\r\n' % (site, failure.getErrorMessage()) def got_dns_error(self, failure, site): failure.trap(DomainError) return 'DNS ERROR %s (%s)\r\n' % (site, failure.getErrorMessage()) def got_other_error(self, failure, site): return 'UNKNOWN ERROR %s (%s)\r\n' % (site, failure.getErrorMessage()) def render_GET(self, request): dl = [] for site in SITES[:10]: d = getHostByName(site) d.addCallback(get, site, '/') d.addCallback(self.got_site, site) d.addErrback(self.got_dns_error, site) d.addErrback(self.got_get_error, site) d.addErrback(self.got_other_error, site) d.addCallback(request.write) dl.append(d)

monitor3.py
Saturday, 18 June 2011

getHostByName
get got_site got_dns_error got_get_error got_other_error request.write
Saturday, 18 June 2011

curl -N http://localhost:8000

def render_GET(self, request): dl = [] for site in SITES[:10]: d = getHostByName(site) def on_get_name(address, site): d2 = get(address, site, '/') d2.addCallbacks(self.got_site, self.got_get_error, callbackArgs=(site,), errbackArgs=(site,)) return d2 d.addCallbacks(on_get_name, self.got_dns_error, callbackArgs=(site,), errbackArgs=(site,)) d.addErrback(self.got_other_error, site) d.addCallback(request.write) dl.append(d)

monitor3.py
Saturday, 18 June 2011

getHostByName
on_get_name got_dns_error

got_site

got_get_error

got_other_error request.write
Saturday, 18 June 2011

Lets drink from the firehose


remove the site limit

Saturday, 18 June 2011

Doesnt scale!

You just DOSed your operating system Twisted will happily open the connections You need to ensure you dont overload the system

Saturday, 18 June 2011

Many issues
We overload the DNS We saturate the network We exhaust the open file limit We do this for every request!

Saturday, 18 June 2011

DeferredSemaphore
limit = defer.DeferredSemaphore(10) d = limit.acquire() def on_acquire(limit): v = func(a, b) limit.release() return v d.addCallback(on_acquire) d.addCallback(done)

limit = defer.DeferredSemaphore(10) d = limit.run(func, a, b) d.addCallback(done)

Saturday, 18 June 2011

DeferredSemaphore
dnsLimit = defer.DeferredSemaphore(5) getLimit = defer.DeferredSemaphore(10) for site in SITES: d = dnsLimit.run(getHostByName, site) def on_get_name(address, site): d2 = getLimit.run(get, address, site, '/') d2.addCallbacks(self.got_site, self.got_get_error, callbackArgs=(site,), errbackArgs=(site,)) return d2

Example: You do not want to have more than 5 simultaneous DNS queries and 10 GETs

Saturday, 18 June 2011

Seems to work, but...

We are creating the workload while we are operating on it We seem to be blocking - everything is run inside a giant callback chain

Saturday, 18 June 2011

The callbacks must be cooperative


When accessing the network, return control back to the loop The loop will call your code when the network is ready Must do as little work as possible Doesnt eliminate CPU-bound delays!

Saturday, 18 June 2011

Seems to work, but...

In our case, the limit.release is triggering a callback which triggers a deferred which triggers a callback immediately reactor doesnt get a chance to breathe

Saturday, 18 June 2011

twisted.internet.task

Saturday, 18 June 2011

cooperate

Saturday, 18 June 2011

Saturday, 18 June 2011

DeferredQueue

Saturday, 18 June 2011

Writing clients

timeouts error handling

Saturday, 18 June 2011

Using twisted from client code

show sendmail show spawnProcess show deferToThread/callInThread

Saturday, 18 June 2011

twisted codebase

interfaces/adapters/components/plugins

Saturday, 18 June 2011

More deferred goodness

cancellable deferreds

Saturday, 18 June 2011

Perspective broker

just simple mention

Saturday, 18 June 2011

manholes?

Saturday, 18 June 2011

testing

Saturday, 18 June 2011

Anda mungkin juga menyukai