Network Protocols

Overview

                      This chapter describes Python's socket protocol support, and the networking modules built on top of the socket module. This includes client handlers for most popular Internet protocols, as well as several
frameworks that can be used to implement Internet servers. For the low-level examples in this chapter I'll use two protocols for illustration; the Internet Time Protocol, and the Hypertext Transfer Protocol.

Internet Time Protocol

              The Internet Time Protocol (RFC 868, Postel and Harrenstien 1983) is a simple protocol which allows a network client to get the current time from a server. Since this protocol is relatively light weight, many (but far from all) Unix systems provide this service. It's also about as easy to implement as a network protocol can possibly be. The server simply waits for a connection request, and immediately returns the current time as a 4-byte integer, containing the number of seconds since January 1st, 1900.
In fact, the protocol is so simple that I can include the entire specification:
Network Working Group J. Postel - ISI
Request for Comments: 868 K. Harrenstien - SRI
May 1983
Time Protocol
This RFC specifies a standard for the ARPA Internet community. Hosts on
the ARPA Internet that choose to implement a Time Protocol are expected
to adopt and implement this standard.
This protocol provides a site-independent, machine readable date and
time. The Time service sends back to the originating source the time in
seconds since midnight on January first 1900.
One motivation arises from the fact that not all systems have a
date/time clock, and all are subject to occasional human or machine
error. The use of time-servers makes it possible to quickly confirm or
correct a system's idea of the time, by making a brief poll of several
independent sites on the network.
This protocol may be used either above the Transmission Control Protocol
(TCP) or above the User Datagram Protocol (UDP).
When used via TCP the time service works as follows:
S: Listen on port 37 (45 octal).
U: Connect to port 37.
S: Send the time as a 32 bit binary number.
U: Receive the time.
U: Close the connection.
S: Close the connection.
The server listens for a connection on port 37. When the connection
is established, the server returns a 32-bit time value and closes the
connection. If the server is unable to determine the time at its
site, it should either refuse the connection or close it without
sending anything.


When used via UDP the time service works as follows:
S: Listen on port 37 (45 octal).
U: Send an empty datagram to port 37.
S: Receive the empty datagram.
S: Send a datagram containing the time as a 32 bit binary number.
U: Receive the time datagram.
The server listens for a datagram on port 37. When a datagram
arrives, the server returns a datagram containing the 32-bit time
value. If the server is unable to determine the time at its site, it
should discard the arriving datagram and make no reply.
The Time
The time is the number of seconds since 00:00 (midnight) 1 January 1900
GMT, such that the time 1 is 12:00:01 am on 1 January 1900 GMT; this
base will serve until the year 2036.
For example:
the time 2,208,988,800 corresponds to 00:00 1 Jan 1970 GMT,
2,398,291,200 corresponds to 00:00 1 Jan 1976 GMT,
2,524,521,600 corresponds to 00:00 1 Jan 1980 GMT,
2,629,584,000 corresponds to 00:00 1 May 1983 GMT,
and -1,297,728,000 corresponds to 00:00 17 Nov 1858 GMT.
Python Standard Library: Network Protocols 7-4
Hypertext Transfer Protocol
The Hypertext Transfer Protocol (HTTP, Fielding et al., RFC 2616) is something completely different.
The most recent specification (version 1.1), is over 100 pages.
However, in its simplest form, this protocol is very straightforward. To fetch a document, the client
connects to the server, and sends a request like:
GET /hello.txt HTTP/1.0
Host: hostname
User-Agent: name
[optional request body]
In return, the server returns a response like this:
HTTP/1.0 200 OK
Content-Type: text/plain
Content-Length: 7
Hello
Both the request and response headers usually contains more fields, but the Host field in the request
header is the only one that must always be present.
The header lines are separated by "\r\n", and the header must be followed by an empty line, even if
there is no body (this applies to both the request and the response).
The rest of the HTTP specification deals with stuff like content negotiation, cache mechanics, persistent
connections, and much more. For the full story, see Hypertext Transfer Protocol — HTTP/1.1.
Python Standard Library: Network Protocols 7-5
The socket module
This module implements an interface to the socket communication layer. You can create both client
and server sockets using this module.
Let's start with a client example. The following client connects to a time protocol server, reads the 4-
byte response, and converts it to a time value.
Example: Using the socket module to implement a time client
# File:socket-example-1.py
import socket
import struct, time
# server
HOST = "www.python.org"
PORT = 37
# reference time (in seconds since 1900-01-01 00:00:00)
TIME1970 = 2208988800L # 1970-01-01 00:00:00
# connect to server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
# read 4 bytes, and convert to time value
t = s.recv(4)
t = struct.unpack("!I", t)[0]
t = int(t - TIME1970)
s.close()
# print results
print "server time is", time.ctime(t)
print "local clock is", int(time.time()) - t, "seconds off"
server time is Sat Oct 09 16:42:36 1999
local clock is 8 seconds off
The socket factory function creates a new socket of the given type (in this case, an Internet stream
socket, also known as a TCP socket). The connect method attempts to connect this socket to the given
server. Once that has succeeded, the recv method is used to read data.
Creating a server socket is done in a similar fashion. But instead of connecting to a server, you bind
the socket to a port on the local machine, tell it to listen for incoming connection requests, and
process each request as fast as possible.
The following example creates a time server, bound to port 8037 on the local machine (port numbers
up to 1024 are reserved for system services, and you have to have root privileges to use them to
implement services on a Unix system):
Python Standard Library: Network Protocols 7-6
Example: Using the socket module to implement a time server
# File:socket-example-2.py
import socket
import struct, time
# user-accessible port
PORT = 8037
# reference time
TIME1970 = 2208988800L
# establish server
service = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
service.bind(("", PORT))
service.listen(1)
print "listening on port", PORT
while 1:
# serve forever
channel, info = service.accept()
print "connection from", info
t = int(time.time()) + TIME1970
t = struct.pack("!I", t)
channel.send(t) # send timestamp
channel.close() # disconnect
listening on port 8037
connection from ('127.0.0.1', 1469)
connection from ('127.0.0.1', 1470)
...
The listen call tells the socket that we're willing to accept incoming connections. The argument gives
the size of the connection queue (which holds connection requests that our program hasn't gotten
around to processing yet). Finally, the accept loop returns the current time to any client bold enough
to connect.
Note that the accept function returns a new socket object, which is directly connected to the client.
The original socket is only used to establish the connection; all further traffic goes via the new socket.
To test this server, we can use the following generalized version of our first example:
Python Standard Library: Network Protocols 7-7
Example: A time protocol client
# File:timeclient.py
import socket
import struct, sys, time
# default server
host = "localhost"
port = 8037
# reference time (in seconds since 1900-01-01 00:00:00)
TIME1970 = 2208988800L # 1970-01-01 00:00:00
def gettime(host, port):
# fetch time buffer from stream server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
t = s.recv(4)
s.close()
t = struct.unpack("!I", t)[0]
return int(t - TIME1970)
if __name__ == "__main__":
# command line utility
if sys.argv[1:]:
host = sys.argv[1]
if sys.argv[2:]:
port = int(sys.argv[2])
else:
port = 37 # default for public servers
t = gettime(host, port)
print "server time is", time.ctime(t)
print "local clock is", int(time.time()) - t, "seconds off"
server time is Sat Oct 09 16:58:50 1999
local clock is 0 seconds off
This sample script can also be used as a module; to get the current time from a server, import the
timeclient module, and call the gettime function.
This far, we've used stream (or TCP) sockets. The time protocol specification also mentions UDP
sockets, or datagrams. Stream sockets work pretty much like a phone line; you'll know if someone at
the remote end picks up the receiver, and you'll notice when she hangs up. In contrast, sending
datagrams is more like shouting into a dark room. There might be someone there, but you won't know
unless she replies.
You don't need to connect to send data over a datagram socket. Instead, you use the sendto method,
which takes both the data and the address of the receiver. To read incoming datagrams, use the
recvfrom method.

Example: Using the socket module to implement a datagram time client
# File:socket-example-4.py
import socket
import struct, time
# server
HOST = "localhost"
PORT = 8037
# reference time (in seconds since 1900-01-01 00:00:00)
TIME1970 = 2208988800L # 1970-01-01 00:00:00
# connect to server
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# send empty packet
s.sendto("", (HOST, PORT))
# read 4 bytes from server, and convert to time value
t, server = s.recvfrom(4)
t = struct.unpack("!I", t)[0]
t = int(t - TIME1970)
s.close()
print "server time is", time.ctime(t)
print "local clock is", int(time.time()) - t, "seconds off"
server time is Sat Oct 09 16:42:36 1999
local clock is 8 seconds off
Note that recvfrom returns two values; the actual data, and the address of the sender. Use the latter if
you need to reply.
Here's the corresponding server:
Example: Using the socket module to implement a datagram time server
# File:socket-example-5.py
import socket
import struct, time
# user-accessible port
PORT = 8037
# reference time
TIME1970 = 2208988800L
# establish server
service = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
service.bind(("", PORT))

print "listening on port", PORT
while 1:
# serve forever
data, client = service.recvfrom(0)
print "connection from", client
t = int(time.time()) + TIME1970
t = struct.pack("!I", t)
service.sendto(t, client) # send timestamp
listening on port 8037
connection from ('127.0.0.1', 1469)
connection from ('127.0.0.1', 1470)
...
The main difference is that the server uses bind to assign a known port number to the socket, and
sends data back to the client address returned by recvfrom.
Python Standard Library: Network Protocols 7-10
The select module
This module allows you to check for incoming data on one or more sockets, pipes, or other compatible
stream objects.
You can pass one or more sockets to the select function, to wait for them to become readable, writable,
or signal an error.
• A socket becomes ready for reading when 1) someone connects after a call to listen (which
means that accept won't block), or 2) data arrives from the remote end, or 3) the socket is
closed or reset (in this case, recv will return an empty string).
• A socket becomes ready for writing when 1) the connection is established after a non-blocking
call to connect, or 2) data can be written to the socket.
• A socket signals an error condition when the connection fails after a non-blocking call to
connect.
Example: Using the select module to wait for data arriving over sockets
# File:select-example-1.py
import select
import socket
import time
PORT = 8037
TIME1970 = 2208988800L
service = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
service.bind(("", PORT))
service.listen(1)
print "listening on port", PORT
while 1:
is_readable = [service]
is_writable = []
is_error = []
r, w, e = select.select(is_readable, is_writable, is_error, 1.0)
if r:
channel, info = service.accept()
print "connection from", info
t = int(time.time()) + TIME1970
t = chr(t>>24&255) + chr(t>>16&255) + chr(t>>8&255) + chr(t&255)
channel.send(t) # send timestamp
channel.close() # disconnect
else:
print "still waiting"
Python Standard Library: Network Protocols 7-11
listening on port 8037
still waiting
still waiting
connection from ('127.0.0.1', 1469)
still waiting
connection from ('127.0.0.1', 1470)
...
In this example, we wait for the listening socket to become readable, which indicates that a connection
request has arrived. We treat the channel socket as usual, since it's not very likely that writing the four
bytes will fill the network buffers. If you need to send larger amounts of data to the client, you should
add it to the is_writable list at the top of the loop, and write only when select tells you to.
If you set the socket in non-blocking mode (by calling the setblocking method), you can use select
also to wait for a socket to become connected. But the asyncore module (see the next section)
provides a powerful framework which handles all this for you, so I won't go into further details here.

The asyncore module

This module provides a "reactive" socket implementation. Instead of creating socket objects, and
calling methods on them to do things, this module lets you write code that is called when something
can be done. To implement an asynchronous socket handler, subclass the dispatcher class, and
override one or more of the following methods:
• handle_connect is called when a connection is successfully established.
• handle_expt is called when a connection fails.
• handle_accept is called when a connection request is made to a listening socket. The callback
should call the accept method to get the client socket.
• handle_read is called when there is data waiting to be read from the socket. The callback
should call the recv method to get the data.
• handle_write is called when data can be written to the socket. Use the send method to write
data.
• handle_close is called when the socket is closed or reset.
• handle_error(type, value, traceback) is called if a Python error occurs in any of the other
callbacks. The default implementation prints an abbreviated traceback to sys.stdout.
The first example shows a time client, similar to the one for the socket module:
Example: Using the asyncore module to get the time from a time server
# File:asyncore-example-1.py
import asyncore
import socket, time
# reference time (in seconds since 1900-01-01 00:00:00)
TIME1970 = 2208988800L # 1970-01-01 00:00:00
class TimeRequest(asyncore.dispatcher):
# time requestor (as defined in RFC 868)
def __init__(self, host, port=37):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((host, port))
def writable(self):
return 0 # don't have anything to write
def handle_connect(self):
pass # connection succeeded
def handle_expt(self):
self.close() # connection failed, shutdown
def handle_read(self):
# get local time
here = int(time.time()) + TIME1970
# get and unpack server time
s = self.recv(4)
there = ord(s[3]) + (ord(s[2])<<8) + (ord(s[1])<<16) + (ord(s[0])<<24L)
self.adjust_time(int(here - there))
self.handle_close() # we don't expect more data
def handle_close(self):
self.close()
def adjust_time(self, delta):
# override this method!
print "time difference is", delta
#
# try it out
request = TimeRequest("www.python.org")
asyncore.loop()
log: adding channel <TimeRequest at 8cbe90>
time difference is 28
log: closing channel 192:<TimeRequest connected at 8cbe90>
If you don't want the log messages, override the log method in your dispatcher subclass.
Here's the corresponding time server. Note that it uses two dispatcher subclasses, one for the
listening socket, and one for the client channel.
Example: Using the asyncore module to implement a time server
# File:asyncore-example-2.py
import asyncore
import socket, time
# reference time
TIME1970 = 2208988800L
class TimeChannel(asyncore.dispatcher):
def handle_write(self):
t = int(time.time()) + TIME1970
t = chr(t>>24&255) + chr(t>>16&255) + chr(t>>8&255) + chr(t&255)
self.send(t)
self.close()
Python Standard Library: Network Protocols 7-14
class TimeServer(asyncore.dispatcher):
def __init__(self, port=37):
self.port = port
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.bind(("", port))
self.listen(5)
print "listening on port", self.port
def handle_accept(self):
channel, addr = self.accept()
TimeChannel(channel)
server = TimeServer(8037)
asyncore.loop()
log: adding channel <TimeServer at 8cb940>
listening on port 8037
log: adding channel <TimeChannel at 8b2fd0>
log: closing channel 52:<TimeChannel connected at 8b2fd0>
In addition to the plain dispatcher, this module also includes a dispatcher_with_send class. This
class allows you send larger amounts of data, without clogging up the network transport buffers.
The following module defines an AsyncHTTP class based on the dispatcher_with_send class.
When you create an instance of this class, it issues an HTTP GET request, and sends the incoming data
to a "consumer" target object.
Example: Using the asyncore module to do HTTP requests
# File:SimpleAsyncHTTP.py
import asyncore
import string, socket
import StringIO
import mimetools, urlparse
class AsyncHTTP(asyncore.dispatcher_with_send):
# HTTP requestor
def __init__(self, uri, consumer):
asyncore.dispatcher_with_send.__init__(self)
self.uri = uri
self.consumer = consumer
# turn the uri into a valid request
scheme, host, path, params, query, fragment = urlparse.urlparse(uri)
assert scheme == "http", "only supports HTTP requests"
try:
host, port = string.split(host, ":", 1)
port = int(port)
except (TypeError, ValueError):
port = 80 # default port
if not path:
path = "/"
if params:
path = path + ";" + params
if query:
path = path + "?" + query
self.request = "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n" % (path, host)
self.host = host
self.port = port
self.status = None
self.header = None
self.data = ""
# get things going!
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((host, port))
def handle_connect(self):
# connection succeeded
self.send(self.request)
def handle_expt(self):
# connection failed; notify consumer (status is None)
self.close()
try:
http_header = self.consumer.http_header
except AttributeError:
pass
else:
http_header(self)
def handle_read(self):
data = self.recv(2048)
if not self.header:
self.data = self.data + data
try:
i = string.index(self.data, "\r\n\r\n")
except ValueError:
return # continue
else:
# parse header
fp = StringIO.StringIO(self.data[:i+4])
# status line is "HTTP/version status message"
status = fp.readline()
self.status = string.split(status, " ", 2)
# followed by a rfc822-style message header
self.header = mimetools.Message(fp)
# followed by a newline, and the payload (if any)
data = self.data[i+4:]
self.data = ""
# notify consumer (status is non-zero)
try:
http_header = self.consumer.http_header
except AttributeError:
pass
else:
http_header(self)
if not self.connected:
return # channel was closed by consumer
self.consumer.feed(data)
def handle_close(self):
self.consumer.close()
self.close()
And here's a simple script using that class:
Example: Using the SimpleAsyncHTTP class
# File:asyncore-example-3.py
import SimpleAsyncHTTP
import asyncore
class DummyConsumer:
size = 0
def http_header(self, request):
# handle header
if request.status is None:
print "connection failed"
else:
print "status", "=>", request.status
for key, value in request.header.items():
print key, "=", value
def feed(self, data):
# handle incoming data
self.size = self.size + len(data)
def close(self):
# end of data
print self.size, "bytes in body"
#
# try it out
consumer = DummyConsumer()
request = SimpleAsyncHTTP.AsyncHTTP(
"http://www.pythonware.com",
consumer
)
asyncore.loop()
log: adding channel <AsyncHTTP at 8e2850>
status => ['HTTP/1.1', '200', 'OK\015\012']
server = Apache/Unix (Unix)
content-type = text/html
content-length = 3730
...
3730 bytes in body
log: closing channel 156:<AsyncHTTP connected at 8e2850>
Python Standard Library: Network Protocols 7-18
Note that the consumer interface is designed to be compatible with the htmllib and xmllib parsers.
This allows you to parse HTML or XML data on the fly. Note that the http_header method is
optional; if it isn't defined, it's simply ignored.
A problem with the above example is that it doesn't work for redirected resources. The following
example adds an extra consumer layer, which handles the redirection:
Example: Using the SimpleAsyncHTTP class with redirection
# File:asyncore-example-4.py
import SimpleAsyncHTTP
import asyncore
class DummyConsumer:
size = 0
def http_header(self, request):
# handle header
if request.status is None:
print "connection failed"
else:
print "status", "=>", request.status
for key, value in request.header.items():
print key, "=", value
def feed(self, data):
# handle incoming data
self.size = self.size + len(data)
def close(self):
# end of data
print self.size, "bytes in body"
class RedirectingConsumer:
def __init__(self, consumer):
self.consumer = consumer
def http_header(self, request):
# handle header
if request.status is None or\
request.status[1] not in ("301", "302"):
try:
http_header = self.consumer.http_header
except AttributeError:
pass
else:
return http_header(request)
else:
# redirect!
uri = request.header["location"]
print "redirecting to", uri, "..."
request.close()
SimpleAsyncHTTP.AsyncHTTP(uri, self)
Python Standard Library: Network Protocols 7-19
def feed(self, data):
self.consumer.feed(data)
def close(self):
self.consumer.close()
#
# try it out
consumer = RedirectingConsumer(DummyConsumer())
request = SimpleAsyncHTTP.AsyncHTTP(
"http://www.pythonware.com/library",
consumer
)
asyncore.loop()
log: adding channel <AsyncHTTP at 8e64b0>
redirecting to http://www.pythonware.com/library/ ...
log: closing channel 48:<AsyncHTTP connected at 8e64b0>
log: adding channel <AsyncHTTP at 8ea790>
status => ['HTTP/1.1', '200', 'OK\015\012']
server = Apache/Unix (Unix)
content-type = text/html
content-length = 387
...
387 bytes in body
log: closing channel 236:<AsyncHTTP connected at 8ea790>
If the server returns status 301 (permanent redirection) or 302 (temporary redirection), the redirecting
consumer closes the current request, and issues a new one for the new address. All other calls to the
consumer are delegated to the original consumer.
The asynchat module
This module is an extension to asyncore. It provides additional support for line oriented protocols. It
also provides improved buffering support, via the push methods and the "producer" mechanism.
The following example implements a very minimal HTTP responder. It simply returns a HTML
document containing information from HTTP request (the output appears in the browser window):
Example: Using the asynchat module to implement a minimal HTTP server
# File:asynchat-example-1.py
import asyncore, asynchat
import os, socket, string
PORT = 8000
class HTTPChannel(asynchat.async_chat):
def __init__(self, server, sock, addr):
asynchat.async_chat.__init__(self, sock)
self.set_terminator("\r\n")
self.request = None
self.data = ""
self.shutdown = 0
def collect_incoming_data(self, data):
self.data = self.data + data
def found_terminator(self):
if not self.request:
# got the request line
self.request = string.split(self.data, None, 2)
if len(self.request) != 3:
self.shutdown = 1
else:
self.push("HTTP/1.0 200 OK\r\n")
self.push("Content-type: text/html\r\n")
self.push("\r\n")
self.data = self.data + "\r\n"
self.set_terminator("\r\n\r\n") # look for end of headers
else:
# return payload.
self.push("<html><body><pre>\r\n")
self.push(self.data)
self.push("</pre></body></html>\r\n")
self.close_when_done()
Python Standard Library: Network Protocols 7-21
class HTTPServer(asyncore.dispatcher):
def __init__(self, port):
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.bind(("", port))
self.listen(5)
def handle_accept(self):
conn, addr = self.accept()
HTTPChannel(self, conn, addr)
#
# try it out
s = HTTPServer(PORT)
print "serving at port", PORT, "..."
asyncore.loop()
GET / HTTP/1.1
Accept: */*
Accept-Language: en, sv
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; Bruce/1.0)
Host: localhost:8000
Connection: Keep-Alive
The producer interface allows you to "push" objects that are too large to store in memory. asyncore
calls the producer's more method whenever it needs more data. To signal end of file, just return an
empty string.
The following example implements a very simple file-based HTTP server, using a simple
FileProducer class that reads data from a file, a few kilobytes at the time.
Example: Using the asynchat module to implement a simple HTTP server
# File:asynchat-example-2.py
import asyncore, asynchat
import os, socket, string, sys
import StringIO, mimetools
ROOT = "."
PORT = 8000
Python Standard Library: Network Protocols 7-22
class HTTPChannel(asynchat.async_chat):
def __init__(self, server, sock, addr):
asynchat.async_chat.__init__(self, sock)
self.server = server
self.set_terminator("\r\n\r\n")
self.header = None
self.data = ""
self.shutdown = 0
def collect_incoming_data(self, data):
self.data = self.data + data
if len(self.data) > 16384:
# limit the header size to prevent attacks
self.shutdown = 1
def found_terminator(self):
if not self.header:
# parse http header
fp = StringIO.StringIO(self.data)
request = string.split(fp.readline(), None, 2)
if len(request) != 3:
# badly formed request; just shut down
self.shutdown = 1
else:
# parse message header
self.header = mimetools.Message(fp)
self.set_terminator("\r\n")
self.server.handle_request(
self, request[0], request[1], self.header
)
self.close_when_done()
self.data = ""
else:
pass # ignore body data, for now
def pushstatus(self, status, explanation="OK"):
self.push("HTTP/1.0 %d %s\r\n" % (status, explanation))
class FileProducer:
# a producer which reads data from a file object
def __init__(self, file):
self.file = file
def more(self):
if self.file:
data = self.file.read(2048)
if data:
return data
self.file = None
return ""
Python Standard Library: Network Protocols 7-23
class HTTPServer(asyncore.dispatcher):
def __init__(self, port=None, request=None):
if not port:
port = 80
self.port = port
if request:
self.handle_request = request # external request handler
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.bind(("", port))
self.listen(5)
def handle_accept(self):
conn, addr = self.accept()
HTTPChannel(self, conn, addr)
def handle_request(self, channel, method, path, header):
try:
# this is not safe!
while path[:1] == "/":
path = path[1:]
filename = os.path.join(ROOT, path)
print path, "=>", filename
file = open(filename, "r")
except IOError:
channel.pushstatus(404, "Not found")
channel.push("Content-type: text/html\r\n")
channel.push("\r\n")
channel.push("<html><body>File not found.</body></html>\r\n")
else:
channel.pushstatus(200, "OK")
channel.push("Content-type: text/html\r\n")
channel.push("\r\n")
channel.push_with_producer(FileProducer(file))
#
# try it out
s = HTTPServer(PORT)
print "serving at port", PORT
asyncore.loop()
serving at port 8000
log: adding channel <HTTPServer at 8e54d0>
log: adding channel <HTTPChannel at 8e64a0>
samples/sample.htm => .\samples/sample.htm
log: closing channel 96:<HTTPChannel connected at 8e64a0>

No comments: