Python and networking

Vincent Bernat

Note

This ar­ti­cle was pub­lished in GNU/Linux Mag­a­zine HS n° 40 in 2009 in French. It is trans­lated here to Eng­lish with an LLM.

Like many lan­guages, Python pro­vides the full ar­se­nal needed to build net­work ap­pli­ca­tions. The stan­dard li­brary cov­ers both servers and clients. This ar­ti­cle ex­plores what Python of­fers in this area.

The ar­ti­cle has four parts. First, we ex­plore how to write net­work clients us­ing the stan­dard li­brary. Then, we tackle the server side. In these first two parts, we use high-level prim­i­tives that speed up de­vel­op­ment. In the third part, we drop down to prim­i­tives equiv­a­lent to those found in C pro­grams. Python can, us­ing these prim­i­tives, mimic the be­hav­ior of any net­work tool usu­ally writ­ten in C. Fi­nally, we ex­plore a dif­fer­ent ap­proach with the Twisted net­work framework.

Writing a client#

Python’s stan­dard li­brary in­cludes many mod­ules that make it easy to write clients for a wide va­ri­ety of pro­to­cols, in­clud­ing, for ex­am­ple, HTTP, FTP, POP, IMAP, SMTP, and Telnet.

Other pro­to­cols are sup­ported too, and you can al­ways write a mod­ule for your own pro­to­col or find what you need among third-party mod­ules. We fo­cus on a few us­age ex­am­ples of the mod­ules men­tioned above.

These are high-level mod­ules. They per­form many tasks in a sin­gle com­mand to es­tab­lish a con­nec­tion with­out re­quir­ing you to deal with the de­tails. For in­stance, the FTP mod­ule ex­poses a sim­ple func­tion to send a file to the server. This func­tion ne­go­ti­ates the data chan­nel and man­ages ac­tive or pas­sive mode au­to­mat­i­cally. These are de­tails you do not want to deal with when writ­ing a pro­gram that up­loads or re­trieves files over FTP.

We see later how to write lower-level ap­pli­ca­tions and man­age all net­work ex­changes ourselves.

A simple HTTP client#

To write an HTTP client, the sim­plest ap­proach is to use the urllib2 mod­ule. It is not lim­ited to HTTP: it han­dles URLs in gen­eral and sup­ports other pro­to­cols like FTP or GOPHER. Still, its pri­mary use re­mains HTTP. It sup­ports au­then­ti­ca­tion, prox­ies, redi­rec­tions, and cook­ies. We will not ex­plore all avail­able fea­tures. For de­tails, read the on­line doc­u­mentation.

Let us start with a sim­ple client that re­trieves the con­tent of a web page:

import urllib2

lm = urllib2.urlopen("http://www.gnulinuxmag.com/")
print(lm.read())

This pro­gram re­trieves the web page of the GNU/Linux Mag site and dis­plays it. You should see the HTML source of your fa­vorite mag­a­zine’s web page. If some­thing goes wrong, you get an ex­cep­tion such as URLError or HTTPError. It is up to you to han­dle this case properly.

Our first HTTP client is not very in­ter­est­ing. Let us write a slightly more use­ful script that dis­plays the lat­est CVE vul­ner­a­bil­i­ties—a mini RSS reader. The feed is avail­able on­line. Since it is XML, we can parse it with an XML li­brary. Python 2.5 in­te­grated the ElementTree mod­ule, which is sim­ple to use. For Python 2.4 users, it can be in­stalled sep­a­rately (on De­bian, it is the python-celementtree package).

import urllib2

try:
    import xml.etree.ElementTree as ET
except ImportError:
    import cElementTree as ET
cve = urllib2.urlopen("http://nvd.nist.gov/download/nvd-rss.xml")
root = ET.parse(cve).getroot()
titles = []
for i in root.findall("{http://purl.org/rss/1.0/}item"):
    titles.append(i.find("{http://purl.org/rss/1.0/}title").text)
print("\n".join(titles))

Let us ex­plain this code. We at­tempt to load the XML mod­ule ElementTree from Python 2.5. If it is not found, we fall back to the cElementTree mod­ule, which must be in­stalled sep­a­rately. Then, we re­trieve the web page of in­ter­est and feed it to the XML parser. Fi­nally, we ex­tract the el­e­ments we care about: from the root, we search for all “item” el­e­ments. Within those, we keep only the “ti­tle” element.

Note

We use a cu­ri­ous no­ta­tion to find the el­e­ments in ques­tion. This no­ta­tion is due to the use of XML names­paces in the XML file. In­deed, it is pos­si­ble to mix mul­ti­ple XML vo­cab­u­lar­ies within the same file, such as XHTML with RSS. ElementTree han­dles these names­paces us­ing the no­ta­tion shown in our ex­am­ple. Here, {http://​purl.​org/​rss/​1.0/} in­di­cates the RSS names­pace. A more com­mon no­ta­tion would be rss:​title with a pre­fix dec­la­ra­tion in the XML preamble.

Our pro­gram is al­ready a bit more use­ful. We could im­prove it by han­dling the ex­cep­tions that may oc­cur dur­ing re­trieval or pars­ing. But that is not the pur­pose of this article.

Sub­mit­ting a form and re­triev­ing the con­tent is an al­most equally sim­ple op­eration:

import urllib, urllib2

content = urllib2.urlopen(
    "http://www.example.com/form", data=urllib.urlencode({"p1": 6, "p2": "alpha"})
)

When the data pa­ram­e­ter is present, urllib2 uses the POST method in­stead of GET. The data must be en­coded into a string the re­ceiv­ing ap­pli­ca­tion un­der­stands. In our ex­am­ple, the data must be sent in the form p2=​alpha&p1=6. This is the role of the urlencode method from the urllib module.

An SMTP client#

Let us go back to our first ex­am­ple. Imag­ine we want to send the CVE vul­ner­a­bil­i­ties by email rather than dis­play­ing them on screen. The smtplib mod­ule han­dles this: it com­mu­ni­cates with an SMTP server to send a message.

smtplib only han­dles the SMTP part. You need to pro­vide it with an email com­pli­ant with RFC 822. In its stan­dard li­brary, Python in­cludes the email mod­ule for cre­at­ing such mes­sages. In our pre­vi­ous pro­gram, we re­place the fi­nal dis­play with the con­struc­tion of the email:

import smtplib
from email.MIMEText import MIMEText

msg = MIMEText("\n".join(titles)
msg['Subject'] = 'Latest CVE'
msg['From'] = 'cve@example.com'
msg['To'] = 'user@example.com'

msg rep­re­sents the mes­sage we want to send via SMTP. Us­ing smtplib is straight­forward:

s = smtplib.SMTP()
s.connect()
s.sendmail("cve-bounces@example.com", ["user@example.com"], msg.as_string())
s.close()

Since we pro­vide no pa­ram­e­ters, nei­ther to the SMTP con­struc­tor nor to the connect method, the con­nec­tion will be made to IP 127.0.0.1. You need a func­tional SMTP server run­ning lo­cally. Oth­er­wise, you can pass the SMTP server name as the first pa­ram­e­ter of the SMTP ob­ject con­struc­tor (or the connect method).

We spec­ify the sender and re­cip­i­ent ad­dresses in two dif­fer­ent places. In fact, the SMTP pro­to­col dis­tin­guishes be­tween the en­ve­lope and the mes­sage. When you re­ceive an email, you only see the mes­sage (which in­cludes head­ers and body). When the email trav­els from SMTP server to SMTP server, the servers use the en­ve­lope and ig­nore the mes­sage. Once the mes­sage ar­rives, the en­ve­lope is de­stroyed. Thus, you can put what­ever you want in the To: and From: fields. What mat­ters is what is given to the sendmail func­tion. In our ex­am­ple, we used cve-bounces@example.​com as the en­ve­lope ad­dress. This way, bounces will be re­ceived at this ad­dress (they are sent to the sender in­di­cated in the en­ve­lope) and any replies from the re­cip­i­ent will go to cve@example.​com, which is the ad­dress that will ap­pear in the email client.

Our pro­gram re­mains ba­sic and does not han­dle the ex­cep­tions that may occur.

An FTP client#

The ftplib mod­ule makes it easy to write an FTP client. Al­though this pro­to­col is par­tic­u­larly in­se­cure, it is still the most com­mon pro­to­col for trans­fer­ring data to a host­ing provider. It is also the pro­to­col used when you have a ded­i­cated server and your host­ing provider gives you stor­age space. Let us place our prac­ti­cal ex­am­ple in this lat­ter sce­nario: you per­form daily back­ups of your ded­i­cated server into a spe­cific di­rec­tory. You now want to trans­fer these back­ups to the space al­lo­cated to you on the host­ing provider’s FTP server.

It would be pos­si­ble to script an ex­ist­ing FTP client. But such a client would have trou­ble com­pet­ing with what Python of­fers. Be­sides, we are in a Python spe­cial issue!

We as­sume the back­ups to trans­fer fol­low the pat­tern “name.xx” where “xx” is a se­quence num­ber. For ex­am­ple, “web.15”, “archives.17”, and so on. We will write a func­tion that takes the name and se­quence num­ber as pa­ram­e­ters and per­forms the fol­low­ing actions:

  • check if the file al­ready ex­ists and up­load it if it does not;
  • delete files with the same name but with a se­quence num­ber that is too old.

Here is a proposal:

import re
from ftplib import FTP

dst = "/backup"
ftp_host = "ftp.example.com"
ftp_login = "login"
ftp_password = "password"
keep = 5

def upload_ftp(backups):
    ftp = FTP(ftp_host, ftp_login, ftp_password)
    remotes = ftp.nlst()
    for f in backups:
        name = "%s.%d" % (f, backups[f])
        # Upload the file if it is not present
        if name not in remotes:
            print("Envoi de %s" % name)
            ftp.storbinary("STOR %s" % name, file("%s/%s" % (dst, name)))
        # Delete old files
        for r in remotes:
            mo = re.match(r"%s\.([0-9]+)$" % f, r)
            if mo:
                current = int(mo.group(1))
                if current < backups[f] - keep + 1:
                    print("Supprime %s" % r)
                    ftp.delete(r)
                else:
                    print("Conserve %s" % r)
    ftp.quit()

The backups vari­able ex­pected by the func­tion is a dic­tio­nary whose keys are the names and val­ues are the se­quence num­bers. For example:

{ 'web': 15, 'archives': 17 }

What does it do? First, we cre­ate an FTP ob­ject by pro­vid­ing the host­name, lo­gin, and pass­word. With­out the last two, the client con­nects anony­mously. The nlst method gives us the list of files in the cur­rent di­rec­tory. This list tells us whether the files we want to send al­ready ex­ist and which to delete.

Then, for each backup file, we check whether the file ex­ists. If not, we up­load it us­ing the storbinary method. This method per­forms the fol­low­ing actions:

  • switch to bi­nary mode,
  • ne­go­ti­ate the data chan­nel (ac­tive, pas­sive, port se­lec­tion, etc.),
  • send the file content.

We pass as a pa­ram­e­ter the com­mand to send to the server. It must start with STOR fol­lowed by the name the file should have on the server. The sec­ond pa­ram­e­ter is the file to up­load. Any­thing that looks like a file to Python works. For ex­am­ple, you could pass the re­sult of urllib2.​urlopen()!

Then, we look at all the files present on the server and delete the old­est ones with the delete method.

Al­though the FTP pro­to­col is quite com­plex to im­ple­ment from scratch, the ftplib mod­ule makes it easy with a few meth­ods. As with the other ex­am­ples, you should add proper er­ror handling…

And the rest?#

There re­main many un­ex­plored pro­to­cols. For some of them, Python pro­vides a mod­ule that does the heavy lift­ing. You should there­fore read the as­so­ci­ated doc­u­men­ta­tion, as each mod­ule of­fers a dif­fer­ent in­ter­face. If you en­counter a pro­to­col that does not have a mod­ule in Python’s stan­dard li­brary, you have sev­eral options:

  • go one level down and use the socket mod­ule, as we will see later;
  • use the telnetlib mod­ule if the pro­to­col uses TCP and is text-oriented;
  • find an ex­ter­nal module;
  • use Twisted, which sup­ports ad­di­tional protocols.

When test­ing a server, it is com­mon to use the telnet com­mand. The tel­net pro­to­col is ba­sic, and the tel­net client pro­vided on many sys­tems al­lows you to com­mu­ni­cate with an SMTP, POP, NNTP server, and so on. You can use Python’s telnetlib mod­ule in sim­i­lar sit­u­a­tions. But the tel­net pro­to­col has a set of com­mands in­voked through a spe­cific es­cape se­quence. This se­quence may con­flict with the pro­to­col you are try­ing to im­ple­ment. Make sure the telnetlib mod­ule is only used with a tel­net server or a server that does not use this es­cape sequence.

The telnetlib mod­ule pro­vides some handy meth­ods for wait­ing for spe­cific char­ac­ter se­quences. Still, for the rea­sons stated above, pre­fer the lower-level meth­ods we see later, com­bined with a mod­ule like pexpect that pro­vides the same services.

As for ex­ter­nal mod­ules, there are many choices, in­clud­ing mod­ules that com­pete with those pro­vided in Python’s stan­dard li­brary. Python’s stan­dard li­brary does not claim to be ex­haus­tive, nei­ther in sup­ported pro­to­cols nor in func­tional rich­ness, so do not hes­i­tate to look into com­mu­nity mod­ules. For ex­am­ple, if you want SSH client ca­pa­bil­i­ties, the paramiko mod­ule is a good candidate.

Writing a server#

We have just seen that Python of­fers many mod­ules for writ­ing clients. For servers, the of­fer­ing is much smaller. If you want to write an FTP server, for ex­am­ple, you will need to ei­ther use a third-party mod­ule or roll up your sleeves and write things from scratch.

Still, Python pro­vides an SMTP server as well as an HTTP server. We do not cover the SMTP server here. Python also pro­vides a mod­ule for writ­ing generic servers. We cover these lat­ter two modules.

An HTTP server#

In its stan­dard li­brary, Python pro­vides sev­eral mod­ules for writ­ing web servers. These mod­ules build on the HTTPServer class from the BaseHTTPServer mod­ule. An in­stance of BaseHTTPRequestHandler han­dles each HTTP request.

Serving static files#

The SimpleHTTPServer mod­ule pro­vides a class in­her­it­ing from BaseHTTPRequestHandler that serves files.

For ex­am­ple, if you want to serve the con­tents of the public_​html di­rec­tory in your home folder, you can write the fol­low­ing code:

#!/usr/bin/env python
import os
from BaseHTTPServer import HTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler

os.chdir(os.path.expanduser("~/public_html"))
httpd = HTTPServer(("", 8080), SimpleHTTPRequestHandler)
httpd.serve_forever()

Let us look at the last two lines in de­tail. We in­stan­ti­ate the HTTPServer class with two pa­ram­e­ters. The first pa­ram­e­ter in­di­cates, as a tu­ple, on which ad­dress and port the server should lis­ten. We pro­vide an empty string for the ad­dress, mean­ing we lis­ten on all ad­dresses. We could also have spec­i­fied an IP like 127.0.0.1. The sec­ond pa­ram­e­ter is a class that will be in­stan­ti­ated with three pa­ram­e­ters for each re­quest, whose role is to process the re­quest. Once the web server is in­stan­ti­ated, we ask it to re­spond to re­quests. That is the pur­pose of the last line.

The SimpleHTTPRequestHandler class is so sim­ple that there is no triv­ial way to choose the di­rec­tory to serve. We there­fore change di­rec­tory be­fore serv­ing re­quests. Once this script is launched, you can test it with your browser us­ing the URL http://localhost:8080. You will see the con­tents of the public_​html di­rec­tory in your home folder.

In just a few lines, you get a func­tional web server. Al­though ba­sic, it sup­ports HTTP/0.9, HTTP/1.0, and HTTP/1.1 and has some se­cu­rity mea­sures pre­vent­ing at­tacks based on adding “..” to the path. But it is in­ef­fi­cient: it han­dles one re­quest at a time.

Serving dynamic pages#

There is lit­tle in­ter­est in serv­ing only static pages. To cre­ate a mini dy­namic site, you just need to write a class in­her­it­ing from BaseHTTPRequestHandler. This class must im­ple­ment meth­ods such as do_​GET, do_​HEAD, do_​POST, etc. Let us look at a first sim­ple example.

#!/usr/bin/env python
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler

class MyRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()
        self.wfile.write("""<html><body><h1>Hello world !</h1></body></html>""")

httpd = HTTPServer(("", 8080), MyRequestHandler)
httpd.serve_forever()

We wrote our own class to re­spond to re­quests. We can only re­spond to GET re­quests, and we in­vari­ably re­turn the same HTML page. The BaseHTTPRequestHandler class pro­vides a num­ber of meth­ods to make our job eas­ier. With the send_​response method, we send the 200 re­sponse code in­di­cat­ing the page was found. Then, us­ing send_​header, we send the Content-Type HTTP header in­di­cat­ing that the re­sponse con­tent is HTML. Next, the end_​headers method writes a blank line. We are then free to send what­ever con­tent we like. The wfile at­tribute serves this pur­pose: writ­ing to it sends data to the client.

Try ac­cess­ing the URL http://​localhost:​8080/​test/ and you should see a page say­ing “Hello world!” Not very dy­namic. But you can then do what­ever you want. For ex­am­ple, let us re­write the MyRequestHandler class as follows:

class MyRequestHandler(BaseHTTPRequestHandler):
    visiteur = 0

    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()
        MyRequestHandler.visiteur += 1
        self.wfile.write("""<html><body><h1>Hello world !</h1>
Vous avez demandé la page %s. Votre IP est %s. Vous êtes le visiteur %d.
</body></html>""" % (self.path, self.client_address[0], MyRequestHandler.visiteur))

We get a dy­namic page show­ing the re­quested page, the client’s IP, and dis­play­ing a counter. Note that the class is re­in­stan­ti­ated for each re­quest. For the counter to per­sist be­tween re­quests, we made it a class at­tribute rather than an in­stance at­tribute, which ex­plains why we use MyRequestHandler.​visiteur in­stead of self.​visiteur.

Python’s stan­dard li­brary is not suited for writ­ing a full web ap­pli­ca­tion. You would have to code ses­sion man­age­ment, cookie han­dling, ar­gu­ment de­cod­ing, and URL-to-func­tion map­ping your­self. On top of that, you have to write HTML man­u­ally. A frame­work like Django or Twisted pro­vides such fea­tures for free.

A TCP server#

The HTTPServer class ac­tu­ally in­her­its from a more generic class that al­lows pro­gram­ming ar­bi­trary servers. Python’s stan­dard li­brary pro­vides classes to fa­cil­i­tate writ­ing TCP or UDP servers. These classes are in the SocketServer mod­ule. There, you can find for ex­am­ple the TCPServer class, which we will use to write a small server that pro­vides some sys­tem in­formation.

We de­fine a very sim­ple pro­to­col for this server: upon con­nec­tion, the user can is­sue com­mands such as uptime and df. The server then re­turns the out­put of these com­mands and closes the con­nec­tion. This is not un­like the ba­sic op­er­a­tion of an HTTP server.

As with writ­ing an HTTP server, we must de­fine a TCPServer in­stance and pro­vide it with a class that will be in­stan­ti­ated for each re­quest. Let us first see how to use TCPServer. We will then see how to build the server we just de­scribed.

#!/usr/bin/env python
import os
from SocketServer import TCPServer, StreamRequestHandler

class MyRequestHandler(StreamRequestHandler):
    def handle(self):
        self.wfile.write("Votre est IP est %s. Au revoir\n" % self.client_address[0])

tcpd = TCPServer(("", 2020), MyRequestHandler)
tcpd.serve_forever()

The struc­ture is very sim­i­lar to our HTTP server! We de­fine a class in­her­it­ing from StreamRequestHandler. It gets in­stan­ti­ated for each con­nec­tion and must han­dle it from start to fin­ish. We then cre­ate the TCP server by in­stan­ti­at­ing TCPServer and ask it to con­tin­u­ously serve in­com­ing con­nections.

You can test this very sim­ple server us­ing the nc program:

$ nc localhost 2020
Votre est IP est 127.0.0.1. Au revoir

For each con­nec­tion, the TCP server will in­stan­ti­ate MyRequestHandler and call, among oth­ers, the handle method. When this method re­turns, the con­nec­tion is closed. To build our TCP server as we de­fined it, we re­write our pro­gram as follows:

#!/usr/bin/env python
import os
import time
import commands
from SocketServer import TCPServer, StreamRequestHandler

class MyRequestHandler(StreamRequestHandler):
    def handle(self):
        self.wfile.write("Que puis-je faire pour vous ?\n")
        ligne = self.rfile.readline().strip()
        if hasattr(self, "do_%s" % ligne):
            getattr(self, "do_%s" % ligne)()
        else:
            self.wfile.write("Commande inconnue.")
        self.wfile.write("\n")

    def do_uptime(self):
        status, uptime = commands.getstatusoutput("uptime")
        self.wfile.write(uptime)

    def do_df(self):
        status, df = commands.getstatusoutput("df -h")
        self.wfile.write(df)

TCPServer.allow_reuse_address = True
tcpd = TCPServer(("", 2020), MyRequestHandler)
tcpd.serve_forever()

We mod­i­fied the MyRequestHandler class. Now, we read a line (thus wait­ing for client in­put), then look for a func­tion match­ing the re­quested com­mand and ex­e­cute it. If the func­tion does not ex­ist, we re­turn a generic mes­sage. Adding new com­mands is there­fore straight­forward.

This sim­plis­tic ex­am­ple does not han­dle er­rors, and if prob­lems oc­cur within a MyRequestHandler in­stance, the con­nec­tion is ter­mi­nated, but the server re­mains fully functional.

Our server has a ma­jor flaw: it can only han­dle one con­nec­tion at a time. Try the fol­low­ing ex­per­i­ment: open two ter­mi­nals. Con­nect to the server from the first one, do not type any com­mand, then try to con­nect from the sec­ond. As long as the con­nec­tion is not closed on the first ter­mi­nal, you will not see the ban­ner ap­pear on the sec­ond! In­deed, your server han­dles only one si­mul­ta­ne­ous con­nection.

There are sev­eral so­lu­tions for han­dling mul­ti­ple si­mul­ta­ne­ous con­nec­tions. Among them are us­ing threads or mul­ti­ple pro­cesses. In the first case, a thread is cre­ated to han­dle each new con­nec­tion. The sec­ond case is iden­ti­cal, but pro­cesses are used in­stead of threads.

Python pro­vides two classes de­rived from TCPServer: ThreadingTCPServer and ForkingTCPServer. In­stead of in­stan­ti­at­ing TCPServer, in­stan­ti­ate one or the other of these classes. You can then han­dle mul­ti­ple con­nec­tions si­mul­ta­ne­ously. Try it!

We now know how to write both clients and servers us­ing the var­i­ous mod­ules in Python’s stan­dard li­brary. We now go one level lower and use the same prim­i­tives as in C. This can be use­ful to write a server with­out threads or mul­ti­ple pro­cesses while still han­dling mul­ti­ple si­mul­ta­ne­ous con­nec­tions. Or for fun.

Using low-level primitives#

When writ­ing clients or servers in C, it of­ten comes down to a few ba­sic prim­i­tives, namely the BSD sock­ets API. There are higher-level frame­works that make the task eas­ier, es­pe­cially to in­te­grate a server or client into a graph­i­cal tool­kit (such as Qt or GTK), but most of the time, func­tions like socket(), bind(), listen(), connect(), accept(), etc. are used. A de­scrip­tion can be found in the ip man page (sec­tion 7), as well as in the udp and tcp man pages (also in section 7).

To write a TCP client (for UDP, it is slightly dif­fer­ent, but that does not mat­ter much for our ex­am­ple), you need to:

  • open a socket with socket();
  • es­tab­lish a con­nec­tion to a re­mote server with connect();
  • send data with send() or re­ceive data with recv();
  • fin­ish with close().

For a TCP server, you use the fol­low­ing functions:

  • open a socket with socket();
  • al­lo­cate a lo­cal port with bind();
  • lis­ten on this port with listen();
  • ac­cept an in­com­ing con­nec­tion with accept(), which pro­vides a new socket spe­cific to the client;
  • send data with send() or re­ceive data with recv();
  • ter­mi­nate the con­nec­tion with the client us­ing close();
  • close the server socket with close().

Writing a client#

Let us re­visit our sim­plis­tic ex­am­ples. How could we write an HTTP client in C us­ing the prim­i­tives de­scribed above? The code be­low rep­re­sents a sim­plis­tic client. But this is not a C spe­cial is­sue. We only want to show a pro­to­type ap­plication.

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

int main() {
  int s;
  struct sockaddr_in serv;
  char request[] = "GET / HTTP/1.0\r\nHost:www.gnulinuxmag.com\r\n\r\n";
  char buffer[200];
  serv.sin_family = AF_INET;
  serv.sin_port = htons(80);
  serv.sin_addr.s_addr = inet_addr("91.121.166.153");
  if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
    perror("socket(): ");
    exit(1);
  }
  if (connect(s, (struct sockaddr *)&serv,
              (socklen_t)sizeof(struct sockaddr_in)) < 0) {
    perror("connect(): ");
    exit(1);
  }
  if (send(s, request, sizeof(request), 0) < 0) {
    perror("send(): ");
    exit(1);
  }
  if (recv(s, buffer, sizeof(buffer), 0) < 0) {
    perror("recv(): ");
    exit(1);
  }
  buffer[199] = '\0';
  printf("Nous avons reçu ceci:\n\n%s\n\n", buffer);
  return 0;
}

Let us com­ment on our code. We be­gin with the socket() call. Its pur­pose is to cre­ate a com­mu­ni­ca­tion end­point for send­ing and re­ceiv­ing data. We must spec­ify the do­main (an IPv4 socket here with PF_​INET), the type (SOCK_​STREAM, which in prac­tice means we want re­li­able com­mu­ni­ca­tion—and for IPv4, the sys­tem un­der­stands this as TCP), and a pro­to­col num­ber set to 0 since there is noth­ing left to choose.

Our socket is not con­nected to any­thing. If we tried to send data on it, the sys­tem would re­ject it. We con­nect it to a re­mote server us­ing the connect() prim­i­tive. If you are not fa­mil­iar with C, the cor­re­spond­ing line may seem cryp­tic. connect() takes as its first pa­ram­e­ter the socket we just ob­tained. The sec­ond pa­ram­e­ter is a struc­ture in­di­cat­ing which server we want to con­tact. We filled this struc­ture in the first lines of the pro­gram, spec­i­fy­ing the fam­ily (IPv4), the tar­get port in net­work byte or­der (which is the re­verse of x86), hence the use of htons() for the con­ver­sion, and the host ad­dress. We hard­coded the IP in the pro­gram. Oth­er­wise, we would ad­di­tion­ally need to per­form a name res­o­lu­tion. Things get a bit more com­pli­cated. The func­tion ex­pects a struct sockaddr type, and we pro­vide a struct sockaddr_in (which cor­re­sponds to the type for IPv4). The com­plex no­ta­tion is there­fore there to per­form the con­ver­sion so the com­piler is sat­is­fied. If we wanted to com­mu­ni­cate over IPv6, we would have used struct sockaddr_in6.

If ev­ery­thing goes well, we are now con­nected. We can then use the send() and recv() func­tions to send or re­ceive data. We send a hand­crafted HTTP re­quest and re­ceive the first 200 bytes in re­sponse. Let us com­pile and test:

$ gcc web1.c -o web1
$ ./web1
Nous avons reçu ceci:

HTTP/1.1 200 OK
Date: Sat, 10 May 2008 12:46:10 GMT
Server: Apache/2.2.6 (Debian) DAV/2 PHP/5.2.3-1+b1
X-Powered-By: PHP/5.2.3-1+b1
Last-Modified: Tue, 29 Apr 2008 10:01:15 GMT
Cache-Control: mu

Our buf­fer is not large enough to re­ceive more than the be­gin­ning of the head­ers. But there you go, our HTTP client in C is done. But wait, are we not in a Python spe­cial is­sue? Let us con­vert it to Python. We need to use the func­tions from the socket mod­ule. The re­sult has a cer­tain fam­ily re­sem­blance to the C version.

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
s.connect(("213.186.60.195", 80))
s.send("GET / HTTP/1.0\r\nHost:www.gnulinuxmag.com\r\n\r\n")
buf = s.recv(200)
print("Nous avons recu ceci:\n\n%s\n\n" % buf)

We start by cre­at­ing our socket. The pa­ram­e­ters are the same as in C. Since the con­stants be­long to the socket mod­ule, we must look them up in that names­pace. Then, we con­nect our socket to the re­mote server. Here, no com­pli­cated con­ver­sions: we pass a tu­ple con­tain­ing the IP and port. The connect prim­i­tive from the socket mod­ule does a bit more than call­ing the C socket function.

Then, we send our re­quest and re­ceive the re­sponse, which we dis­play. Let us test:

$ python ./web4.py
Nous avons recu ceci:

HTTP/1.1 200 OK
Date: Sat, 10 May 2008 13:03:51 GMT
Server: Apache/2.2.6 (Debian) DAV/2 PHP/5.2.3-1+b1
X-Powered-By: PHP/5.2.3-1+b1
Last-Modified: Tue, 29 Apr 2008 10:01:15 GMT
Cache-Control: mus

The re­sult is iden­ti­cal, but we saved a lot of lines of code com­pared to the C ver­sion. We no longer have to man­u­ally han­dle er­rors, as the ex­cep­tion mech­a­nism does it for us.

Note that this Python pro­gram does not quite cor­re­spond to the ex­am­ple with urllib2 at the be­gin­ning of the ar­ti­cle. If the server had re­sponded with a re­di­rect, the pro­gram us­ing urllib2 would have au­to­mat­i­cally fol­lowed it, while our pro­gram would not.

Writing a server#

Let us now move on to writ­ing a server. Like our first Python server ex­am­ple, it will ac­cept the con­nec­tion, dis­play the client’s IP, and close the con­nec­tion. We will write our pro­gram di­rectly in Python, which makes things easier:

import socket

s = socket.socket()
s.bind(("", 2020))
s.listen(5)
while True:
    conn, client = s.accept()
    conn.send("Votre IP est %s\n" % client[0])
    conn.close()

As with the client, we cre­ate a socket. No need to re­quest an IPv4 socket of type SOCK_​STREAM: these are the de­fault pa­ram­e­ters of the socket func­tion. We then in­di­cate which port we will use. We spec­ify that we will lis­ten on all avail­able in­ter­faces on port 2020. In C, we would have had to fill a struc­ture and use INADDR_​ANY in­stead of the empty string. If we wanted to re­strict the server to the lo­cal ma­chine, we would have used 127.0.0.1 as the first el­e­ment of the tu­ple passed to bind. Then, we tell the op­er­at­ing sys­tem to start lis­ten­ing on our socket. We tell the op­er­at­ing sys­tem to queue up to 5 con­nec­tions for us. Thus, if our pro­gram is too busy to ac­cept new con­nec­tions, the op­er­at­ing sys­tem will put them on hold. Be­yond 5, the sys­tem re­fuses new con­nections.

We then en­ter an in­fi­nite loop to process con­nec­tions from each client that may come our way. We use the accept func­tion. In C, it re­turns a socket for com­mu­ni­cat­ing with the client and passes the client’s ad­dress by mod­i­fy­ing the last two ar­gu­ments. In Python, the func­tion re­turns both as a tuple.

Note

For a server, there is one socket per client. If TCP is used, each socket cor­re­sponds to an IP and a port. Two sock­ets are there­fore needed to make a con­nec­tion: the server’s (server IP, port on which the server lis­tens) and the client’s (client IP, port on which the client awaits re­sponses, usu­ally a ran­dom high port). Be­fore us­ing a socket, it must be as­so­ci­ated with this pair. This is the pur­pose of the bind func­tion. This func­tion is au­to­mat­i­cally called by connect in the case of a client if it was omit­ted, which ex­plains its ab­sence in our HTTP client ex­am­ple. Other types of sock­ets ex­ist. For ex­am­ple, a named Unix socket uses a file­name rather than an IP/port pair. To com­mu­ni­cate on the same socket, two pro­cesses must bind to the same path. Read­ers in­ter­ested in socket in­ter­nals can re­fer to the man pages for socket, ip, tcp, udp, raw, and unix in section 7.

Our server is more frag­ile than the one pre­sented ear­lier. If an er­ror oc­curs in the loop, the server ter­mi­nates. This was not the case when us­ing the TCPServer class.

Fi­nally, our server only pro­cesses one con­nec­tion at a time. This is not an is­sue for our sim­plis­tic ex­am­ple, but if you are wait­ing for user in­put, no­body else can con­nect in the mean­time! Let us mod­ify our pro­gram to ac­cept user input.

#!/usr/bin/env python
import os
import commands
import socket

s = socket.socket()
s.bind(("", 2021))
s.listen(5)
while True:
    conn, client = s.accept()
    conn.send("Que puis-je faire pour vous ?\n")
    ordre = conn.recv(30).strip()
    if ordre == "uptime":
        status, uptime = commands.getstatusoutput("uptime")
        conn.send("%s\n" % uptime)
    elif ordre == "df":
        status, df = commands.getstatusoutput("df -h")
        conn.send("%s\n" % df)
    else:
        conn.send("Je n'ai rien compris.\n")
    conn.close()

The be­gin­ning is iden­ti­cal to the pre­vi­ous ver­sion. We then send a prompt ask­ing the user to en­ter an ac­tion. We then en­counter an im­por­tant func­tional dif­fer­ence with the ver­sion us­ing higher-level prim­i­tives: we read at most 30 char­ac­ters. If we wanted to read com­plete lines prop­erly, we would have to han­dle the split­ting and re­assem­bly our­selves, while Python pro­vided a ded­i­cated class for this pur­pose. For our ex­am­ple, it does not mat­ter much. The rest is straight­forward.

You can re­peat the test: open two ter­mi­nals, con­nect to the server from one, then con­nect from the other. The sec­ond ar­rival will not get the prompt un­til the first has fin­ished the con­nec­tion. In the pre­vi­ous case, we solved the prob­lem by sub­sti­tut­ing TCPServer with a de­rived class han­dling one process per con­nec­tion. Here, such a ma­nip­u­la­tion is not as sim­ple: we will have to man­age this as­pect our­selves. We will use the fork prim­i­tive avail­able in the os mod­ule. There are other so­lu­tions, in­clud­ing the threading module.

#!/usr/bin/env python
import os
import commands
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
s.bind(("0.0.0.0", 2022))
s.listen(5)
while True:
    conn, client = s.accept()
    if os.fork():
        # Nous sommes dans le pere
        conn.close()
    else:
        # Nous sommes dans le fils
        conn.send("Que puis-je faire pour vous ?\n")
        ordre = conn.recv(30).strip()
        if ordre == "uptime":
            status, uptime = commands.getstatusoutput("uptime")
            conn.send("%s\n" % uptime)
        elif ordre == "df":
            status, df = commands.getstatusoutput("df -h")
            conn.send("%s\n" % df)
        else:
            conn.send("Je n'ai rien compris.\n")
        conn.close()
        os._exit(0)

Af­ter ac­cept­ing a con­nec­tion, we use the fork func­tion. This func­tion du­pli­cates the cur­rent process, and both copies run si­mul­ta­ne­ously with a very slight dif­fer­ence be­tween them. In the child, fork re­turns 0, while in the par­ent, it re­turns the child’s PID. This dif­fer­ence al­lows us to ex­e­cute dif­fer­ent code in each case.

If we are in the par­ent, we close the socket and go back to the loop to ac­cept a new client. Clos­ing the socket does not ter­mi­nate the con­nec­tion, as the socket is still open in the child. The con­nec­tion will only end when this socket is closed.

In the child, we do our work as be­fore. At the end, we ter­mi­nate the process with the _exit func­tion from the os mod­ule. This func­tion avoids all the side ef­fects of the reg­u­lar exit func­tion that could af­fect the child.

You can now re­peat the test: we can han­dle mul­ti­ple con­nec­tions at once. Our ex­am­ple is not per­fect: we leave zom­bie pro­cesses ev­ery­where. The par­ent should ac­knowl­edge each child’s ter­mi­na­tion with waitpid. The sim­plest way to do this is to set up a sig­nal han­dler. But that is well be­yond the scope of this article.

What is it good for?#

Look­ing at these ex­am­ples, one might won­der why we would use low-level prim­i­tives. The code is longer, less Pythonic, and has ad­di­tional lim­i­ta­tions and fragili­ties. So many draw­backs. Is there any ben­e­fit be­yond the ed­u­ca­tional as­pect? Low-level prim­i­tives be­come nec­es­sary when try­ing to go be­yond what the high-level prim­i­tives of­fer. For ex­am­ple, the TCP server pro­vided by Python only al­lows us­ing a sin­gle process (but han­dling only one con­nec­tion at a time in that case), us­ing threads, or us­ing mul­ti­ple pro­cesses. Imag­ine that none of these ap­proaches suits us. We want to han­dle mul­ti­ple con­nec­tions, but with a sin­gle thread and a sin­gle process. Yes, it is pos­si­ble, but we must use the low-level primitives!

There are sev­eral meth­ods to achieve such a re­sult. One of them is to use the select mod­ule con­tain­ing the select func­tion, which mon­i­tors a list of file de­scrip­tors wait­ing for an event to oc­cur on one of them. It is a fairly low-level func­tion with ca­pa­bil­i­ties equiv­a­lent to the select() func­tion in C, made slightly more Pythonic by han­dling ex­ceptions.

How shall we pro­ceed? We will main­tain the list of con­nect­ing clients our­selves and mon­i­tor their ac­tiv­ity with the select func­tion. If a client sends data, the select func­tion tells us and we per­form the nec­es­sary ac­tions. This way, we will not be blocked if a client be­comes in­ac­tive. The select func­tion takes a list of de­scrip­tors (sock­ets are de­scrip­tors) and re­turns when any of these de­scrip­tors be­comes avail­able (for a read de­scrip­tor, this means new data is avail­able to read).

#!/usr/bin/env python
import os
import commands
import socket
import select

s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("", 2022))
s.listen(5)
conns = [s]
while True:
    rlist, wlist, xlist = select.select(conns, [], [])
    for r in rlist:
        if r == s:
            conn, client = s.accept()
            conn.send("Que puis-je faire pour vous ?\n")
            conns.append(conn)
        else:
            ordre = r.recv(30).strip()
            if ordre == "uptime":
                status, uptime = commands.getstatusoutput("uptime")
                r.send("%s\n" % uptime)
            elif ordre == "df":
                status, df = commands.getstatusoutput("df -h")
                r.send("%s\n" % df)
            else:
                conn.send("Je n'ai rien compris.\n")
            r.close()
            conns.remove(r)

Let us ex­am­ine the code start­ing from the select call. Select ac­cepts three sets of de­scrip­tors as pa­ram­e­ters: those on which we are wait­ing for data, those for which we are wait­ing for write avail­abil­ity, and those for which we are wait­ing for events such as con­nec­tion clo­sure or an er­ror. We only pop­u­late the first type of de­scrip­tors. The select call will there­fore block un­til one of the de­scrip­tors in the conns list has data avail­able. When we ac­cept a new con­nec­tion, we add it to this list. When we close a con­nec­tion, we re­move it. We also add our server socket to this list. If a new con­nec­tion can be ac­cepted, select will also re­turn. The func­tion re­turns three sets: the set of de­scrip­tors ready for read­ing, those ready for writ­ing, and those in an ex­cep­tional con­di­tion. Each of these sets is a sub­set of the pa­ram­e­ters. Thus, in rlist, we get a sub­set of conns.

In rlist, we have the sub­set of de­scrip­tors from conns from which we can read at least one byte with­out block­ing. If one of the mem­bers is the server socket, it means we can ac­cept a new con­nec­tion. For the oth­ers, we per­form the usual actions.

Test this pro­gram to ver­ify it works as ex­pected. You can in­deed open mul­ti­ple con­nec­tions to the server while only a sin­gle process is run­ning. How­ever, this pro­gram has sev­eral im­por­tant flaws:

  • We use select only for read­ing; the send calls can block (which should not hap­pen on a socket).
  • We count on re­ceiv­ing the com­plete com­mand on the first at­tempt. With TCP, this is not guar­an­teed. If the client sent the com­mand char­ac­ter by char­ac­ter, it would not work. We would there­fore need to re­assem­ble the com­mand be­fore tak­ing action.
  • If the com­mand to ex­e­cute takes a long time, our pro­gram is blocked dur­ing its ex­e­cu­tion. Since there is only one thread and one process, this means ev­ery­thing is blocked. We would need to use select on the stan­dard out­put of these pro­grams, which would com­pli­cate the code.

Fix­ing these flaws re­quires sev­eral ad­di­tional lines of code. We will not study them here. This ex­am­ple al­lows us to in­tro­duce the next sec­tion of this ar­ti­cle. There ex­ist frame­works in Python that al­low writ­ing asyn­chro­nous code (i.e., code that avoids all block­ing calls). Among them is Twisted!

Using Twisted#

Twisted de­fines it­self as the en­gine for your In­ter­net. It has two es­sen­tial char­ac­teristics:

  • It is a very com­pre­hen­sive net­work frame­work for build­ing both clients and servers us­ing a wide va­ri­ety of pro­to­cols: HTTP, SMTP, IMAP, POP, SSH, DNS, FTP, IRC…
  • It is asyn­chro­nous and event-driven, mean­ing ac­tions are as­so­ci­ated with events that oc­cur (such as the ar­rival of a packet).

Sup­port for many pro­to­cols is a strength of Twisted. Not only does it sup­port more pro­to­cols than Python’s stan­dard li­brary, but these pro­to­cols are most of­ten avail­able as both client and server. More­over, these are typ­i­cally com­plete im­ple­men­ta­tions. For ex­am­ple, the SSH server sup­ports SFTP and the SSH client can use an agent. This is an im­por­tant point for Twisted de­vel­op­ers: the goal is to pro­vide pro­duc­tion-grade im­ple­men­tations.

The asyn­chro­nous as­pect of Twisted can be dis­con­cert­ing. When an ac­tion is block­ing, you pro­vide a func­tion that runs when the ac­tion com­pletes. So when you want to read a net­work packet, you pro­vide a func­tion that re­ceives the packet as a pa­ram­e­ter. The Twisted en­gine calls this func­tion when the packet ar­rives. In the mean­time, it can han­dle other tasks, such as ac­cept­ing a new con­nec­tion. The ad­van­tages of such an ap­proach are numerous:

  • There is no need to man­age in­ter-process com­mu­ni­ca­tion or thread con­cur­rency issues.
  • Scal­a­bil­ity is more pre­dictable; for ex­am­ple, han­dling 200,000 si­mul­ta­ne­ous con­nec­tions is not a prob­lem (aside from mem­ory and CPU con­sump­tion), whereas it would be prob­lem­atic if each con­nec­tion had to be man­aged by a sep­a­rate process.
  • Per­for­mance can be bet­ter. A piece of code only yields con­trol when it per­forms a block­ing ac­tion (of­ten net­work-re­lated). The rest of the time, it uses 100% of the CPU. Keep in mind that Twisted is a net­work frame­work. This ef­fi­ciency ap­plies to net­work ap­pli­ca­tions. If you were to write a data­base with Twisted, the mech­a­nisms of­fered would not be as ef­fi­cient. For ex­am­ple, Twisted does not han­dle asyn­chro­nous file reads from disk.

If you are not con­vinced of Twisted’s per­for­mance, here are two open-source prod­ucts that use it and de­liver re­mark­able per­for­mance thanks to it:

  • Flu­mo­tion, a stream­ing server.
  • ZenOSS, a mon­i­tor­ing soft­ware, com­pet­ing with Na­gios or Zabbix.

Us­ing Twisted also brings some drawbacks:

  • Asyn­chro­nous pro­gram­ming can be dif­fi­cult to grasp. In Twisted, it trans­lates into the need to de­fine many func­tions to re­act to events. The code there­fore can­not be read as a sin­gle block. Still, it is pos­si­ble to write more con­ven­tional-look­ing code us­ing Python 2.5’s new it­erators.
  • There is only one process, only one thread. Multi-core ma­chines are there­fore not ex­ploited. But these cores are avail­able for other pro­cesses (the data­base, for ex­am­ple). Twisted does of­fer the abil­ity to man­age mul­ti­ple pro­cesses or threads, but in that case, some ad­van­tages of asyn­chro­nous pro­gram­ming are lost.
  • Asyn­chro­nous pro­gram­ming makes trace­backs harder to un­der­stand, which can make de­bug­ging ap­pli­ca­tions quite painful: when an ex­cep­tion oc­curs, if it hap­pens dur­ing the pro­cess­ing of a de­ferred call, the con­text cor­re­spond­ing to that de­ferred call is no longer available.
  • The doc­u­men­ta­tion sorely lacks ex­am­ples. It is some­times quite dif­fi­cult to know how to as­sem­ble the var­i­ous build­ing blocks to achieve the de­sired re­sult. The doc­u­men­ta­tion con­tains some very good tu­to­ri­als, but be­yond that, you will of­ten need to read the source code.
  • Twisted is very com­pre­hen­sive and aims to adapt to most sit­u­a­tions. It there­fore uses very high-level con­cepts that you do not nec­es­sar­ily see in other projects: in­ter­faces, com­po­nents, fac­to­ries, etc. It has many fea­tures that al­low, for ex­am­ple, man­ag­ing au­then­ti­ca­tion us­ing ei­ther a flat file or a data­base. The same au­then­ti­ca­tion en­gine can be used to ob­tain an en­tity rep­re­sent­ing a user for a web server (as­so­ci­ated with a ses­sion) or a set of mail­boxes. Twisted uses the con­cepts men­tioned above to prop­erly nest things to­gether, but these can be dif­fi­cult to approach.

It is im­pos­si­ble to cover Twisted en­tirely in a sin­gle ar­ti­cle. A pre­vi­ous is­sue of GNU/Linux Mag­a­zine al­ready con­tained an ex­cel­lent in­tro­duc­tion to Twisted. We try a dif­fer­ent ap­proach by pre­sent­ing sim­ple ex­am­ples cor­re­spond­ing to what we pre­vi­ously did with­out Twisted. Some ex­am­ples will be en­hanced to demon­strate Twisted’s power.

Asynchronous programming with Twisted#

For read­ers who may not have read the pre­vi­ously men­tioned in­tro­duc­tion to Twisted, we will cover here the ba­sics of asyn­chro­nous pro­gram­ming with Twisted.

We have al­ready writ­ten an asyn­chro­nous ap­pli­ca­tion us­ing the select sys­tem call. Twisted does not ex­pose such a method. When your pro­gram per­forms a po­ten­tially block­ing ac­tion, you call a func­tion that im­me­di­ately re­turns a Deferred ob­ject. You can then at­tach ac­tions to this ob­ject. These ac­tions run ei­ther when the ex­pected data is avail­able or when an er­ror oc­curs. The Twisted en­gine, called a re­ac­tor, main­tains a list of all these ob­jects and ex­e­cutes the at­tached meth­ods as events occur.

To bet­ter un­der­stand this prin­ci­ple, let us re­visit our very first ex­am­ple. We re­pro­duce it here:

import urllib2

lm = urllib2.urlopen('http://www.gnulinuxmag.com/')
print(lm.read())

How do we do it with Twisted? There is a block­ing ac­tion, which is read­ing the web page. Twisted sim­pli­fies our life by of­fer­ing an equiv­a­lent of urllib2, but adapted to its en­gine. Here is a sim­i­lar pro­gram us­ing Twisted:

from twisted.web.client import getPage
from twisted.internet import reactor

def printContents(content):
    print(content)
    reactor.stop()

deferred = getPage("http://www.gnulinuxmag.com/")
deferred.addCallback(printContent)
reactor.run()

It is a bit longer. Let us see step by step how it works. Twisted has a li­brary for writ­ing a web client. We im­port the method we need. We also im­port the Twisted en­gine: the re­ac­tor. It has two meth­ods: run and stop. The first method starts the re­ac­tor. Start­ing the re­ac­tor ini­ti­ates an in­fi­nite loop that man­ages the var­i­ous events. This loop is sim­i­lar to what Qt and GTK use. There is even a re­ac­tor com­pat­i­ble with the GTK loop. The loop stops when you call stop. Do not try to restart it with run. Call­ing stop gen­er­ally ter­mi­nates the pro­gram, and Twisted can no longer be used af­terwards.

Let us ig­nore the printContent func­tion for now. We call the getPage func­tion with the page we want to re­trieve as a pa­ram­e­ter. This ac­tion is not im­me­di­ate. A net­work socket must be opened, the do­main name re­solved, an HTTP re­quest made, and then the re­sponse awaited. The getPage func­tion does all of this, just as urlopen did. But the lat­ter did not re­turn un­til it had con­tacted the server. With Twisted, func­tions that per­form po­ten­tially block­ing ac­tions re­turn im­me­di­ately. They re­turn a Deferred ob­ject that you can at­tach ac­tions to.

In our ex­am­ple, we cap­ture this Deferred ob­ject in a vari­able and at­tach a call­back to it—a func­tion that will be called when the data be­comes avail­able. This at­tach­ment is done us­ing the addCallback method of the Deferred ob­ject. It takes as its first pa­ram­e­ter a func­tion, here printContent. Ad­di­tional pa­ram­e­ters can be given, which will be passed to printContent.

When the web page con­tent is avail­able, the re­ac­tor will call the printContent func­tion with the page con­tent as the first pa­ram­e­ter. In gen­eral, the func­tion de­clared as a call­back re­ceives the ex­pected data as its first pa­ram­e­ter. It can take ad­di­tional pa­ram­e­ters that must then be pro­vided when call­ing addCallback.

Our printContent func­tion dis­plays the ob­tained page and then stops the re­ac­tor. This last ac­tion al­lows the pro­gram to terminate.

Let us look again at the very first pro­gram in this ar­ti­cle. What hap­pens if the re­quested URL does not ex­ist? For ex­am­ple, if the re­quested page re­turns a 404 er­ror? We get an ex­cep­tion. We can han­dle it as shown in this example:

import urllib2

try:
    lm = urllib2.urlopen("http://www.gnulinuxmag.com/")
    print(lm.read())
except:
    print("Impossible de récupérer la page")

What hap­pens with Twisted? Noth­ing hap­pens—the pro­gram does not re­turn, but does not dis­play any­thing ei­ther. If you in­ter­rupt it with Ctrl+C, it tells you:

Unhandled error in Deferred:
Traceback (most recent call last):
Failure: twisted.web.error.Error: 404 Not Found

Twisted only calls the printContent func­tion on suc­cess. In case of an er­ror, you must pro­vide an­other func­tion to han­dle it. We then par­tially lose Python’s ex­cep­tion mech­a­nism. It is still pos­si­ble to raise ex­cep­tions, but to catch them, you must at­tach func­tions de­signed for that pur­pose. Let us mod­ify our example.

from twisted.web.client import getPage
from twisted.internet import reactor

def printContent(content):
    print(content)
    reactor.stop()

def handleError(error):
    print("Une erreur est survenue :")
    print(error)
    reactor.stop()

deferred = getPage("http://www.gnulinuxmag.com/ooo")
deferred.addCallback(printContent)
deferred.addErrback(handleError)
reactor.run()

We de­clared a new func­tion that takes the er­ror as a pa­ram­e­ter (it is ac­tu­ally an in­stance of the Failure class). We at­tach this func­tion to the Deferred ob­ject ob­tained ear­lier us­ing the addErrback method.

To con­clude this in­tro­duc­tion to asyn­chro­nous pro­gram­ming with Twisted, one last sub­tlety: you can chain callbacks:

from twisted.web.client import getPage
from twisted.internet import reactor

def printContent(content):
    print(content[:100])
    return 18

def addTwo(number):
    return number + 2

def printResult(number):
    print("J'ai obtenu %d" % number)

def finalCallback(dummy):
    print("Fini!")
    reactor.stop()

deferred = getPage("http://www.gnulinuxmag.com/")
deferred.addCallback(printContent)
deferred.addCallback(addTwo)
deferred.addCallback(printResult)
deferred.addBoth(finalCallback)
reactor.run()

Our pro­gram will re­trieve the spec­i­fied page. On suc­cess, it will dis­play the first char­ac­ters of the page. Still on suc­cess, the addTwo func­tion will be called. It takes as a pa­ram­e­ter what­ever printContent re­turned. On suc­cess, the printResult func­tion will be called. Fi­nally, in all cases, the finalCallback func­tion is called, which stops the re­ac­tor. Run­ning this pro­gram, we get:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1
J'ai obtenu 20
Fini!

It is thus pos­si­ble to chain call­backs. Each call­back uses the re­sult of the pre­vi­ous call­back. A call­back can also re­turn a Deferred. In that case, the next call­back will only be called when the con­tent as­so­ci­ated with that Deferred is avail­able, re­ceiv­ing said con­tent as its pa­ram­e­ter. You can also raise an ex­cep­tion or re­turn a Failure ob­ject. In that case, the next er­ror call­back in the chain takes over.

Fig­ure 1 shows the ex­pected be­hav­ior for the fol­low­ing pseudo code. The red ar­row is fol­lowed in case of er­ror (ex­cep­tion or Failure ob­ject re­turned by the func­tion), while the green ar­row is fol­lowed in all other cases.

deferred.addCallbacks(callback1, errback1)
deferred.addCallbacks(callback2, errback2)
deferred.addCallbacks(callback3, errback3)

The addCallbacks func­tion reg­is­ters both a call­back and an er­rback si­mul­ta­ne­ously. This is slightly dif­fer­ent from the fol­low­ing code:

deferred.addCallback(callback1)
deferred.addErrback(errback1)

In­deed, in the first case, errback1 will only han­dle er­rors from getPage, while in the sec­ond case, it will also han­dle er­rors that may have oc­curred in callback1. To sum­ma­rize: if a call­back or er­rback raises an ex­cep­tion or re­turns a Failure ob­ject, the next er­rback in the chain will han­dle it. Oth­er­wise, the next call­back in the chain takes over.

Callbacks and errbacks handling in Twisted with potential crossovers between
the two paths
Callback chaining

Writing a server with Twisted#

First, we write a server equiv­a­lent to the one we wrote to il­lus­trate the use of select. To do this, we will use the twistd pro­gram pro­vided with Twisted. It will han­dle a num­ber of things for us, in­clud­ing re­ac­tor man­age­ment (start­ing and stop­ping), dae­mo­niza­tion, and logging.

Discovering twistd#

The twistd pro­gram can take dif­fer­ent types of files as in­put. We will fo­cus on the op­tion that lets us pro­vide a Python file to eval­u­ate. In that case, we must de­fine an application vari­able that is an in­stance of twisted.​applications.​service.​Application. The min­i­mal ex­am­ple is then the fol­low­ing (which we call exemple.py):

from twisted.application import service

application = service.Application("exemple")

Let us try launch­ing it to see what happens:

$ twistd -n -o -y exemple.py
2008-06-14 18:54:32+0200 [-] Log opened.
2008-06-14 18:54:32+0200 [-] twistd 8.1.0 (/usr/bin/python 2.5.2) starting up
2008-06-14 18:54:32+0200 [-] reactor class: <class 'twisted.internet.selectreactor.SelectReactor'>

If we press Ctrl+C, the ap­pli­ca­tion shuts down cleanly:

2008-06-14 18:55:11+0200 [-] Received SIGINT, shutting down.
2008-06-14 18:55:11+0200 [-] Main loop terminated.
2008-06-14 18:55:11+0200 [-] Server Shut Down.

We passed the -n, -o, and -y pa­ram­e­ters to twistd. The first in­di­cates that we do not want it to dae­mo­nize, so we keep con­trol. Oth­er­wise, it would run in the back­ground and logs would be writ­ten to sys­log. The sec­ond tells it not to save the ap­pli­ca­tion state on exit. In­deed, Twisted pro­vides fea­tures for tak­ing a snap­shot of an ap­pli­ca­tion to restart it from the same point later. Fi­nally, the last pa­ram­e­ter in­di­cates that we will pro­vide a Python file.

Dur­ing startup, twistd in­di­cates which re­ac­tor it is us­ing (select-based in our case). Oth­ers ex­ist. Con­sult the man page to learn how to choose a dif­fer­ent reactor.

First steps toward a functional server#

Our ap­pli­ca­tion is cur­rently not very in­ter­est­ing: we cre­ated an ap­pli­ca­tion that does noth­ing. We will at­tach a ser­vice to it. This ser­vice will lis­ten on a port and, for each con­nect­ing client, ask a spe­cial en­tity (Factory) to cre­ate a Protocol in­stance that will com­mu­ni­cate with the client. When mul­ti­ple clients are con­nected, they each have their own Protocol in­stance, but there is only one Factory in­stance. When there are no clients, the Factory in­stance is still there. In prac­tice, this in­stance stores per­sis­tent in­for­ma­tion (which clients can po­ten­tially modify).

Let us see what this looks like. Be­fore writ­ing our com­mand server, let us write a server that says hello, echoes ev­ery­thing we send it, and keeps track of the num­ber of clients. Here is the code!

from twisted.application import internet, service
from twisted.internet.protocol import Protocol, Factory

class SimpleProtocol(Protocol):
    def connectionMade(self):
        self.factory.numClients += 1
        self.transport.write("Bonjour ! Il y a %d clients.\n" % self.factory.numClients)

    def connectionLost(self, reason):
        self.factory.numClients -= 1

    def dataReceived(self, data):
        self.transport.write(data)

class SimpleFactory(Factory):
    protocol = SimpleProtocol

    def __init__(self):
        self.numClients = 0

application = service.Application("exemple")
myfactory = SimpleFactory()
myservice = internet.TCPServer(2022, myfactory)
myservice.setServiceParent(application)

We de­fine two classes. The first in­her­its from the Protocol class and com­mu­ni­cates di­rectly with the client. The connectionMade method is called when the con­nec­tion is es­tab­lished, connectionLost when it is sev­ered, and dataReceived when data is re­ceived. An in­stance of this class con­tains a ref­er­ence to the Factory in­stance it de­pends on. This al­lows us to store the client count. It also con­tains a ref­er­ence to a trans­port ob­ject that lets us ac­tu­ally send data to the client. This ob­ject has a write method that we use to echo the re­ceived data back to the client.

The sec­ond class is the fac­tory that pro­vides Protocol in­stances for each client. It must con­tain a buildProtocol method re­turn­ing a Protocol in­stance ca­pa­ble of com­mu­ni­cat­ing with the client. We in­herit this method from Factory. It will in­stan­ti­ate the protocol at­tribute that we ini­tial­ized to SimpleProtocol. This method is also re­spon­si­ble for link­ing the Factory in­stance to the cre­ated Protocol instance.

Fi­nally, af­ter defin­ing our ap­pli­ca­tion, we in­stan­ti­ate our fac­tory and cre­ate a ser­vice that will use this fac­tory to ob­tain a Protocol in­stance ca­pa­ble of com­mu­ni­cat­ing with the client. We at­tach this ser­vice to our ap­pli­ca­tion so that it starts when the ap­pli­ca­tion launches.

Let us see how it works!

$ twistd -noy exemple2.py
2008-06-14 19:15:12+0200 [-] Log opened.
2008-06-14 19:15:12+0200 [-] twistd 8.1.0 (/usr/bin/python 2.5.2) starting up
2008-06-14 19:15:12+0200 [-] reactor class: <class 'twisted.internet.selectreactor.SelectReactor'>
2008-06-14 19:15:12+0200 [-] __builtin__.SimpleFactory starting on 2022
2008-06-14 19:15:12+0200 [-] Starting factory <__builtin__.SimpleFactory instance at 0x18cc2d8>

And from an­other ter­mi­nal, we can connect.

$ nc localhost 2022
Bonjour ! Il y a 1 clients.
Allo ?
Allo ?

If you con­nect from other ter­mi­nals, the client count will in­crease. All is well.

A note on Twisted concepts#

Twisted uses many ob­ject-ori­ented con­cepts that take get­ting used to. Read­ing the in­tro­duc­tory ar­ti­cles on Twisted is im­por­tant for un­der­stand­ing each prin­ci­ple. If you are fa­mil­iar with the con­cept of “De­sign Pat­terns,” you rec­og­nized the fac­tory pat­tern in use. With­out go­ing into de­tails, the motto of such an ap­proach is max­i­mum de­cou­pling: the ser­vice does not know how to cre­ate the pro­to­col di­rectly; it calls an in­ter­me­di­ary that does. Not link­ing the ser­vice to the pro­to­col may seem un­nat­u­ral at first, but it pro­vides flex­i­bil­ity later: this in­ter­me­di­ary can, for ex­am­ple, de­cide not to pro­vide the pro­to­col im­me­di­ately or to pro­vide a dif­fer­ent pro­to­col de­pend­ing on the client (e.g., if there are too many clients).

A sec­ond con­cept to get used to is the use of in­ter­faces. We saw above that we had to pro­vide the ser­vice with a Factory in­stance hav­ing a buildProtocol method. This is ac­tu­ally in­ac­cu­rate. In gen­eral, Twisted does not re­quire ob­jects to be in­stances of spe­cific classes. It asks you to pro­vide an in­stance that re­spects an in­ter­face, i.e., that has a cer­tain num­ber of meth­ods and at­tributes. To cre­ate a ser­vice, you must there­fore pro­vide an ob­ject that re­spects the IProtocolFactory in­ter­face. You can use or in­herit from the Factory class, but it is not manda­tory. Some in­ter­faces have no canon­i­cal class. In that case, to cre­ate a class that re­spects such an in­ter­face, you must write each method de­clared in the in­ter­face and in­di­cate that the class im­ple­ments the de­sired in­ter­face (with implements). For an in­tro­duc­tion to in­ter­faces, read­ers are in­vited to re­fer to the ar­ti­cle on the Zope Com­po­nent Ar­chi­tec­ture in this is­sue. Note, how­ever, that Twisted only uses part of this ar­chi­tec­ture and reim­ple­ments cer­tain parts (the adapter reg­istry, for ex­am­ple). There­fore, it is also ad­vis­able to read the in­tro­duc­tion to in­ter­faces and adapters in the Twisted doc­u­men­ta­tion. It is es­sen­tial read­ing for go­ing fur­ther with Twisted.

The Twisted API doc­u­men­ta­tion in­di­cates for each ob­ject which in­ter­face it re­spects and, for each in­ter­face, which classes im­plement it.

This no­tion of in­ter­faces also con­trib­utes to de­cou­pling be­tween ob­jects. By re­quest­ing an ob­ject that re­spects a given in­ter­face, you al­low the im­ple­men­ta­tion to be changed eas­ily. This is sim­i­lar to us­ing ab­stract classes (classes that must be in­her­ited from, with all de­clared meth­ods im­ple­mented).

First implementation of the command server#

We will now tackle a first im­ple­men­ta­tion of the com­mand server. If we adapt the code we used to demon­strate the select func­tion, we get the fol­low­ing code:

import commands
from twisted.protocols.basic import LineReceiver
from twisted.application import internet, service
from twisted.internet.protocol import Protocol, Factory

class SimpleProtocol(LineReceiver):
    delimiter = "\n"

    def connectionMade(self):
        self.transport.write("Que puis-je faire pour vous ?\n")

    def lineReceived(self, line):
        ordre = line.strip()
        if ordre == "uptime":
            status, uptime = commands.getstatusoutput("uptime")
            self.transport.write("%s\n" % uptime)
        elif ordre == "df":
            status, df = commands.getstatusoutput("df -h")
            self.transport.write("%s\n" % df)
        else:
            self.transport.write("Je n'ai rien compris.\n")
        self.transport.loseConnection()

application = service.Application("exemple")
myfactory = Factory()
myfactory.protocol = SimpleProtocol
myservice = internet.TCPServer(2022, myfactory)
myservice.setServiceParent(application)

To shorten the code, we used a small short­cut that avoids declar­ing a class in­her­it­ing from Factory. We cre­ate a Factory in­stance and mod­ify the protocol at­tribute.

An­other nov­elty: we do not in­herit from Protocol, but from one of its spe­cial­iza­tions, LineReceiver, which of­fers the abil­ity to re­ceive com­mands line by line. The dataReceived method of Protocol did not guar­an­tee that we would re­ceive ex­actly one line each time. Here, no such prob­lem. This class pro­vides the lineReceived method, which is called with the re­ceived line as a pa­ram­e­ter. The rest of the code is sim­i­lar to what we did when pre­sent­ing select.

The ap­pli­ca­tion ap­pears to be­have ex­actly the same way. But it also has the same flaws. Us­ing the commands mod­ule is block­ing. If one of the com­mands blocks (for ex­am­ple, if df takes a long time to re­turn due to NFS mounts), our ap­pli­ca­tion freezes: it is im­pos­si­ble to open new con­nec­tions or use ex­ist­ing ones.

We did not present a ver­sion cor­rect­ing this flaw when we were us­ing select, be­cause it was quite te­dious. With Twisted, it is much sim­pler! Twisted pro­vides a set of classes to help us achieve this. First, you can com­mu­ni­cate with an ex­ter­nal process just as with a net­work client. We could there­fore cre­ate a pro­to­col that reads ev­ery­thing the process sends us and for­wards it to the client. But there is some­thing even sim­pler! Twisted al­ready has a func­tion for this. Let us see:

from twisted.protocols.basic import LineReceiver
from twisted.application import internet, service
from twisted.internet.protocol import Protocol, Factory
from twisted.internet import utils

class SimpleProtocol(LineReceiver):
    delimiter = "\n"

    def connectionMade(self):
        self.transport.write("Que puis-je faire pour vous ?\n")

    def lineReceived(self, line):
        ordre = line.strip()
        if ordre == "uptime":
            output = utils.getProcessOutput("/usr/bin/uptime")
            output.addCallbacks(self.sendOutput, self.sendError)
        elif ordre == "df":
            output = utils.getProcessOutput("/bin/df", ("-h",))
            output.addCallbacks(self.sendOutput, self.sendError)
        else:
            self.transport.write("Je n'ai rien compris.\n")
            self.transport.loseConnection()

    def sendOutput(self, output):
        self.transport.write(output)
        self.transport.loseConnection()

    def sendError(self, reason):
        self.transport.write("Uh?\n")
        self.transport.loseConnection()

application = service.Application("exemple")
myfactory = Factory()
myfactory.protocol = SimpleProtocol
myservice = internet.TCPServer(2022, myfactory)
myservice.setServiceParent(application)

We used the getProcessOutput func­tion. This func­tion al­lows us to in­tro­duce a Deferred ob­ject in a real ap­pli­ca­tion for the first time. It re­turns a Deferred ob­ject since the process ex­e­cu­tion is block­ing. While the process runs, the re­ac­tor per­forms other tasks such as ac­cept­ing new clients or pro­cess­ing ex­ist­ing ones.

For this Deferred to have an ef­fect, we at­tach call­backs. The sendOutput method on suc­cess and the sendError method on fail­ure (for ex­am­ple, if the com­mand does not ex­ist). When re­sults are avail­able, the sendOutput method will be called with the com­mand’s out­put as an ar­gu­ment. We write the re­sult and close the con­nection.

Note that in the lineReceived method, we do not close the con­nec­tion at the end of the method (ex­cept for un­rec­og­nized com­mands). If we did, the con­nec­tion would close be­fore the re­sult could be dis­played (and this in all cases; the code is never pre­empted!).

We have built our first fully asyn­chro­nous ap­pli­ca­tion with Twisted. That was not so dif­fi­cult, was it?

What about security?#

Let us say we want to put our ap­pli­ca­tion on the In­ter­net so we can check disk space re­motely. Cur­rently, all in­for­ma­tion trav­els in clear text and there is no au­then­ti­ca­tion. We will use SSL for pro­tec­tion. We also take the op­por­tu­nity to ask for a pass­word be­fore ac­cept­ing a com­mand. Here is the new code:

from twisted.protocols.basic import LineReceiver
from twisted.application import internet, service
from twisted.internet.protocol import Protocol, Factory
from twisted.internet import utils, ssl

class SimpleProtocol(LineReceiver):
    delimiter = "\n"

    def connectionMade(self):
        self.transport.write("Le mot de passe ?\n")
        self.authenticated = 0

    def lineReceived(self, line):
        if self.authenticated == 0:
            password = line.strip()
            if password != "toto":
                self.transport.loseConnection()
            else:
                self.transport.write("Que puis-je faire pour vous ?\n")
                self.authenticated = 1
            return
        ordre = line.strip()
        if ordre == "uptime":
            output = utils.getProcessOutput("/usr/bin/uptime")
            output.addCallbacks(self.sendOutput, self.sendError)
        elif ordre == "df":
            output = utils.getProcessOutput("/bin/df", ("-h",))
            output.addCallbacks(self.sendOutput, self.sendError)
        else:
            self.transport.write("Je n'ai rien compris.\n")
            self.transport.loseConnection()

    def sendOutput(self, output):
        self.transport.write(output)
        self.transport.loseConnection()

    def sendError(self, reason):
        self.transport.write("Uh?\n")
        self.transport.loseConnection()

application = service.Application("exemple")
myfactory = Factory()
myfactory.protocol = SimpleProtocol
myservice = internet.TCPServer(2022, myfactory)
myservice.setServiceParent(application)
sslcontext = ssl.DefaultOpenSSLContextFactory("/tmp/privkey.pem", "/tmp/cacert.pem")
sslservice = internet.SSLServer(2023, myfactory, sslcontext)
sslservice.setServiceParent(application)

We added some code to ask for a pass­word at the be­gin­ning of the lineReceived method. Then, we added a ser­vice to our ap­pli­ca­tion: these are the last three lines. To test, you need to cre­ate the key pair and self-signed cer­tifi­cate as follows:

$ cd /tmp
$ openssl req -new -x509 -keyout privkey.pem -out cacert.pem -days 1000

And to test:

$ openssl s_client -host localhost -port 2023

Sim­ple, isn’t it? We are be­gin­ning to see the ad­van­tages of de­cou­pling. We kept the same pro­to­col and added a new ser­vice wrap­ping our pro­to­col in SSL. The TCP ser­vice is still available.

Adding a web interface#

Why not add a web in­ter­face? Twisted al­lows you to eas­ily cre­ate a web server. Here is what we need to add to our code:

from twisted.web import server, resource

class SimpleWeb(resource.Resource):
    isLeaf = True

    def render_GET(self, request):
        ordre = request.path[1:]
        if ordre == "":
            return """<html><ul>
<li><a href="/df">df</a></li>
<li><a href="/uptime">uptime</a></li>
</ul></html>"""
        if ordre == "uptime":
            output = utils.getProcessOutput("/usr/bin/uptime")
            output.addCallback(self.sendOutput, request)
        elif ordre == "df":
            output = utils.getProcessOutput("/bin/df", ("-h",))
            output.addCallback(self.sendOutput, request)
        else:
            return """<html>Je n'ai rien compris</html>"""
        return server.NOT_DONE_YET

    def sendOutput(self, data, request):
        request.write("<html>Voici le resultat : <pre>%s</pre></html>" % data)
        request.finish()

website = server.Site(SimpleWeb())
webservice = internet.TCPServer(2024, website)
webservice.setServiceParent(application)

The code is not a model of beauty. We should have pre­pared a sep­a­rate class to share com­mand han­dling be­tween the web part and the com­mand-line part. But we keep this sim­pler ap­proach. We in­stan­ti­ate a re­source, turn it into a ser­vice, and at­tach it to our ap­pli­ca­tion. This re­source has a render_​GET method called each time a GET re­quest is made. This method takes a Request in­stance as a pa­ram­e­ter, which we use to re­trieve the re­quested path and also to respond.

There are then two ways to re­spond. If the re­sponse is ready, you can re­turn a string that will be trans­mit­ted to the client for dis­play in their browser. But when the re­sponse is not ready, we must re­turn a spe­cial value (server.​NOT_​DONE_​YET). In that case, you can call the write method of the Request ob­ject as many times as you want and fin­ish with the finish method call. This is what we do in our callback.

Note

This ap­proach is not con­sis­tent with Twisted’s phi­los­o­phy. It would have been more nat­u­ral to re­turn a Deferred ob­ject. Like Rome, Twisted was not built in a day. This is a flaw that is cor­rected in the new gen­er­a­tion of web servers for Twisted.

To test, point your browser to http://localhost:2024.

Twisted’s web frame­work is rel­a­tively com­plete. You can as­so­ciate classes with cer­tain URLs to mod­u­lar­ize the web site con­struc­tion. But it re­mains fairly ba­sic. For ex­am­ple, it does not in­clude a tem­plate en­gine for build­ing web pages more cleanly. A new ver­sion is un­der de­vel­op­ment (web2), but do not use it yet. Its strengths will grad­u­ally be in­te­grated into the cur­rent mod­ule. In the mean­time, use Nevow, a com­plete web frame­work built on top of Twisted.

SSH access?#

Twisted pro­vides a mod­ule called conch for writ­ing SSH clients or servers. Let us see how to add SSH ac­cess. SSH is a com­plex pro­to­col of­fer­ing many pos­si­bil­i­ties: var­i­ous au­then­ti­ca­tion meth­ods, the abil­ity to open mul­ti­ple chan­nels, open­ing a ter­mi­nal, re­mote com­mand ex­e­cu­tion, etc. Twisted tries to sim­plify its use while al­low­ing all sub­tleties to be ex­ploited. One might have hoped for some­thing as sim­ple as lis­ten­ing on a TCP port, but un­for­tu­nately such a level of ab­strac­tion does not ex­ist. Twisted pro­vides a mod­ule that hides most of the com­plex­ity for sim­ple use. But we can­not eas­ily re­use our ex­ist­ing SimpleProtocol class, be­cause the ex­pected pro­to­col must re­spect the ITerminalProtocol in­ter­face. There does not ap­pear to be an adapter be­tween the two in­ter­faces at the mo­ment. We will once again du­pli­cate code for the sake of sim­plic­ity. Here is the SSH part of our ap­plication:

from twisted.conch.manhole_ssh import ConchFactory, TerminalRealm
from twisted.cred import portal, checkers
from twisted.conch import avatar, interfaces, recvline
from twisted.conch.ssh import session
from twisted.conch.insults import insults
from zope.interface import implements

class MySSHProtocol(recvline.HistoricRecvLine):
    def lineReceived(self, line):
        ordre = line.strip()
        if ordre == "uptime":
            output = utils.getProcessOutput("/usr/bin/uptime")
            output.addCallbacks(self.sendOutput, self.sendError)
        elif ordre == "df":
            output = utils.getProcessOutput("/bin/df", ("-h",))
            output.addCallbacks(self.sendOutput, self.sendError)
        elif ordre == "quit":
            self.terminal.loseConnection()
        else:
            self.terminal.write("Je n'ai rien compris.")
            self.terminal.nextLine()

    def sendOutput(self, output):
        self.terminal.write(output)

    def sendError(self, reason):
        self.terminal.write("Uh?\n")

tr = TerminalRealm()
tr.chainedProtocolFactory.protocolFactory = MySSHProtocol
p = portal.Portal(tr)
users = {"admin": "toto", "root": "hello"}
p.registerChecker(checkers.InMemoryUsernamePasswordDatabaseDontUse(**users))
sshfactory = ConchFactory(p)
sshservice = internet.TCPServer(2025, sshfactory)
sshservice.setServiceParent(application)

Let us start from the end. Twisted pro­vides an en­tire in­fra­struc­ture for au­then­ti­ca­tion, de­signed to be as generic as pos­si­ble. This in­fra­struc­ture is not lim­ited to SSH but can be reused for a web server, for ex­am­ple. The Twisted doc­u­men­ta­tion pro­vides a com­plete in­tro­duc­tion to this sys­tem. The cen­ter­piece is the Portal ob­ject. It in­ter­acts with the pro­to­col to ob­tain the user’s iden­ti­fi­ca­tion el­e­ments. It then sub­mits them to var­i­ous ver­i­fi­ca­tion mech­a­nisms. Here, we in­stan­ti­ated only one: pass­word ver­i­fi­ca­tion with an in-mem­ory data­base stored in a dic­tio­nary. Once the user is au­then­ti­cated, the Portal ob­ject con­tacts a Realm ob­ject that must re­turn an avatar—a rep­re­sen­ta­tion of the user adapted to the re­quested con­text (here an SSH user).

We there­fore in­stan­ti­ate var­i­ous ob­jects to build the Factory ob­ject that will be used to con­struct the ser­vice. Dur­ing in­stan­ti­a­tion, we find an op­por­tu­nity to at­tach our pro­to­col. It is nearly iden­ti­cal to the pro­to­col used for plain­text and SSL con­nec­tions. The trans­port is re­placed by a ter­mi­nal that re­quires some ad­di­tional pre­cau­tions. Since the ter­mi­nal is cleared be­fore clos­ing the con­nec­tion, we ac­cept mul­ti­ple com­mands and add a quit command.

Conch is con­sid­ered one of Twisted’s most com­plex mod­ules. It is cur­rently un­der­go­ing sig­nif­i­cant refac­tor­ing to make it more con­sis­tent with other mod­ules. Still, we man­aged to write our SSH server in a few lines. We now have a se­cure ap­pli­ca­tion sup­port­ing pass­word au­then­ti­ca­tion that we can ex­tend to also sup­port key-based au­then­tication.

Conclusion#

Python is a first-class lan­guage for writ­ing any type of net­work ap­pli­ca­tion. Its stan­dard li­brary cov­ers both high-level and low-level prim­i­tives, close to those of­fered in C. Many third-party con­tri­bu­tions en­rich ex­ist­ing ca­pa­bil­i­ties by sup­port­ing ad­di­tional protocols.

Com­bined with the pro­duc­tiv­ity and read­abil­ity that char­ac­ter­ize Python, these ca­pa­bil­i­ties let you de­sign ap­pli­ca­tions that di­rectly or in­di­rectly lever­age the network.

For am­bi­tious projects, take a look at Twisted. If you adapt to the asyn­chro­nous par­a­digm, it will re­ward you with its ver­sa­til­ity and ro­bustness.