How to Develop your own httpserver-nanohttpd source code

Source: Internet
Author: User
Tags keep alive

Now, as a developer, HTTP server-related content has to be understood in any way. Using curl to send a request, configure Apache, and deploy a web server is not very difficult for us, but it is not very easy to figure out the technical details behind these events. So the new series will be the process of sharing my learning about HTTP server.


Nanohttpd is an open-source project on GitHub. It claims that an HTTP server can be created with only one Java file. I will analyze the source code of nanohttpd and how to develop my own httpserver. GitHub address: https://github.com/NanoHttpd/nanohttpd


Before you start, briefly describe the basic elements of httpserver:

1. Can accept httprequest and return httpresponse

2. It meets the basic features of a server and can run for a long time


Generally, HTTP server declares the features that support HTTP. As a lightweight HTTP server, nanohttpd only implements the simplest and most commonly used functions, but we can still learn a lot from it.


First, let's take a look at the start function of the nanohttpd class.

public void start() throws IOException {        myServerSocket = new ServerSocket();        myServerSocket.bind((hostname != null) ? new InetSocketAddress(hostname, myPort) : new InetSocketAddress(myPort));        myThread = new Thread(new Runnable() {            @Override            public void run() {                do {                    try {                        final Socket finalAccept = myServerSocket.accept();                        registerConnection(finalAccept);                        finalAccept.setSoTimeout(SOCKET_READ_TIMEOUT);                        final InputStream inputStream = finalAccept.getInputStream();                        asyncRunner.exec(new Runnable() {                            @Override                            public void run() {                                OutputStream outputStream = null;                                try {                                    outputStream = finalAccept.getOutputStream();                                    TempFileManager tempFileManager = tempFileManagerFactory.create();                                    HTTPSession session = new HTTPSession(tempFileManager, inputStream, outputStream, finalAccept.getInetAddress());                                    while (!finalAccept.isClosed()) {                                        session.execute();                                    }                                } catch (Exception e) {                                    // When the socket is closed by the client, we throw our own SocketException                                    // to break the  "keep alive" loop above.                                    if (!(e instanceof SocketException && "NanoHttpd Shutdown".equals(e.getMessage()))) {                                        e.printStackTrace();                                    }                                } finally {                                    safeClose(outputStream);                                    safeClose(inputStream);                                    safeClose(finalAccept);                                    unRegisterConnection(finalAccept);                                }                            }                        });                    } catch (IOException e) {                    }                } while (!myServerSocket.isClosed());            }        });        myThread.setDaemon(true);        myThread.setName("NanoHttpd Main Listener");        myThread.start();    }
1. Create serversocket, bind Port

2. Create a main thread and the main thread is responsible for establishing a connection with the client

3.after the connection is established, a runnableobject will be generated and stored in asyncrunner. asyncrunner.exe C will create a thread to handle the new connection.

4.create an httpsessionat the Beginning of the New thread, and then run httpsession.exe C for the while(truesion.

Here we will introduce the concept of httpsession. httpsession is the implementation of the concept of session in Java. Simply put, a session is a connection of httpclient-> httpserver. When the connection is closed, the session ends, if it does not end, the session will always exist. The code here also shows that if the socket is not closed or exec does not throw an exception (the exception may be due to a client segment disconnection), the session will always execute the exec method.

An httpsession stores the information stored by the server in a network connection, such as Uri, method, Params, headers, and cookies.

5. Here, the server model of an independent thread is created by an accept client socket. It is characterized by a connection that creates a thread, which is a simple and common socket server implementation. The disadvantage is that thread switching consumes a lot of resources when processing a large number of connections at the same time. If you are interested, you can learn more about the efficient NiO implementation method.

After obtaining the client's socket, you must start to process the httprequest sent by the client.


Parse of HTTP Request Header:

// Read the first 8192 bytes.// The full header should fit in here.                // Apache's default header limit is 8KB.                // Do NOT assume that a single read will get the entire header at once!                byte[] buf = new byte[BUFSIZE];                splitbyte = 0;                rlen = 0;                {                    int read = -1;                    try {                        read = inputStream.read(buf, 0, BUFSIZE);                    } catch (Exception e) {                        safeClose(inputStream);                        safeClose(outputStream);                        throw new SocketException("NanoHttpd Shutdown");                    }                    if (read == -1) {                        // socket was been closed                        safeClose(inputStream);                        safeClose(outputStream);                        throw new SocketException("NanoHttpd Shutdown");                    }                    while (read > 0) {                        rlen += read;                        splitbyte = findHeaderEnd(buf, rlen);                        if (splitbyte > 0)                            break;                        read = inputStream.read(buf, rlen, BUFSIZE - rlen);                    }                }
1. Read the first 8192 bytes of the socket data stream, because the header in the HTTP protocol is up to 8192

2. Find the end position of the header data through the findheaderend function and save the position to splitbyte.

if (splitbyte < rlen) {                    inputStream.unread(buf, splitbyte, rlen - splitbyte);                }                parms = new HashMap<String, String>();                if(null == headers) {                    headers = new HashMap<String, String>();                }                // Create a BufferedReader for parsing the header.                BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, rlen)));                // Decode the header into parms and header java properties                Map<String, String> pre = new HashMap<String, String>();                decodeHeader(hin, pre, parms, headers);

1. the unread function is used to return the previously read body pushback. pushbackstream is used here, because once the header is read, you need to enter the following logic to determine whether to read it again, instead of reading it all the time, until no data is read.

2. decodeheader: converts byte headers to Java objects.


private int findHeaderEnd(final byte[] buf, int rlen) {            int splitbyte = 0;            while (splitbyte + 3 < rlen) {                if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') {                    return splitbyte + 4;                }                splitbyte++;            }            return 0;        }
1. the HTTP protocol requires that the header and body should be separated by two carriage return lines.


private void decodeHeader(BufferedReader in, Map<String, String> pre, Map<String, String> parms, Map<String, String> headers)            throws ResponseException {            try {                // Read the request line                String inLine = in.readLine();                if (inLine == null) {                    return;                }                StringTokenizer st = new StringTokenizer(inLine);                if (!st.hasMoreTokens()) {                    throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html");                }                pre.put("method", st.nextToken());                if (!st.hasMoreTokens()) {                    throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html");                }                String uri = st.nextToken();                // Decode parameters from the URI                int qmi = uri.indexOf('?');                if (qmi >= 0) {                    decodeParms(uri.substring(qmi + 1), parms);                    uri = decodePercent(uri.substring(0, qmi));                } else {                    uri = decodePercent(uri);                }                // If there's another token, it's protocol version,                // followed by HTTP headers. Ignore version but parse headers.                // NOTE: this now forces header names lowercase since they are                // case insensitive and vary by client.                if (st.hasMoreTokens()) {                    String line = in.readLine();                    while (line != null && line.trim().length() > 0) {                        int p = line.indexOf(':');                        if (p >= 0)                            headers.put(line.substring(0, p).trim().toLowerCase(Locale.US), line.substring(p + 1).trim());                        line = in.readLine();                    }                }                pre.put("uri", uri);            } catch (IOException ioe) {                throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe);            }        }
1. The first line of HTTP protocol is method URI http_version.

2. The following lines are headers in key: Value format.

3. Uri can be used only after uridecode Processing

4. If the URI contains? It indicates that Param exists. The param of httprequest is:/index. jsp? Username = Xiaoming & id = 2


The following describes how to process cookies. However, the implementation of cookies is relatively simple, so the process is skipped. The serve method provides a good interface for users to implement the specific logic of httpserver. The Serve Method in nanohttpd implements a default Simple processing function.

/**     * Override this to customize the server.     * <p/>     * <p/>     * (By default, this delegates to serveFile() and allows directory listing.)     *     * @param session The HTTP session     * @return HTTP response, see class Response for details     */    public Response serve(IHTTPSession session) {        Map<String, String> files = new HashMap<String, String>();        Method method = session.getMethod();        if (Method.PUT.equals(method) || Method.POST.equals(method)) {            try {                session.parseBody(files);            } catch (IOException ioe) {                return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());            } catch (ResponseException re) {                return new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());            }        }        Map<String, String> parms = session.getParms();        parms.put(QUERY_STRING_PARAMETER, session.getQueryParameterString());        return serve(session.getUri(), method, session.getHeaders(), parms, files);    }
This default method processes the put and post methods. If not, the default return values are returned.

The tmpfile method is used in the parsebody method to save the content information of httprequest and process it. The specific logic is not described in detail. It is not a typical implementation.


Finally, let's take a look at the logic of sending response:

/**         * Sends given response to the socket.         */        protected void send(OutputStream outputStream) {            String mime = mimeType;            SimpleDateFormat gmtFrmt = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);            gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT"));            try {                if (status == null) {                    throw new Error("sendResponse(): Status can't be null.");                }                PrintWriter pw = new PrintWriter(outputStream);                pw.print("HTTP/1.1 " + status.getDescription() + " \r\n");                if (mime != null) {                    pw.print("Content-Type: " + mime + "\r\n");                }                if (header == null || header.get("Date") == null) {                    pw.print("Date: " + gmtFrmt.format(new Date()) + "\r\n");                }                if (header != null) {                    for (String key : header.keySet()) {                        String value = header.get(key);                        pw.print(key + ": " + value + "\r\n");                    }                }                sendConnectionHeaderIfNotAlreadyPresent(pw, header);                if (requestMethod != Method.HEAD && chunkedTransfer) {                    sendAsChunked(outputStream, pw);                } else {                    int pending = data != null ? data.available() : 0;                    sendContentLengthHeaderIfNotAlreadyPresent(pw, header, pending);                    pw.print("\r\n");                    pw.flush();                    sendAsFixedLength(outputStream, pending);                }                outputStream.flush();                safeClose(data);            } catch (IOException ioe) {                // Couldn't write? No can do.            }        }
To send a response, follow these steps:

1. Set mimetype and time.

2. Create a printwriter and write the content according to the HTTP protocol in sequence.

3. The first line is the HTTP return code.

4. Then Content-Type

5. Then the date time

6. Other HTTP headers

7. Set the header of keep-alive. keep-alive is a new feature of http1.1 to maintain a long link between the client and the server.

8. If the client specifies chunkedencoding, the client sends response in multiple parts. Chunked encoding is another new feature of http1.1. Generally used when the response body is large, the server first sends the response header, and then sends the response body in parts, each Chunk is composed of chunk length \ r \ n and chunk data \ r \ n, and ends with a 0 \ r \ n.

private void sendAsChunked(OutputStream outputStream, PrintWriter pw) throws IOException {            pw.print("Transfer-Encoding: chunked\r\n");            pw.print("\r\n");            pw.flush();            int BUFFER_SIZE = 16 * 1024;            byte[] CRLF = "\r\n".getBytes();            byte[] buff = new byte[BUFFER_SIZE];            int read;            while ((read = data.read(buff)) > 0) {                outputStream.write(String.format("%x\r\n", read).getBytes());                outputStream.write(buff, 0, read);                outputStream.write(CRLF);            }            outputStream.write(String.format("0\r\n\r\n").getBytes());        }

9. If chunkedencoding is not specified, You need to specify Content-Length to allow the client to specify the size of the response body, and then write the body until it is written.

private void sendAsFixedLength(OutputStream outputStream, int pending) throws IOException {            if (requestMethod != Method.HEAD && data != null) {                int BUFFER_SIZE = 16 * 1024;                byte[] buff = new byte[BUFFER_SIZE];                while (pending > 0) {                    int read = data.read(buff, 0, ((pending > BUFFER_SIZE) ? BUFFER_SIZE : pending));                    if (read <= 0) {                        break;                    }                    outputStream.write(buff, 0, read);                    pending -= read;                }            }        }


Finally, the most important part of httpserver implementation is summarized as follows:

1. Able to access the TCP connection and read request data from the socket

2. Change the bits of the request to the object data in the request object.

3. handle HTTP requests according to HTTP specifications

4. Generate HTTP Response and write it back to the socket and send it to the client.




How to Develop your own httpserver-nanohttpd source code

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.