Analysis of httpserver source code in jdk

Before I wrote this blog, I checked it for a long time and found that there was no one on the whole network who wrote httpserver source code analysis

So today, I will analyze the source code of http server for you. (here I will remove the source code of https part and only talk about http part. Readers interested in the implementation of https in httpserver can try to read it by themselves, which is not complicated.)

For the first time, if you write such a long source code analysis without reference, there may be many mistakes and unclear points. I hope you can point them out as much as possible.

Link to this article https://www.cnblogs.com/fatmanhappycode/p/12614428.html

A simple example of httpserver

You'd better follow me to build such a small demo first, and then go to the source code step by step

/**
 * @author Happy fat house code
 */
public class HttpServerSample {

    private static void serverStart() throws IOException {
        HttpServerProvider provider = HttpServerProvider.provider();
        // Listening port 8080,Connect to the queue. If the number of connections in the queue exceeds this number, the connection will be rejected
        HttpServer httpserver =provider.createHttpServer(new InetSocketAddress(8080), 100);
        // Monitoring path is RestSample,Callback after request processing RestGetHandler Li handle Method
        httpserver.createContext("/RestSample", new RestGetHandler());
        // Manage worker pool
        ExecutorService executor = new ThreadPoolExecutor(10,200,60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy());
        httpserver.setExecutor(executor);
        httpserver.start();
        System.out.println("server started");
    }

    public static void main(String[] args) throws IOException {
        serverStart();
    }
}

/**
 * Callback class, in which the handle method mainly completes the function of returning the wrapped request header to the client
 */
class RestGetHandler implements HttpHandler {
    @Override
    public void handle(HttpExchange he) throws IOException {
        String requestMethod = he.getRequestMethod();
        // If it is get Method
        if ("GET".equalsIgnoreCase(requestMethod)) {
            // Get the response header. Next, let's set the response header information
            Headers responseHeaders = he.getResponseHeaders();
            // with json Form return, others text/html Wait
            responseHeaders.set("Content-Type", "application/json");
            // Set response code 200 and response body Length, here we set 0, no response body
            he.sendResponseHeaders(200, 0);
            // Get response body
            OutputStream responseBody = he.getResponseBody();
            // Get request header and print
            Headers requestHeaders = he.getRequestHeaders();
            Set<String> keySet = requestHeaders.keySet();
            Iterator<String> iter = keySet.iterator();
            while (iter.hasNext()) {
                String key = iter.next();
                List values = requestHeaders.get(key);
                String s = key + " = " + values.toString() + "\r\n";
                responseBody.write(s.getBytes());
            }
            // Close output stream
            responseBody.close();
        }
    }
}

 

httpserver initialization and start source code

 

Initialization

① In the beginning, we created an HttpServerProvider through HttpServerProvider = HttpServerProvider. Provider(); which is DefaultHttpServerProvider here

// HttpServerProvider.java
public static HttpServerProvider provider () {
    // Here we delete the rest, leaving only 172 and 173 lines
    // Here we create a DefaultHttpServerProvider
    provider = new sun.net.httpserver.DefaultHttpServerProvider();
    return provider;
}

 

② Then we call HttpServer httpserver =provider.createHttpServer(new InetSocketAddress(8080), 100),

In other words, the createHttpServer of DefaultHttpServerProvider is called to create an HttpServerImpl. Of course, the createHttpsServer can also be used to create an HttpsServerImpl, but as mentioned above, we do not analyze https in this article, so the createHttpsServer method is ignored here

And here's how to create ServerImpl. We won't talk about it for the moment. Let's leave it later

// DefaultHttpServerProvider.java
public HttpServer createHttpServer (InetSocketAddress addr, int backlog) throws IOException {
    return new HttpServerImpl (addr, backlog);
}

// HttpServerImpl.java
HttpServerImpl (
    InetSocketAddress addr, int backlog
) throws IOException {
    server = new ServerImpl (this, "http", addr, backlog);
}

 

③ next, we create a listening path, httpserver.createContext("/RestSample", new RestGetHandler())

// HttpServer.java
public abstract HttpContext createContext (String path, HttpHandler handler) ;

// HttpContextImpl.java
public HttpContextImpl createContext (String path, HttpHandler handler) {
    // Called here server yes ServerImpl Class objects
    return server.createContext (path, handler);
}

Here we successfully return an HttpContextImpl object, which we will say later. What we need to know here is that HttpServerImpl calls the implementation of ServerImpl

Here we can talk about the main structure of httpserver:

 

Main structure

HttpServer is the ancestor class here. It is an abstract class that abstracts a method that HttpServer should have

HttpsServer is different from what we think. It is not a parallel relationship with HttpServer, but a subclass of HttpServer. It adds two methods, setHttpsConfigurator and getHttpsConfigurator, on the basis of HttpServer

Although HttpServerImpl and HttpsServerImpl are implementation classes, their methods call ServerImpl's methods, which are all around ServerImpl

So we can also regard ServerImpl as the core class of this project

 

④ Then set up the worker thread pool, and the initialization task is completed

ExecutorService executor = new ThreadPoolExecutor(10,200,60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy()); 
httpserver.setExecutor(executor);

 

Start up

  httpserver.start(); 

Starting is naturally the same as the structure we just talked about. It starts from HttpServer and calls the ServerImpl method layer by layer

// HttpServer.java
public abstract void start () ;

// HttpServerImpl.java
public void start () {
    server.start();
}

// ServerImpl.java
public void start () {
    // server Unbound port or in started or closed state
    // By the way, you can notice that, ServerImpl As a core class, it manages various states(state)etc.
    if (!bound || started || finished) {
        throw new IllegalStateException ("server in wrong state");
    }
    // If the thread pool is not set, the default is used. By default, it is equal to no thread pool. It is direct execute So create thread pools as directly as possible
    if (executor == null) {
        executor = new DefaultExecutor();
    }
    // Created a Dispatcher Thread, used to distribute tasks, such as Accept perhaps Readable
    Thread t = new Thread (dispatcher);
    // Set the status
    started = true;
    // Thread of operation
    t.start();
}

 

 

ServerImpl structure chart

As we mentioned earlier, ServerImpl is the core part of the whole project. It manages the state of httpserver, provides various interfaces and common methods, and is also responsible for the startup of several internal class threads

Therefore, we will explain it in four parts: ServerImpl, Dispatcher, Exchange, ServerTimerTask and ServerTimerTask 1

 

 

ServerImpl

 

Main attributes

(I removed the https related)

It's quite a long time. You'll have an impression after a while, and then you can come back when you meet it

// http or https
private String protocol;

private Executor executor;

// The class responsible for receiving the connection (originally near line 209, I brought it up)
private Dispatcher dispatcher;

// ContextList This class just encapsulates a List<HttpContextImpl>And some methods, such as limiting monitoring context(Number of paths) and find context Method
private ContextList contexts;

private InetSocketAddress address;

// nio Related classes
private ServerSocketChannel schan;
private Selector selector;
private SelectionKey listenerKey;

// Responsible for managing the aforementioned idle Connected, that is, long connected set
// For long connection, if there is no task in the connection, add it. If there is no task after a certain period of time, disconnect the long connection actively
private Set<HttpConnection> idleConnections;
// Manage all connections for easy access to stop Disconnect all connections directly in case of
private Set<HttpConnection> allConnections;
// Administration req Connection and rsp Connect to prevent request or response timeout,Timeout disconnected by timed thread
private Set<HttpConnection> reqConnections;
private Set<HttpConnection> rspConnections;

// After these two 6.4 Of Exchange Of addEvent Method part. Let's talk about it
private List<Event> events;
private final Object loLock = new Object();

// All kinds of States, I believe you can understand what it means
private volatile boolean finished = false;
private volatile boolean terminating = false;
private boolean bound = false;
private boolean started = false;

// System time, will be ServerTimerTask Update
private volatile long time;
// This doesn't seem to work
private volatile long subticks = 0;
// This is to record how many times it has been updated time It's like a time stamp
private volatile long ticks;

// hold HttpServer Packed in, easy to call
private HttpServer wrapper;

// This means ServerTimerTask How often and regularly run Just a moment, because ServerTimerTask Is a scheduled task thread
// The default is 10000 ms Once in 10 seconds
private final static int CLOCK_TICK = ServerConfig.getClockTick();
// This is the allowed long connection dwell time. The default is 30 seconds
private final static long IDLE_INTERVAL = ServerConfig.getIdleInterval();
// Maximum number of long connections allowed, default 200
private final static int MAX_IDLE_CONNECTIONS = ServerConfig.getMaxIdleConnections();
// ServerTimerTask1 The default is 1 second
private final static long TIMER_MILLIS = ServerConfig.getTimerMillis ();
// The last two defaults to-1,As for why-1 behind ServerTimerTask Part of what we're going to say
private final static long MAX_REQ_TIME = getTimeMillis(ServerConfig.getMaxReqTime());
private final static long MAX_RSP_TIME=getTimeMillis(ServerConfig.getMaxRspTime());
private final static boolean REQ_RSP_CLEAN_ENABLED = MAX_REQ_TIME != -1 || MAX_RSP_TIME != -1;

// ServerTimerTask and ServerTimerTask1 The object of running ServerTimerTask and ServerTimerTask1 Thread
private Timer timer, timer1;

private Logger logger;

 

Construction method

This is the construction method of ServerImpl just mentioned in Section 2.1. It's nothing more than initializing variables and starting the ServerTimerTask and ServerTimerTask 1 threads

ServerImpl (
        HttpServer wrapper, String protocol, InetSocketAddress addr, int backlog
    ) throws IOException {

        this.protocol = protocol;
        this.wrapper = wrapper;
        this.logger = Logger.getLogger ("com.sun.net.httpserver");
        ServerConfig.checkLegacyProperties (logger);
        this.address = addr;
        contexts = new ContextList();
        schan = ServerSocketChannel.open();
        if (addr != null) {
            ServerSocket socket = schan.socket();
            socket.bind (addr, backlog);
            bound = true;
        }
        selector = Selector.open ();
        schan.configureBlocking (false);
        listenerKey = schan.register (selector, SelectionKey.OP_ACCEPT);
        dispatcher = new Dispatcher();
        idleConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());
        allConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());
        reqConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());
        rspConnections = Collections.synchronizedSet (new HashSet<HttpConnection>());
        time = System.currentTimeMillis();
        timer = new Timer ("server-timer", true);
        // As you can see, in the initialization phase, two timing tasks have been started
        timer.schedule (new ServerTimerTask(), CLOCK_TICK, CLOCK_TICK);
        if (timer1Enabled) {
            timer1 = new Timer ("server-timer1", true);
            timer1.schedule (new ServerTimerTask1(),TIMER_MILLIS,TIMER_MILLIS);
            logger.config ("HttpServer timer1 enabled period in ms:  "+TIMER_MILLIS);
            logger.config ("MAX_REQ_TIME:  "+MAX_REQ_TIME);
            logger.config ("MAX_RSP_TIME:  "+MAX_RSP_TIME);
        }
        events = new LinkedList<Event>();
        logger.config ("HttpServer created "+protocol+" "+ addr);
    }

 

Of course, there are many common methods in ServerImpl, but we won't talk about them here. We'll talk about them when we use them. It's more convenient to understand the specific uses of these common methods

 

 

Dispatcher

Let's first look at its run method

run()

public void run() {
    // If the server has been shut down completely, there is no need to deal with it
    while (!finished) {
        try {
            // ================This section is about registering the connection after processing and returning the result idle In the long-term connection, the following process will be explained in detail=====================================
            List<Event> list = null;
            synchronized (lolock) {
                if (events.size() > 0) {
                    list = events;
                    events = new LinkedList<Event>();
                }
            }

            if (list != null) {
                for (Event r: list) {
                    handleEvent (r);
                }
            }

            for (HttpConnection c : connsToRegister) {
                reRegister(c);
            }
            connsToRegister.clear();
            // ========================================================================================================================

            // Blocking, over 1000 ms Keep running
            selector.select(1000);

            /* process the selected list now  */
            Set<SelectionKey> selected = selector.selectedKeys();
            Iterator<SelectionKey> iter = selected.iterator();
            while (iter.hasNext()) {
                SelectionKey key = iter.next();
                iter.remove ();

                // Here listenrKey yes accept Event, equivalent to key.isAcceptable()
                if (key.equals (listenerKey)) {
                     // If you're shutting down the server, you don't have to deal with it. Just connect the new connection continue Then? remove Just drop it
                    if (terminating) {
                        continue;
                    }
                    SocketChannel chan = schan.accept();

                    // On as needed TCPNoDelay,Close Nagle Algorithm to reduce the delay caused by cache
                    if (ServerConfig.noDelay()) {
                        chan.socket().setTcpNoDelay(true);
                    }

                    if (chan == null) {
                        continue; /* cancel something ? */
                    }
                    chan.configureBlocking (false);
                    SelectionKey newkey = chan.register (selector, SelectionKey.OP_READ);
                    // Establish connection And put channel Put it in
                    HttpConnection c = new HttpConnection ();
                    c.selectionKey = newkey;
                    c.setChannel (chan);
                    // hold connection Cache to Key in
                    newkey.attach (c);
                    // Request to start, register to reqConnections in
                    requestStarted (c);
                    allConnections.add (c);
                } else {
                    try {
                        if (key.isReadable()) {
                            boolean closed;
                            SocketChannel chan = (SocketChannel)key.channel();
                            // Here we put the attach Cached connection Take it out.
                            HttpConnection conn = (HttpConnection)key.attachment();

                            // This read mode of deregistration and blocking is related to multiple reads
                            // Because the head is read first, and then read body Other parts
                            key.cancel();
                            chan.configureBlocking (true);
                            // If this connection If it is a previously saved free long connection, remove it directly idleConnections in
                            // And add reqConnections Start request (because io The stream is initialized and can be used directly.)
                            if (idleConnections.remove(conn)) {
                                // join reqConnections Start asking
                                requestStarted (conn);
                            }
                            // call handle Follow up
                            handle (chan, conn);
                        } else {
                            assert false;
                        }
                    } catch (CancelledKeyException e) {
                        handleException(key, null);
                    } catch (IOException e) {
                        handleException(key, e);
                    }
                }
            }
            // call select Remove cancel Of course. key
            selector.selectNow();
        } catch (IOException e) {
            logger.log (Level.FINER, "Dispatcher (4)", e);
        } catch (Exception e) {
            logger.log (Level.FINER, "Dispatcher (7)", e);
        }
    }
    try {selector.close(); } catch (Exception e) {}
}           

 

Here is a little summary. Dispatcher run is mainly used to complete the accept of socket connection and the distribution of Readable events. Accept is distributed to itself. It creates its own channel and registers, creates its own connection and caches. However, after simple processing, the Readable event is handed over to the handle to call the Exchange thread for further tasks

 

handle(SocketChannel, HttpConnection)

public void handle (SocketChannel chan, HttpConnection conn)
throws IOException
{
    try {
        // Construct a Exchange Later let executor Thread pool to execute, which is equivalent to an asynchronous task
·       // In the process of handing over the task to executor Later, dispatcher You can go back
        Exchange t = new Exchange (chan, protocol, conn);
        executor.execute (t);
    } catch (HttpError e1) {
        logger.log (Level.FINER, "Dispatcher (4)", e1);
        closeConnection(conn);
    } catch (IOException e) {
        logger.log (Level.FINER, "Dispatcher (5)", e);
        closeConnection(conn);
    }
}

 

 

Exchange

Now that we have left the task to Exchange, let's see what Exchange's run method is doing

run()

public void run () {
     // context It corresponds to this http The path and processor to request access,
     // And an unresolved http Request nature context by null,I don't know which path this request wants to request
    context = connection.getHttpContext();
    boolean newconnection;

    try {
        // Here it has been resolved http Request to go in, because they have context
        // Why has it been parsed http What about the request? Think about the long connection, front Dispatcher We've talked about lines 75 and 76 of
        // Long connection is idleConnection Will cache those io Flow in connection Inside, of course context
        //(But just being doesn't mean context It does not need to be re parsed, after all, the requested resource links are not necessarily the same when re requesting)
        if (context != null ) {
            this.rawin = connection.getInputStream();
            this.rawout = connection.getRawOutputStream();
            newconnection = false;
        } else {
      
            newconnection = true;
            if (https) {
                // . . . . . .

            } else {
                // Here Request Two kinds stream They all encapsulate some reading and writing methods, which is rather cumbersome, so we don't analyze them
                rawin = new BufferedInputStream(
                    new Request.ReadStream (
                        ServerImpl.this, chan
                ));
                rawout = new Request.WriteStream (
                    ServerImpl.this, chan
                );
            }
            connection.raw = rawin;
            connection.rawout = rawout;
        }
        Request req = new Request (rawin, rawout);
        requestLine = req.requestLine();
        // Close if the request is empty after reading a row of the request connection
        // So what is empty? As you all know, http The request consists of three parts,
        // 1.Triple handshake connection, encapsulated as socket Of accept
        // 2.Start sending content, encapsulated as socket Of readable Event
        // What about four waves? Actually, too. readable,But its content is empty
        // So this is actually a wave to close the connection
        if (requestLine == null) {
            closeConnection(connection);
            return;
        }
 
        // Get request type( GET/POST...)
        int space = requestLine.indexOf (' ');
        if (space == -1) {
            reject (Code.HTTP_BAD_REQUEST,
                    requestLine, "Bad request line");
            return;
        }
        String method = requestLine.substring (0, space);

        // Get requested url
        int start = space+1;
        space = requestLine.indexOf(' ', start);
        if (space == -1) {
            reject (Code.HTTP_BAD_REQUEST,
                    requestLine, "Bad request line");
            return;
        }
        String uriStr = requestLine.substring (start, space);
        URI uri = new URI (uriStr);

        // http Requested version(1.0/1.1...)
        start = space+1;
        String version = requestLine.substring (start);
         
        Headers headers = req.headers();
        
        // If adopted Transfer-encoding,So analysis body In different ways,
        // And Context-Length Will be ignored, so marked as length clen = -1
        // You can get to know more about it Transfer-encoding
        String s = headers.getFirst ("Transfer-encoding");
        long clen = 0L;
        if (s !=null && s.equalsIgnoreCase ("chunked")) {
            clen = -1L;
        } else {
            // Useless Transfer-encoding And used Content-Length
            s = headers.getFirst ("Content-Length");
            if (s != null) {
                clen = Long.parseLong(s);
            }
            if (clen == 0) {
                // If the principal length is 0, then the request has ended. Here connection from 
                // reqConnections Move out of, add current time, join rspConnections
                requestCompleted (connection);
            }
        }
        
        // This is the beginning ServerImpl Properties (you can go back to) ContextList The method of inner packing
        // Used to query whether there is a matching context Route
        ctx = contexts.findContext (protocol, uri.getPath());
        if (ctx == null) {
            reject (Code.HTTP_NOT_FOUND,
                    requestLine, "No context found for request");
            return;
        }
        connection.setContext (ctx);

        // If there is no callback method, it is the beginning demo Customized in RestGetHandler class
        if (ctx.getHandler() == null) {
            reject (Code.HTTP_INTERNAL_ERROR,
                    requestLine, "No handler for context");
            return;
        }
        
        // Amount to http The complete encapsulation of the request, and then the next layer HttpExchangeImpl Namely
        // RestGetHandler Callback method in class handle The parameters of the
        tx = new ExchangeImpl (
            method, uri, req, clen, connection
        );

        // See if there's any connection: close Parameters, 1.0 default close,Manual opening required keep-alive
        String chdr = headers.getFirst("Connection");
        Headers rheaders = tx.getResponseHeaders();
        if (chdr != null && chdr.equalsIgnoreCase ("close")) {
            tx.close = true;
        }
        if (version.equalsIgnoreCase ("http/1.0")) {
            tx.http10 = true;
            if (chdr == null) {
                tx.close = true;
                rheaders.set ("Connection", "close");
            } else if (chdr.equalsIgnoreCase ("keep-alive")) {
                rheaders.set ("Connection", "keep-alive");
                int idle=(int)(ServerConfig.getIdleInterval()/1000);
                int max=ServerConfig.getMaxIdleConnections();
                String val = "timeout="+idle+", max="+max;
                rheaders.set ("Keep-Alive", val);
            }
        }

        // If it's a new connection instead of a long one, give connection Assignment
        if (newconnection) {
            connection.setParameters (
                rawin, rawout, chan, engine, sslStreams,
                sslContext, protocol, ctx, rawin
            );
        }
        
        // If the client issues expect:100-continue,It means that the client wants to post Things (usually larger), ask if you agree
        // The client will not continue until response code 100 is returned post data
        String exp = headers.getFirst("Expect");
        if (exp != null && exp.equalsIgnoreCase ("100-continue")) {
            logReply (100, requestLine, null);
            sendReply (
                Code.HTTP_CONTINUE, false, null
            );
        }

        // Get the filter of the system sf Or user-defined filter uf,It's none by default
        List<Filter> sf = ctx.getSystemFilters();
        List<Filter> uf = ctx.getFilters();
        // Construct a linked list, call the filter layer by layer in the form of linked list
        Filter.Chain sc = new Filter.Chain(sf, ctx.getHandler());
        Filter.Chain uc = new Filter.Chain(uf, new LinkHandler (sc));

        // Initialize the wrapped io Liu, here I put getRequestBody Take it. They're the same
        /**
         *public InputStream getRequestBody () {
         *     if (uis != null) {
         *         return uis;
         *     }
         *     if (reqContentLen == -1L) {
         *         uis_orig = new ChunkedInputStream (this, ris);
         *         uis = uis_orig;
         *     } else {
         *         uis_orig = new FixedLengthInputStream (this, ris, reqContentLen);
         *         uis = uis_orig;
         *     }
         *     return uis;
         *}
         */
        tx.getRequestBody();
        tx.getResponseBody();
        if (https) {
            uc.doFilter (new HttpsExchangeImpl (tx));
        } else {
            // Start to execute the filtering method, and the parameters are the same as I just mentioned, that is, package HttpExchangeImpl Of ExchangeImpl
            // Next we'll look here
            uc.doFilter (new HttpExchangeImpl (tx));
        }

    } catch (IOException e1) {
        logger.log (Level.FINER, "ServerImpl.Exchange (1)", e1);
        closeConnection(connection);
    } catch (NumberFormatException e3) {
        reject (Code.HTTP_BAD_REQUEST,
                requestLine, "NumberFormatException thrown");
    } catch (URISyntaxException e) {
        reject (Code.HTTP_BAD_REQUEST,
                requestLine, "URISyntaxException thrown");
    } catch (Exception e4) {
        logger.log (Level.FINER, "ServerImpl.Exchange (2)", e4);
        closeConnection(connection);
    }
}

doFilter()

// Filter.java Of Chain Inner class
public void doFilter (HttpExchange exchange) throws IOException {
    // Call recursively until no filter Call the custom callback method, that is RestGetHandler Of handle Method
    if (!iter.hasNext()) {
        handler.handle (exchange);
    } else {
        Filter f = iter.next();
        f.doFilter (exchange, this);
    }
}

I'll paste the RestGetHandler in the demo again to show you (the comments on lines 17 and 32 have been changed, please pay attention to it):

/**
 * Callback class, in which the handle method mainly completes the function of returning the wrapped request header to the client
 */
class RestGetHandler implements HttpHandler {
    @Override
    public void handle(HttpExchange he) throws IOException {
        String requestMethod = he.getRequestMethod();
        // If it is get Method
        if ("GET".equalsIgnoreCase(requestMethod)) {
            // Get the response header. Next, let's set the response header information
            Headers responseHeaders = he.getResponseHeaders();
            // with json Form return, others text/html Wait
            responseHeaders.set("Content-Type", "application/json");
            // Set response code 200 and response body Length, here we set 0, no response body,It's also initialized here io flow
            // Here, if it is 0, initialization ChunkedOutputStream or UndefLengthOutputStream
            // If not 0, initialize FixedLengthOutputStream
            he.sendResponseHeaders(200, 0);
            // Get response body
            OutputStream responseBody = he.getResponseBody();
            // Get request header and print
            Headers requestHeaders = he.getRequestHeaders();
            Set<String> keySet = requestHeaders.keySet();
            Iterator<String> iter = keySet.iterator();
            while (iter.hasNext()) {
                String key = iter.next();
                List values = requestHeaders.get(key);
                String s = key + " = " + values.toString() + "\r\n";
                responseBody.write(s.getBytes());
            }
            // Close the output stream, that is, close ChunkedOutputStream
            // Let's see here
            responseBody.close();
        }
    }
}

After the callback method completes the task of returning data to the client, the close method is called

close()

Here we focus on the last line of code

public void close () throws IOException {
        if (closed) {
            return;
        }
        flush();
        try {
            writeChunk();
            out.flush();
            LeftOverInputStream is = t.getOriginalInputStream();
            if (!is.isClosed()) {
                is.close();
            }
        } catch (IOException e) {

        } finally {
            closed = true;
        }

        WriteFinishedEvent e = new WriteFinishedEvent (t);
        // Here we only focus on the last line, others don't
        // This line calls addEvent Method
        t.getHttpContext().getServerImpl().addEvent (e);
    }

addEvent()

// Here we call 4.1 in ServerImpl The contents of lines 28, 29 and 30 of the attribute of
void addEvent (Event r) {
    // And the lock here is to prevent Dispatcher Of run The front of the way
    // Prevent it from being removed events Time and here add Conflict
    synchronized (lolock) {
        events.add (r);
        // There wakeup It is to input a byte into the pipeline to wake up Dispatcher in
        // Of selector.select(1000),Get it out of the way events
        selector.wakeup();
    }
}

The work of Exchange is finished here. Next, I will summarize:

  1. First, Exchange parses and encapsulates the http request, matches the handle of the corresponding context, and initializes the io flow
  2. Then Exchange calls the corresponding callback handle method for processing
  3. The handle method is generally a response method written by ourselves. The handle method of RestGetHandler I defined here is responsible for responding the request header as the content, which is the effect shown in the figure below
  4. The handle method then calls close of the io stream to close the io stream, indicating the end of the response
  5. And call the addEvent method to encapsulate the ExchangeImpl as an event and put it in the List. As for why we do this, we will continue to analyze

 

Since there is a place to join the List, there is a place to take out the List. Recall that there are two main places we just saw List < event >

One is the 28-30 lines in the ServerImpl attribute, that is to say, it is the ServerImpl attribute

Another place is in the run method of Dispatcher class. I'll talk about it later. You can go back and have a look at where it is

Next, let's talk about this part:

 

public void run() {
    // If the server has been shut down completely, there is no need to deal with it
    while (!finished) {
        try {
            // Here we go. events Take it out and put it in list Inside, and events Reassign empty objects
            List<Event> list = null;
            // Remember we just said, lolock Lock is to prevent addEvent Conflicting operation and fetching operation
            synchronized (lolock) {
                if (events.size() > 0) {
                    list = events;
                    events = new LinkedList<Event>();
                }
            }
      
            // After that, traverse and take out each event,And call handleEvent Method      
            if (list != null) {
                for (Event r: list) {
                    // Let's see here
                    handleEvent (r);
                }
            }

            for (HttpConnection c : connsToRegister) {
                reRegister(c);
            }
            connsToRegister.clear();

handleEvent(Event)

/**
 * Process event, add long connection to the list of connectionsToRegister waiting for re registration
 */
private void handleEvent (Event event) {
    ExchangeImpl t = event.exchange;
    HttpConnection c = t.getConnection();
    try {
        if (event instanceof WriteFinishedEvent) {
            if (terminating) {
                finished = true;
            }
            // Complete the response, deal with some states, you can go to see for yourself, few lines
            responseCompleted (c);
            LeftOverInputStream is = t.getOriginalInputStream();
            if (!is.isEOF()) {
                t.close = true;
            }
            // If idle connections exceed MAX_IDLE_CONNECTIONS(The default is 200, which can be viewed before ServerImpl Properties of,
            // Can't add any more and close the connection
            if (t.close || idleConnections.size() >= MAX_IDLE_CONNECTIONS) {
                c.close();
                allConnections.remove (c);
            } else {
                if (is.isDataBuffered()) {
                    requestStarted (c);
                    handle (c.getChannel(), c);
                } else {
                    // Join connection connectionsToRegister Waiting to re register in the list
                    connectionsToRegister.add (c);
                }
            }
        }
    } catch (IOException e) {
        logger.log (
                Level.FINER, "Dispatcher (1)", e
        );
        c.close();
    }
}

The next step is to traverse the connectionsToRegister list and register the connection in the idleConnections long connection set

for (HttpConnection c : connsToRegister) {
    // Let's see here
    reRegister(c);
}
connsToRegister.clear();

reRegister()

/**
 * Re monitor the previously cancel led key in a non blocking way
 * And add the connection to idle connections
 */
void reRegister (HttpConnection connection) {
    try {
        SocketChannel chan = connection.getChannel();
        chan.configureBlocking (false);
        SelectionKey key = chan.register (selector, SelectionKey.OP_READ);
        key.attach (connection);
        connection.time = getTime() + IDLE_INTERVAL;
        idleConnections.add (connection);
    } catch (IOException e) {
        logger.log(Level.FINER, "Dispatcher(8)", e);
        connection.close();
    }
}

In this way, the request to complete the response is cached in the idleConnection

 

 

Overall flow chart

Starting with an HTTP request

Above is the packet I grabbed. You can see that an http request consists of three parts. The first part is tcp three times handshake to connect to the server, the second part is the main body of information transmission, and the third part is tcp four times handshake to disconnect

The tcp operations of these three parts are abstracted into socket operations. The so-called socket is actually an upper level abstraction of tcp and udp, which is convenient for programmers to call

The most obvious is that accept corresponds to three handshakes

So next, our flowchart will start with an http request, showing which parts of the project correspond to each of the three parts, so that the readers can have a clearer understanding

If you still don't understand, it's recommended to read this article again in front of the picture

 

Finally, in this process, there are the requestStarted() method called to ServerImpl, and the responseCompleted method called when I did not mark requestCompleted and close (these two methods are not in this article, you can trace them to see where they are called). These methods are all attributes of ServerImpl:

private Set<HttpConnection> idleConnections;
// Manage all connections for easy access to stop Disconnect all connections directly in case of
private Set<HttpConnection> allConnections;
// Administration req Connection and rsp Connect to prevent request or response timeout,Timeout disconnected by timed thread
private Set<HttpConnection> reqConnections;
private Set<HttpConnection> rspConnections;

A series of adding and deleting operations have been done to represent the beginning of request, the end of request, the beginning of response, the end of response and the caching of long connection. What is the use of these operations? Cache connection? Not at all. Connection is cached in the key and obtained through attachment. In fact, their real function is to facilitate them to be cleaned up by timed tasks when they are out of time.

 

 

 

Timed tasks ServerTimerTask and ServerTimerTask 1

// In front of us ServerImpl Both of these timing tasks are already running
// This is about 10 seconds to clean up long connections( ServerImpl Li CLOCK_TICK)Run once
class ServerTimerTask extends TimerTask {
    public void run () {
        LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>();
        time = System.currentTimeMillis();
        ticks ++;
        synchronized (idleConnections) {
            for (HttpConnection c : idleConnections) {
                if (c.time <= time) {
                    toClose.add (c);
                }
            }
            for (HttpConnection c : toClose) {
                idleConnections.remove (c);
                allConnections.remove (c);
                // Call here HTTPConnection Of close Method, which cleans the I / O stream and shuts it down channel etc.
                c.close();
            }
        }
    }
}

// This is per second( TIMER_MILLIS)Execute once
class ServerTimerTask1 extends TimerTask {

    // runs every TIMER_MILLIS
    public void run () {
        LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>();
        time = System.currentTimeMillis();
        synchronized (reqConnections) {
            if (MAX_REQ_TIME != -1) {
                for (HttpConnection c : reqConnections) {
                    if (c.creationTime + TIMER_MILLIS + MAX_REQ_TIME <= time) {
                        toClose.add (c);
                    }
                }
                for (HttpConnection c : toClose) {
                    logger.log (Level.FINE, "closing: no request: " + c);
                    reqConnections.remove (c);
                    allConnections.remove (c);
                    c.close();
                }
            }
        }
        toClose = new LinkedList<HttpConnection>();
        synchronized (rspConnections) {
            if (MAX_RSP_TIME != -1) {
                for (HttpConnection c : rspConnections) {
                    if (c.rspStartedTime + TIMER_MILLIS +MAX_RSP_TIME <= time) {
                        toClose.add (c);
                    }
                }
                for (HttpConnection c : toClose) {
                    logger.log (Level.FINE, "closing: no response: " + c);
                    rspConnections.remove (c);
                    allConnections.remove (c);
                    c.close();
                }
            }
        }
    }
}

 

Originally, I just wanted to write a simple httpserver to play with, but I checked a lot of information on the Internet and found that the code quality was a little uneven, so I simply referred to the source code of httpserver in jdk, which was very simple overall. Of course, if there is no special need, it is more valuable to read the source code such as the collection class juc.

 

Finally, the old habit is attached with another picture:

Stay up late and become rubbish! (

Tags: Java socket JSON encoding

Posted on Sun, 05 Apr 2020 04:37:45 -0700 by Avendium