Nov 11, 2012

A simple http server in java

A web server is an application which delivers web pages using HTTP protocol
The application explained below is a simple http server application which runs on port 8080 and serves contents from the directory C:/Web. The server handles each client request in a new worker thread.
package com.pankaj.webserver;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * File: WebServer.java
 */
public class WebServer
{
    private static final int PORT = 8080;
    private static ServerSocket listenerSocket = null;
    private static int requestId = 0;

    public static void main(String[] args) throws IOException
    {
        ConsoleLogger.LogMessage("Starting simple http server");
        listenerSocket = new ServerSocket(PORT);

        ConsoleLogger.LogMessage("Server started on port " + listenerSocket.getLocalPort());
        while (true) {
            Socket socket = listenerSocket.accept();
            ConsoleLogger.LogMessage("Client connected on port " + socket.getPort());
            new Worker(new RequestHandler(++requestId, socket));
        }
    }
}

The RequestHandler class is an implementation of Runnable interface and parses the request from the client, processes it and sends the response back to the client.
1. HTTP/1.1 400 Bad Request if the request is malformed or null
2. HTTP/1.1 403 Forbidden if the request is for listing a directory
3. HTTP/1.1 404 Not Found if the resource requested is not available
4. HTTP/1.1 501 Not Implemented if the method other than GET is used
package com.pankaj.webserver;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

/**
 * File: RequestHandler.java
 */
public class RequestHandler implements Runnable
{
    private static final String WEB_DIR = "C:/Web";

    private static final String STR_OK = "Success";
    private static final String STR_BAD_REQUEST = "Malformed Request";
    private static final String STR_FORBIDDEN = "Directory browsing is forbidden";
    private static final String STR_NOT_FOUND = "Requested resource is not found";
    private static final String STR_NOT_IMPLEMENTED = "Method is not implemented";
    private static final String STR_CRLF = "\r\n";

    private Socket socket = null;
    private String filePath = null;
    private StatusCode status = null;
    private int id;

    private enum StatusCode {
        OK(200, STR_OK),
        BAD_REQUEST(400, STR_BAD_REQUEST),
        FORBIDDEN(403, STR_FORBIDDEN),
        NOT_FOUND(404, STR_NOT_FOUND),
        NOT_IMPLEMENTED(501, STR_NOT_IMPLEMENTED);

        private int val;
        private String desc;

        private StatusCode(int sc, String desc)
        {
            this.val = sc;
            this.desc = desc;
        }

        public int getValue()
        {
            return val;
        }

        public String getDescription()
        {
            return desc;
        }
    }

    public RequestHandler(int id, Socket socket)
    {
        this.id = id;
        this.socket = socket;
    }

    @Override
    public void run()
    {
        BufferedReader in = null;
        PrintStream out = null;
        try {
            // read request from the client
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            parseRequest(in);

            // send the response back to the client
            out = new PrintStream(new BufferedOutputStream(socket.getOutputStream()));
            sendResponse(out);
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        finally {
            close(in);
            close(out);
            close(socket);
        }
    }

    // Read filename from the request "GET /filename.html"
    private void parseRequest(BufferedReader in) throws IOException
    {
        String request = in.readLine();
        ConsoleLogger.LogMessage(id, request);

        if (request == null) {
            status = StatusCode.BAD_REQUEST;
            return;
        }

        if (request.contains("GET")) {
            String[] tokens = request.split(" ");
            String fileName = tokens[1];

            filePath = getPath(fileName);
            if (isDirectory(filePath)) {
                status = StatusCode.FORBIDDEN;
            }
            else if (isFile(filePath) == false) {
                status = StatusCode.NOT_FOUND;
            }
            else {
                status = StatusCode.OK;
            }
        }
        else {
            status = StatusCode.NOT_IMPLEMENTED;
        }
    }

    private String getPath(String fileName)
    {
        return WEB_DIR + fileName;
    }

    private boolean isFile(String file)
    {
        File f = new File(file);
        return f.exists() && f.isFile();
    }

    private boolean isDirectory(String file)
    {
        return new File(file).isDirectory();
    }

    private void sendResponse(PrintStream out)
    {
        StringBuilder respHeader = new StringBuilder();
        switch (status)
        {
            case BAD_REQUEST:
                respHeader.append("HTTP/1.1 400 Bad Request");
                break;
            case FORBIDDEN:
                respHeader.append("HTTP/1.1 403 Forbidden");
                break;
            case NOT_FOUND:
                respHeader.append("HTTP/1.1 404 Not Found");
                break;
            case NOT_IMPLEMENTED:
                respHeader.append("HTTP/1.1 501 Not Implemented");
                break;
            default: // OK
                out.print("HTTP/1.1 200 OK\r\n");
                out.print("Content-Type: " + getMimeType(filePath) + "\r\n\r\n");
                transferData(out);
                return;
        }

        out.print(respHeader + STR_CRLF);
        out.print("Content-Type: text/html\r\n\r\n");
        out.print("

My Simple WebServer (:0)


Error:"
+ status.getDescription()
+ " (" + status.getValue() + ") "
+ "
"); out.flush(); ConsoleLogger.LogMessage(id, respHeader.toString()); } private void transferData(PrintStream out) { InputStream fis = null; try { fis = new FileInputStream(filePath); byte[] bytes = new byte[1024]; int bytesRead; while ((bytesRead = fis.read(bytes)) > 0) { out.write(bytes, 0, bytesRead); } out.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { close(fis); } } private String getMimeType(String fileName) { String mimeType = null; if (fileName.endsWith(".txt")) { mimeType = "text/plain"; } else if (fileName.endsWith(".html") || fileName.endsWith(".htm")) { mimeType = "text/html"; } else if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) { mimeType = "image/jpeg"; } else if (fileName.endsWith(".gif")) { mimeType = "image/gif"; } else if (fileName.endsWith(".pdf")) { mimeType = "application/pdf"; } else { mimeType = "application/octet-stream"; } return mimeType; } private void close(BufferedReader in) { if (in != null) { try { in.close(); } catch (IOException e) { // ignore } } } private void close(InputStream fis) { if (fis != null) { try { fis.close(); } catch (IOException e) { // ignore } } } private void close(PrintStream out) { if (out != null) out.close(); } private void close(Socket socket) { if (socket != null) { try { socket.close(); } catch (IOException e) { // ignore } } } }

The Worker class starts new thread and the ConsoleLogger is for printing server messages on the console.
package com.pankaj.webserver;

/**
 * File: Worker.java
 */
public class Worker
{
    public Worker(Runnable task)
    {
        new Thread(task).start();
    }
}

package com.pankaj.webserver;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * File: ConsoleLogger.java
 */
public class ConsoleLogger
{
    private static final String DATE_FORMAT_NOW = "dd-MM-yyyy HH:mm:ss";

    public static void LogMessage(String mesg)
    {
        System.out.println("[" + getDateTime() + "]" + mesg);
    }

    public static void LogMessage(int id, String mesg)
    {
        System.out.println("[" + getDateTime() + "][" + id + "]" + mesg);
    }

    private static String getDateTime()
    {
        DateFormat dateformat = new SimpleDateFormat(DATE_FORMAT_NOW);
        Date date = new Date();
        return dateformat.format(date);
    }
}

Start the server
The server starts logging requests received from the different clients

Requesting a resource from the client
Make sure you have C:\Web directory existing in your machine and you have some html contents in the directory.

Sending request from the web browser (firefox)

Trying from Telnet client
Open windows cmd prompt and type telnet localhost 8080. You will be presented an empty window. Type any command and press enter. You will see the response as shown below
If you enter command in the proper HTTP format as GET /filename.txt, you will see a different result in the telnet window as shown below

No comments :

Post a Comment