Notes
Notes
Notes
class RequestHandler(server.SimpleHTTPRequestHandler):
def do_GET(self):
start_time = time.time()
time.sleep(5)
end_time = time.time()
self.send_response(200, "OK")
self.end_headers()
response = (
"Started at {}, Ended at {} difference {}".format(
start_time, end_time, end_time - start_time
)
)
self.wfile.write(response.encode(’utf-8’))
return
with server.HTTPServer(
(’localhost’, 8000), RequestHandler
) as http_server:
http_server.serve_forever()
1.2 Multi-Threaded
Converting above program to a multithreaded version will help. A multi-
threaded program will handle each HTTP request in a new thread and thus
it can handle mutiple requests at the same time.
import time
1
class RequestHandler(server.SimpleHTTPRequestHandler):
def do_GET(self):
time.sleep(40)
end_time = time.time()
self.send_response(200, "OK")
self.end_headers()
response = "Ended at {}".format(end_time)
self.wfile.write(response.encode(’utf-8’))
return
with ThreadedHTTPServer(
(’localhost’, 8000), RequestHandler
) as http_server:
http_server.serve_forever()
2
1.3 Event-driven
Now, if we convert above example into event-driven asynchronous code then it
will look as below.
import time
class BusyPage(Resource):
isLeaf = True
factory = Site(BusyPage())
reactor.listenTCP(8000, factory)
reactor.run()
The output statistics of both the command will be different. The asyn-
chronous approach will be able to handle all the requests at the same time.
Reason behind this is when the request is coming, the IO thread is trying to
process the request. During that time, if it is blocked the code is handled by
event-driven approach and the thread is again available for entertaining another
request. The processing of request is again resumed when the duration of IO is
completed and returned.
3
2 Introduction to Twisted programming
2.1 History
Twisted was invented by Glyph Lefkowitz in the year October 22, 2002 which
is almost 16 years before. It is an old giant.
4
2.2 Current situation
At present, the Twisted framework is developed and managed by its community.
Latest stable version is 18.9.x. The Twisted framework is licensed under MIT
license. Twisted is based on event-driven programming paradigm.
2.3 Sockets
Twisted programming supports all the TCP, UDP, SSL/TSL based sockets. It
also supports the Unix sockets.
2.4 Protocol
Twisted supports many application layer protocols. Such as HTTP, XMPP,
IMAP, SSH, IRC, FTP and many more.
class EchoServer(protocol.Protocol):
class EchoFactory(protocol.Factory):
5
return EchoServer()
reactor.listenTCP(8000, EchoFactory())
reactor.run()
class EchoClient(protocol.Protocol):
def connectionMade(self):
request = "Hello, world!"
self.transport.write(request.encode("utf-8"))
class EchoFactory(protocol.ClientFactory):
6
4 Reactor
Reactor is the core of the event loop. It is responsible for waiting for the event
and notify the program when the event is occurred. Reactor is dependent on
platform specific libraries. Twisted will automatically choose default platform
specific library.
For under the hood details, In GNU/Linux platform it tries to fetch for epoll
[?]. If epoll [?] is not available, it will use poll [?]. All the POSIX compliant
platforms it will use poll. All other platforms such as Windows and Macintosh
it will use select reactor.
You have to register your callbacks to the reactor and have to start it. Once
the reactor is start, it will listen to the events and notifies to the program forever
or until reactor.stop is called.
In our echo server and echo client example, below code is representing the
reactor. The reactor.run() will start the event loop until the SIGINT or
reactor.stop() is called.
reactor.listenTCP(8000, MyFactory())
reactor.run()
5 Transport
Transport is responsible to provide the behaviour for transferring data to the
other end of the connection. The transport behaves differently according to
the connection for example, TCP or UDP will have different implementation of
transport. In echo client and echo server example below code is representing
Transport
self.transport.write()
5.1 write
Should write data to the connection.
5.2 writeSequence
Should write list of strings to the connection.
5.3 loseConnection
Should close the connection after writing ending data.
7
5.4 getPeer
Should return remote address of the connection.
6 Protocol
Protocol will define the behaviour to process the network events in async man-
ner. Twisted does have in-built definition for many protocols such as HTTP,
Telnet, DNS etc. Base class is protocol.Protocol.
In echo server and echo client example, below code is of Protocol.
\\ Client
class EchoClient(protocol.Protocol):
def connectionMade(self):
request = "Hello, world!"
self.transport.write(request.encode("utf-8"))
\\ Server
class EchoServer(protocol.Protocol):
6.1 makeConnection
Should create a connection.
6.2 connectionMade
Should be called when a new connection is made.
6.3 dataReceived
Should be called when any data is received from the other end of the circuit.
8
6.4 connectionLost
Should be called when the connection is terminated.
7 Protocol Factory
The factory will produce the instance of Protocol when the new connection is
made. Protocol will be garbage collected when the collection is called. The
Factory will store configuration details for Protocol instances.
According to our echo server and echo client example, below is the code of
portable factory.
\\ Server
class EchoFactory(protocol.Factory):
\\ Client
class EchoFactory(protocol.ClientFactory):
7.1 buildProtocol
should create the instance of Protocol class.
From the above example, purpose of protocol factories might not be clear in
your mind. For that reason, we will conver another example.
\\chat_server.py
from twisted.internet.protocol import Factory
9
from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor
class ChatProtocol(LineReceiver):
def connectionMade(self):
self.factory.users.append(self)
class ChatFactory(Factory):
def __init__(self):
self.users = []
reactor.listenTCP(8000, ChatFactory())
reactor.run()
Connect with chat server using multiple connections made by telnet pro-
gram. This will allow to communicate multiple clients as in chatting in group. In
that code, concurrent client instances are stored at Protocol Factory. Whenever
you have to store persistent information beyond the lifecycle of your socket, you
should store that in the Protocol Factory. Protocol will be created and garbage
collected with connection.
8 Deferreds
Deferreds are the core API provided by the Twisted. This API is provided for
writing a callbacks. A callback is a type of function which can be paused by an
event-loop and resumed when specific event is happended.
If you are assuming that just writing code in Twisted will make things asyn-
10
chronous then you are wrong. You have to wrap your blocking code into appro-
priate Deferred to make it event-driven.
We have already encountered with deferred API in our Twisted webserver
example.
8.1 addCallback
Below is the example of addCallback and callback API
def callback_func(result):
print(result)
d = Deferred()
d.addCallback(callback_func)
d.callback("Trigerred the callback!")
In the above example, the callback will fire the callback with the arguemtn
of string. The callbackfunction is added as a callback by the addCallback
8.2 addErrback
\\Example Errback
from twisted.internet.defer import Deferred
def errorback_func(failure):
print(failure)
d = Deferred()
d.addErrback(errorback_func)
d.errback("Triggered! Error!".encode("utf-8"))
11
In above example, the errback is responsible for triggering the error. The
addErrback is responsible for registering the callback.
8.3 addCallbacks
from twisted.internet.defer import Deferred
def callback_func(result):
print(result)
def errback_func(failure):
print(failure)
d = Deferred()
d.addCallbacks(callback_func, errback_func)
d.callback(b"Hello")
#d.errback(b"Hello")
8.4 Comparision
The addCallback or addErrrback will add callbacks at different level whereas
the addCallbacks will add callback at the same level.
9 Testing
Testing in Twisted is considered as tricky. Twisted has dedicated utility called
trial for running and managing the tests. The trial is based on pytest. You
can use trial for various purposes.
class TestEchoServer(unittest.TestCase):
12
def test_response(self):
factory = EchoFactory()
protocol = factory.buildProtocol(("localhost", 0))
transport = proto_helpers.StringTransport()
protocol.makeConnection(transport)
data = b"test\r\n"
protocol.dataReceived(data)
self.assertEqual(transport.value(), data)
10 Deployments
10.1 twistd
twistd is an utility for running the Twisted code. twistd will manage facilities
like logging and running the service in daemonize mode.
10.3 Example
We will consider the echo server example we observed earlier and try to wrap it
into tac file.
class EchoProtocol(protocol.Protocol):
class EchoFactory(protocol.Factory):
13
def buildProtocol(self, addr):
return EchoProtocol()
sys.path.append(os.path.abspath(os.path.dirname(__file__)))
application = service.Application("echo")
echoService = internet.TCPServer(8000, EchoFactory())
echoService.setServiceParent(application)
10.3.3 Run
You can run the twisted application using below command
twistd -n -y echo_server.tac
References
[1] http://man7.org/linux/man-pages/man7/epoll.7.html
[2] http://man7.org/linux/man-pages/man2/poll.2.html
[3] https://buildbot.net/
[4] https://omegle.com
[5] https://www.twilio.com/
[6] https://www.twitch.tv/
[7] https://scrapy.org/
14