Server Modules¶
Python provides modules to setup a server quickly.
The SocketServer Module¶
With the SocketServer
module we do not need
to import the socket
module for the server script.
Follow these steps:
from socketserver import StreamRequestHandler
from socketserver import TCPServer
Inheriting from
StreamRequestHandler
define a request handler class. Overridehandle()
.The
handle()
processes incoming requests.Instantiate
TCPServer
with(address, port)
and an instance of the request handler class. This returns a server object.Apply the method
handle_request()
orserve_forever()
to the server object.
To illustrate the above process, we will setup a server to tell the time to its clients. The window running the server is shown below.
$ python clockserver.py
server is listening to 12091
connected at ('127.0.0.1', 49142)
read "What is the time? " from client
writing "Sun Apr 4 18:16:14 2010" to client
The window running the client shows
$ python clockclient.py
client is connected
Sun Apr 4 18:16:14 2010
The code for the client in file clockclient.py
is listed below.
from socket import socket as Socket
from socket import AF_INET, SOCK_STREAM
HOSTNAME = 'localhost' # on same host
PORTNUMBER = 12091 # same port number
BUFFER = 25 # size of the buffer
SERVER_ADDRESS = (HOSTNAME, PORTNUMBER)
CLIENT = Socket(AF_INET, SOCK_STREAM)
CLIENT.connect(SERVER_ADDRESS)
print('client is connected')
QUESTION = 'What is the time?'
DATA = QUESTION + (BUFFER-len(QUESTION))*' '
CLIENT.send(DATA.encode())
DATA = CLIENT.recv(BUFFER)
print(DATA.decode())
CLIENT.close()
The code for the server in the file clockserver.py
has the following skeleton.
from socketserver import StreamRequestHandler
from socketserver import TCPServer
from time import ctime
PORT = 12091
class ServerClock(StreamRequestHandler):
"""
The server tells the clients the time.
"""
def handle(self):
"""
Handler sends time to client.
"""
def main():
"""
Starts the server and serves requests.
"""
The code for the handler defines the handling of a request.
def handle(self):
"""
Handler sends time to client.
"""
print("connected at", self.client_address)
message = self.rfile.read(25)
data = message.decode()
print('read \"' + data + '\" from client')
now = ctime()
print('writing \"' + now + '\" to client')
self.wfile.write(now.encode())
The code for the main function in the server script is below.
def main():
"""
Starts the server and serves requests.
"""
ss = TCPServer(('', PORT), ServerClock)
print('server is listening to', PORT)
try:
print('press ctrl c to stop server')
ss.serve_forever()
except KeyboardInterrupt:
print(' ctrl c pressed, closing server')
ss.socket.close()
if __name__ == "__main__":
main()
To use the attributes rfile
and wfile
in the class
StreamRequestHandler
we have to know the following:
rfile
contains the input stream to read data from the client, for example:data = self.rfile.read(25)
The client must send exactly 25 characters!
wfile
contains the output stream to write data to the client, for example:self.wfile.write(data)
All data are strings of characters!
We end this example, listing some alternative constructions.
Instead of StreamRequestHandler
,
we can use DatagramRequestHandler
.
Instead of TCPServer
, we can use UDPServer
,
if we want UDP instead of TCP protocol.
On Unix (instead of TCPServer
),
we can use UnixStreamServer
or UnixDatagramServer
.
There is the choice between
handle_request()
to handle one single request, orserve_forever()
to handle indefinitely many requests.
With serve_forever()
,
we serve indefinitely many requests,
simultaneously from multiple clients.
A Forking Server¶
Instead of handler threads, we may use processes to handle requests. Threads in Python are not mapped to cores. For a computationally intensive request, we want to spawn a new process.
>>> import os
>>> help(os.fork)
Help on built-in function fork in module posix:
fork(...)
fork() -> pid
Fork a child process.
Return 0 to child process
and PID of child to parent process.
Our first illustration will just be yet another hello world
.
The child process will just print hello
.
import os
def child():
"""
The code executed by the forked process.
"""
print('hello from child', os.getpid())
os._exit(0) # go back to parent loop
The code for the parent()
function is below.
def parent():
"""
Code executed by the forking process.
Type q to quit this process.
"""
while True:
newpid = os.fork()
if newpid == 0:
child()
else:
print('hello from parent',\
os.getpid(), newpid)
if input() == 'q':
break
The running of the script fork.py
is illustrated below.
$ python fork.py
hello from parent 854 855
hello from child 855
In another terminal window:
$ ps -e | grep "Python"
854 ttys000 0:00.03 /Library/Frameworks/Python.framework/Versions/2.6/Resources/Python.app/Contents/MacOS/Python fork.py
855 ttys000 0:00.00 (Python)
895 ttys001 0:00.00 grep Python
Then we type q
in the first terminal window
to quit the parent process.
Consider the following simulation:
- Any number of clients connect from time to time and they ask for the current time. Are we there yet?!
- For every request, the server forks a process. The child process exits when the client stops.
Two advantages of forking processes over threads:
- We have parallelism, as long as there are enough cores.
- Unlike threads, processes can be killed explicitly.
The script clockforkclient.py
starts in the same
manner as as the script clockclient.py
.
print('client is connected')
data = 'What is the time?'
while True:
message = data + (buffer-len(data))*' '
client.send(message.encode())
data = client.recv(buffer).decode()
print(data)
nbr = randint(3, 10)
print('client sleeps for %d seconds' % nbr)
sleep(nbr)
client.close()
The process to handle a client is defined below.
def handle_client(sck):
"""
Handling a client via the socket sck.
"""
print("client is blocked for ten seconds ...")
sleep(10)
print("handling a client ...")
while True:
data = sck.recv(buffer).decode()
if not data:
break
print('received \"' + data + '\" from client')
now = ctime()
print('sending \"' + now + '\" to client')
sck.send(now.encode())
print('closing client socket, exiting child process')
sck.close()
os._exit(0)
With the os
module, we can kill a process,
once with have its process id.
import os
active_processes = []
def kill_processes():
"""
kills handler processes
"""
while len(active_processes) > 0:
pid = active_processes.pop(0)
print('-> killing process %d' % pid)
os.system('kill -9 %d' % pid)
The main()
in the server is the defined below.
def main():
"""
Listen for connecting clients.
"""
try:
print('press ctrl c to stop server')
while True:
client, address = server.accept()
print('server connected at', address)
child_pid = os.fork()
if child_pid == 0:
handle_client(client)
else:
print('appending PID', child_pid)
active_processes.append(child_pid)
except:
print('ctrl c pressed, closing server')
print('active processes :', active_processes)
kill_processes()
server.close()
In the exception handler, before closing the server socket, all active child processes are killed.
The BaseHTTPServer Module¶
We can create a very simple HTTP server.
The client is the web browser.
Working offline, we point the browser to http://localhost:8000/
as shown in Fig. 95.
This is the default page displayed in response to a GET request.
For now, our server does not make files available.
If a user requests a file, e.g.: test
,
then the server answers, as shown in Fig. 96.
Recall the script myserver.py
which allowed us to
do server side Python scripting without Apache.
We can also serve html pages without Apache:
$ python3 ourwebserver.py
welcome to our web server
press ctrl c to stop server
127.0.0.1 - - [04/Apr/2016 09:20:55] "GET / HTTP/1.1" 200 -
^C ctrl c pressed, shutting down
$
subsection{code for the simple HTTP web server}
begin{frame}{The BaseHTTPServer Module}{writing code for a web server}
Using the BaseHTTPServer
module
is similar to using SocketServer
.
Execute these steps:
Import the following:
from BaseHTTPServer import BaseHTTPRequestHandler
from BaseHTTPServer import HTTPServer
Inheriting from
BaseHTTPRequestHandler
define request handler class. Overridedo_GET()
. Thedo_GET()
defines how to serve GET requests.Instantiate
HTTPServer
with(address, port)
and an instance of the request handler class. This returns a server object.Apply
serve_forever()
to the server object.
The first part of the script ourwebserver.py
is below.
from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer
dynhtml = """
<HTML>
<HEAD><TITLE>My Home Page</TITLE></HEAD>
<BODY> <CENTER>
<H1> hello client </H1>
</CENTER> </BODY>
</HTML>"""
This defines the HTML code we display.
The second part of ourwebserver.py
continues below.
class WebServer(BaseHTTPRequestHandler):
"""
Illustration to set up a web server.
"""
def do_GET(self):
"""
Defines what server must do when
it receives a GET request.
"""
if self.path == '/':
self.send_response(200)
self.send_header('Content-type','text/html')
self.end_headers()
self.wfile.write(dynhtml.encode())
else:
message = self.path + ' not found'
self.wfile.write(message.encode())
The main()
in ourwebserver.py
is below.
def main():
"""
a simple web server
"""
try:
ws = HTTPServer(('', 8000), WebServer)
print('welcome to our web server')
print('press ctrl c to stop server')
ws.serve_forever()
except KeyboardInterrupt:
print(' ctrl c pressed, shutting down')
ws.socket.close()
Exercises¶
- Use the
SocketServer
module to implement a server to swap one data string between two clients. Clients A and B send a string to the server, client B receives what A sent and A receives what B sent. - Implement a server which generates a secret number. Clients connect to the server sending their guess for the secret. In response, the server sends one of these three messages: (1) wrong, (2) right, or (3) secret found. If a client has sent the right answer, all future clients must get reply (3).
- Consider the previous exercise and set up a simple
web server to guess a secret word. The word is the
name typed in after
localhost:8000/
in the URL.