HTTP Server Example

This example shows how to create a simple HTTP server.

A screenshot of the application.

We import the classes and modules that will be needed by the application. The most relevant are the classes from the java.net module.

from java.io import BufferedInputStream, BufferedOutputStream, IOException, \
                    OutputStream
from java.lang import Exception, Runnable, String, Thread
from java.net import BindException, ServerSocket, SocketException
from java.util import LinkedList
from android.os import Handler, Looper
from android.view import View
from android.widget import Button, LinearLayout, ScrollView, TextView, Toast
from serpentine.activities import Activity

We define a class based on the specialised Activity class provided by the serpentine.activities module. This represents the application, and will be used to present a graphical interface to the user.

class HTTPServerActivity(Activity):

    __interfaces__ = [Runnable, View.OnClickListener]
    
    __fields__ = {"inputStream": BufferedInputStream}
    
    START_TEXT = "Start server"
    STOP_TEXT = "Stop server"
    SERVING = "Serving on port "
    NOT_SERVING = "Not serving"
    PORT_IN_USE = "Port already in use"
    
    OK = "HTTP/1.1 200 OK"
    BAD_REQUEST = "HTTP/1.1 400 Bad Request"
    
    PAGE_TEXT = (
        "<html><body><h1>HTTPServerActivity</h1>\n"
        "<p>This is a page served by the HTTP Server example.</p>\n"
        "</body></html>"
        )
    
    ERROR_TEXT = (
        "<html><body><h1>Bad Request</h1>\n"
        "<p>The server failed to understand the request. Sorry!</p>\n"
        "</body></html>"
        )
    
    def __init__(self):
        Activity.__init__(self)
    
    def onCreate(self, bundle):
    
        Activity.onCreate(self, bundle)
        
        # Define whether the server is running.
        self.running = False

We create a label to show the server's status, a button to allow the user to start and stop it, and a label in a scroll view to show a log of requests that the server has received.

        self.label = TextView(self)
        
        self.button = Button(self)
        self.button.setText(self.START_TEXT)
        self.button.setOnClickListener(self)
        
        self.requestLabel = TextView(self)
        self.scrollView = ScrollView(self)
        self.scrollView.addView(self.requestLabel)

We also create a handler that will receive messages sent from the worker thread.

        self.handler = MessageHandler(Looper.getMainLooper(), self)

Finally, we put all the widgets in a layout and make that the main content of the application.

        layout = LinearLayout(self)
        layout.setOrientation(layout.VERTICAL)
        layout.addView(self.label)
        layout.addView(self.button)
        layout.addView(self.scrollView)
        
        self.setContentView(layout)
    
    def onClick(self, view):
    
        if not self.running:
        
            # Create a server socket to handle new connections.
            try:
                self.serverSocket = ServerSocket(8080)
            except BindException:
                Toast.makeText(self, self.PORT_IN_USE, Toast.LENGTH_SHORT).show()
                return
            
            # Indicate that the server is running, mostly for the GUI but also
            # for the server thread.
            self.running = True
            
            # Reset the input stream so that we can detect when there is an
            # active input stream.
            self.inputStream = None
            
            # Start a new thread with this instance as its runnable object.
            self.thread = Thread(self)
            self.thread.start()
            
            # Change the button and status label text to indicate that the
            # server is running.
            self.button.setText(self.STOP_TEXT)
            self.label.setText(self.SERVING + str(self.serverSocket.getLocalPort()))
        else:
            self.stopServer()
    
    def stopServer(self):
    
        # Indicate that the server is not running, mostly for the GUI but
        # also for the server thread.
        self.running = False

        # Close the server socket so that new connections cannot be made.
        self.serverSocket.close()

        # Close the input stream, if opened, so that pending connection
        # socket reads are interrupted.
        if self.inputStream != None:
            self.inputStream.close()

        # Wait until the thread has finished.
        self.thread.join()

        # Reset the button and status label.
        self.button.setText(self.START_TEXT)
        self.label.setText(self.NOT_SERVING)
    
    @args(void, [String])
    def addLogText(self, text):
    
        s = str(self.requestLabel.getText()) + text
        self.requestLabel.setText(s)
        self.scrollView.fullScroll(View.FOCUS_DOWN)
    
    def run(self):
    
        while self.running:
        
            try:
                self.writeLog("Waiting...\n")
                socket = self.serverSocket.accept()
                
                self.inputStream = BufferedInputStream(socket.getInputStream())
                output = BufferedOutputStream(socket.getOutputStream())
                
                self.writeLog("Started handling requests...\n")
                
                while self.running:
                
                    # Handle requests on the same socket until the connection
                    # is closed by the client.
                    request = []
                    
                    while self.running:
                    
                        l = []
                        
                        while self.running:
                        
                            b = self.inputStream.read()
                            
                            if b == 10:         # newline
                                l.add(byte(b))
                                break
                            elif b == -1:
                                raise IOException()
                            
                            l.add(byte(b))
                        
                        s = String(array(l), "ASCII")
                        
                        if s == "\r\n":
                            break
                        
                        request.add(s)
                    
                    self.handleRequest(request, output)
                    self.writeLog("Handled\n")
            
            except (SocketException,), se:
                self.writeLog(str(se) + "\n")
            
            except (IOException,), ioe:
                self.writeLog(str(ioe) + "\n")
        
        self.writeLog("Stopped\n")
        
        message = self.handler.obtainMessage(1, None)
        message.sendToTarget()
    
    @args(void, [String])
    def writeLog(self, s):
    
        message = self.handler.obtainMessage(0, s)
        message.sendToTarget()
    
    @args(void, [LinkedList(String), OutputStream])
    def handleRequest(self, lines, output):
    
        request = lines.pollFirst()
        if request == None:
            self.writeResponse(self.BAD_REQUEST, self.ERROR_TEXT, output)
        
        self.writeLog(request)
        
        headers = {}
        for line in lines:
            at = line.indexOf(":")
            if at != -1:
                headers[line[:at]] = line[at + 1:]
            
        pieces = request.split(" ")
        
        if len(pieces) >= 2:
            if pieces[0] == "GET":
                if pieces[1] == "/":
                    self.writeResponse(self.OK, self.PAGE_TEXT, output)
                    return
        
        self.writeResponse(self.BAD_REQUEST, self.ERROR_TEXT, output)
    
    @args(void, [String, String, OutputStream])
    def writeResponse(self, status, text, output):
    
        b = text.getBytes("UTF-8")
        l = [status,
             "Content-Type: text/html",
             "Content-Length: " + str(len(b))]
        
        s = ""
        for i in l:
            s += i + "\r\n"
        
        s += "\r\n"
        
        #self.writeLog(s + text)
        output.write(s.getBytes("US-ASCII"))
        output.flush()
        output.write(b)
        output.flush()


class MessageHandler(Handler):

    @args(void, [Looper, HTTPServerActivity])
    def __init__(self, looper, activity):
    
        Handler.__init__(self, looper)
        self.activity = activity
    
    def handleMessage(self, inputMessage):
    
        if inputMessage.what == 0:
            self.activity.addLogText(str(inputMessage.obj))
        
        elif inputMessage.what == 1:
            self.activity.stopServer()

Files