JDK 17 jdk.httpserver.jmod - HTTP Server Module

JDK 17 jdk.httpserver.jmod is the JMOD file for JDK 17 HTTP Server module.

JDK 17 HTTP Server module compiled class files are stored in \fyicenter\jdk-17.0.5\jmods\jdk.httpserver.jmod.

JDK 17 HTTP Server module compiled class files are also linked and stored in the \fyicenter\jdk-17.0.5\lib\modules JImage file.

JDK 17 HTTP Server module source code files are stored in \fyicenter\jdk-17.0.5\lib\src.zip\jdk.httpserver.

You can click and view the content of each source code file in the list below.

✍: FYIcenter

sun/net/httpserver/ServerImpl.java

/*
 * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package sun.net.httpserver;

import com.sun.net.httpserver.Filter;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsConfigurator;
import sun.net.httpserver.HttpConnection.State;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executor;

/**
 * Provides implementation for both HTTP and HTTPS
 */
class ServerImpl {

    private String protocol;
    private boolean https;
    private Executor executor;
    private HttpsConfigurator httpsConfig;
    private SSLContext sslContext;
    private ContextList contexts;
    private InetSocketAddress address;
    private ServerSocketChannel schan;
    private Selector selector;
    private SelectionKey listenerKey;
    private final Set<HttpConnection> idleConnections;
    // connections which have been accepted() by the server but which haven't
    // yet sent any byte on the connection yet
    private final Set<HttpConnection> newlyAcceptedConnections;
    private final Set<HttpConnection> allConnections;
    /* following two are used to keep track of the times
     * when a connection/request is first received
     * and when we start to send the response
     */
    private final Set<HttpConnection> reqConnections;
    private final Set<HttpConnection> rspConnections;
    private List<Event> events;
    private final Object lolock = new Object();
    private volatile boolean finished = false;
    private volatile boolean terminating = false;
    private boolean bound = false;
    private boolean started = false;
    private HttpServer wrapper;

    // schedule for the timer task that's responsible for idle connection management
    static final long IDLE_TIMER_TASK_SCHEDULE = ServerConfig.getIdleTimerScheduleMillis();
    static final int MAX_CONNECTIONS = ServerConfig.getMaxConnections();
    static final int MAX_IDLE_CONNECTIONS = ServerConfig.getMaxIdleConnections();
    // schedule for the timer task that's responsible for request/response timeout management
    static final long REQ_RSP_TIMER_SCHEDULE = ServerConfig.getReqRspTimerScheduleMillis();
    static final long MAX_REQ_TIME = getTimeMillis(ServerConfig.getMaxReqTime());
    static final long MAX_RSP_TIME = getTimeMillis(ServerConfig.getMaxRspTime());
    static final boolean reqRspTimeoutEnabled = MAX_REQ_TIME != -1 || MAX_RSP_TIME != -1;
    // the maximum idle duration for a connection which is currently idle but has served
    // some request in the past
    static final long IDLE_INTERVAL = ServerConfig.getIdleIntervalMillis();
    // the maximum idle duration for a newly accepted connection which hasn't yet received
    // the first byte of data on that connection
    static final long NEWLY_ACCEPTED_CONN_IDLE_INTERVAL;
    static {
        // the idle duration of a newly accepted connection is considered to be the least of the
        // configured idle interval and the configured max request time (if any).
        NEWLY_ACCEPTED_CONN_IDLE_INTERVAL = MAX_REQ_TIME > 0
                ? Math.min(IDLE_INTERVAL, MAX_REQ_TIME)
                : IDLE_INTERVAL;
    }

    private Timer timer, timer1;
    private final Logger logger;
    private Thread dispatcherThread;

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

        this.protocol = protocol;
        this.wrapper = wrapper;
        this.logger = System.getLogger ("com.sun.net.httpserver");
        ServerConfig.checkLegacyProperties (logger);
        https = protocol.equalsIgnoreCase ("https");
        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>());
        newlyAcceptedConnections = Collections.synchronizedSet(new HashSet<>());
        timer = new Timer ("idle-timeout-task", true);
        timer.schedule (new IdleTimeoutTask(), IDLE_TIMER_TASK_SCHEDULE, IDLE_TIMER_TASK_SCHEDULE);
        if (reqRspTimeoutEnabled) {
            timer1 = new Timer ("req-rsp-timeout-task", true);
            timer1.schedule (new ReqRspTimeoutTask(), REQ_RSP_TIMER_SCHEDULE, REQ_RSP_TIMER_SCHEDULE);
            logger.log(Level.DEBUG, "HttpServer request/response timeout task schedule ms: ",
                    REQ_RSP_TIMER_SCHEDULE);
            logger.log (Level.DEBUG, "MAX_REQ_TIME:  "+MAX_REQ_TIME);
            logger.log (Level.DEBUG, "MAX_RSP_TIME:  "+MAX_RSP_TIME);
        }
        events = new LinkedList<Event>();
        logger.log (Level.DEBUG, "HttpServer created "+protocol+" "+ addr);
    }

    public void bind (InetSocketAddress addr, int backlog) throws IOException {
        if (bound) {
            throw new BindException ("HttpServer already bound");
        }
        if (addr == null) {
            throw new NullPointerException ("null address");
        }
        ServerSocket socket = schan.socket();
        socket.bind (addr, backlog);
        bound = true;
    }

    public void start () {
        if (!bound || started || finished) {
            throw new IllegalStateException ("server in wrong state");
        }
        if (executor == null) {
            executor = new DefaultExecutor();
        }
        dispatcherThread = new Thread(null, dispatcher, "HTTP-Dispatcher", 0, false);
        started = true;
        dispatcherThread.start();
    }

    public void setExecutor (Executor executor) {
        if (started) {
            throw new IllegalStateException ("server already started");
        }
        this.executor = executor;
    }

    private static class DefaultExecutor implements Executor {
        public void execute (Runnable task) {
            task.run();
        }
    }

    public Executor getExecutor () {
        return executor;
    }

    public void setHttpsConfigurator (HttpsConfigurator config) {
        if (config == null) {
            throw new NullPointerException ("null HttpsConfigurator");
        }
        if (started) {
            throw new IllegalStateException ("server already started");
        }
        this.httpsConfig = config;
        sslContext = config.getSSLContext();
    }

    public HttpsConfigurator getHttpsConfigurator () {
        return httpsConfig;
    }

    public final boolean isFinishing() {
        return finished;
    }

    public void stop (int delay) {
        if (delay < 0) {
            throw new IllegalArgumentException ("negative delay parameter");
        }
        terminating = true;
        try { schan.close(); } catch (IOException e) {}
        selector.wakeup();
        long latest = System.currentTimeMillis() + delay * 1000;
        while (System.currentTimeMillis() < latest) {
            delay();
            if (finished) {
                break;
            }
        }
        finished = true;
        selector.wakeup();
        synchronized (allConnections) {
            for (HttpConnection c : allConnections) {
                c.close();
            }
        }
        allConnections.clear();
        idleConnections.clear();
        newlyAcceptedConnections.clear();
        timer.cancel();
        if (reqRspTimeoutEnabled) {
            timer1.cancel();
        }
        if (dispatcherThread != null && dispatcherThread != Thread.currentThread()) {
            try {
                dispatcherThread.join();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                logger.log (Level.TRACE, "ServerImpl.stop: ", e);
            }
        }
    }

    Dispatcher dispatcher;

    public synchronized HttpContextImpl createContext (String path, HttpHandler handler) {
        if (handler == null || path == null) {
            throw new NullPointerException ("null handler, or path parameter");
        }
        HttpContextImpl context = new HttpContextImpl (protocol, path, handler, this);
        contexts.add (context);
        logger.log (Level.DEBUG, "context created: " + path);
        return context;
    }

    public synchronized HttpContextImpl createContext (String path) {
        if (path == null) {
            throw new NullPointerException ("null path parameter");
        }
        HttpContextImpl context = new HttpContextImpl (protocol, path, null, this);
        contexts.add (context);
        logger.log (Level.DEBUG, "context created: " + path);
        return context;
    }

    public synchronized void removeContext (String path) throws IllegalArgumentException {
        if (path == null) {
            throw new NullPointerException ("null path parameter");
        }
        contexts.remove (protocol, path);
        logger.log (Level.DEBUG, "context removed: " + path);
    }

    public synchronized void removeContext (HttpContext context) throws IllegalArgumentException {
        if (!(context instanceof HttpContextImpl)) {
            throw new IllegalArgumentException ("wrong HttpContext type");
        }
        contexts.remove ((HttpContextImpl)context);
        logger.log (Level.DEBUG, "context removed: " + context.getPath());
    }

    @SuppressWarnings("removal")
    public InetSocketAddress getAddress() {
        return AccessController.doPrivileged(
                new PrivilegedAction<InetSocketAddress>() {
                    public InetSocketAddress run() {
                        return
                            (InetSocketAddress)schan.socket()
                                .getLocalSocketAddress();
                    }
                });
    }

    void addEvent (Event r) {
        synchronized (lolock) {
            events.add (r);
            selector.wakeup();
        }
    }

    /* main server listener task */

    /**
     * The Dispatcher is responsible for accepting any connections and then using those connections
     * to processing any incoming requests. A connection is represented as an instance of
     * sun.net.httpserver.HttpConnection.
     *
     * Connection states:
     *  An instance of HttpConnection goes through the following states:
     *
     *  - NEWLY_ACCEPTED: A connection is marked as newly accepted as soon as the Dispatcher
     *    accept()s a connection. A newly accepted connection is added to a newlyAcceptedConnections
     *    collection. A newly accepted connection also gets added to the allConnections collection.
     *    The newlyAcceptedConnections isn't checked for any size limits, however, if the server is
     *    configured with a maximum connection limit, then the elements in the
     *    newlyAcceptedConnections will never exceed that configured limit (no explicit size checks
     *    are done on the newlyAcceptedConnections collection, since the maximum connection limit
     *    applies to connections across different connection states). A connection in NEWLY_ACCEPTED
     *    state is considered idle and is eligible for idle connection management.
     *
     *  - REQUEST: A connection is marked to be in REQUEST state when the request processing starts
     *    on that connection. This typically happens when the first byte of data is received on a
     *    NEWLY_ACCEPTED connection or when new data arrives on a connection which was previously
     *    in IDLE state. When a connection is in REQUEST state, it implies that the connection is
     *    active and thus isn't eligible for idle connection management. If the server is configured
     *    with a maximum request timeout, then connections in REQUEST state are eligible
     *    for Request/Response timeout management.
     *
     *  - RESPONSE: A connection is marked to be in RESPONSE state when the server has finished
     *    reading the request. A connection is RESPONSE state is considered active and isn't eligible
     *    for idle connection management. If the server is configured with a maximum response timeout,
     *    then connections in RESPONSE state are eligible for Request/Response timeout management.
     *
     *  - IDLE: A connection is marked as IDLE when a request/response cycle (successfully) completes
     *    on that particular connection. Idle connections are held in a idleConnections collection.
     *    The idleConnections collection is limited in size and the size is decided by a server
     *    configuration. Connections in IDLE state get added to the idleConnections collection only
     *    if that collection hasn't reached the configured limit. If a connection has reached IDLE
     *    state and there's no more room in the idleConnections collection, then such a connection
     *    gets closed. Connections in idleConnections collection are eligible for idle connection
     *    management.
     *
     * Idle connection management:
     *  A timer task is responsible for closing idle connections. Each connection that is in a state
     *  which is eligible for idle timeout management (see above section on connection states)
     *  will have a corresponding idle expiration time associated with it. The idle timeout management
     *  task will check the expiration time of each such connection against the current time and will
     *  close the connection if the current time is either equal to or past the expiration time.
     *
     * Request/Response timeout management:
     *  The server can be optionally configured with a maximum request timeout and/or maximum response
     *  timeout. If either of these timeouts have been configured, then an additional timer task is
     *  run by the server. This timer task is then responsible for closing connections which have
     *  been in REQUEST or RESPONSE state for a period of time that exceeds the respective configured
     *  timeouts.
     *
     * Maximum connection limit management:
     *  The server can be optionally configured with a maximum connection limit. A value of 0 or
     *  negative integer is ignored and considered to represent no connection limit. In case of a
     *  positive integer value, any newly accepted connections will be first checked against the
     *  current count of established connections (held by the allConnections collection) and if the
     *  configured limit has reached, then the newly accepted connection will be closed immediately
     *  (even before setting its state to NEWLY_ACCEPTED or adding it to the newlyAcceptedConnections
     *  collection).
     *
     */
    class Dispatcher implements Runnable {

        private void handleEvent (Event r) {
            ExchangeImpl t = r.exchange;
            HttpConnection c = t.getConnection();
            try {
                if (r instanceof WriteFinishedEvent) {

                    logger.log(Level.TRACE, "Write Finished");
                    int exchanges = endExchange();
                    if (terminating && exchanges == 0) {
                        finished = true;
                    }
                    LeftOverInputStream is = t.getOriginalInputStream();
                    if (!is.isEOF()) {
                        t.close = true;
                        if (c.getState() == State.REQUEST) {
                            requestCompleted(c);
                        }
                    }
                    responseCompleted (c);
                    if (t.close || idleConnections.size() >= MAX_IDLE_CONNECTIONS) {
                        c.close();
                        allConnections.remove (c);
                    } else {
                        if (is.isDataBuffered()) {
                            /* don't re-enable the interestops, just handle it */
                            requestStarted (c);
                            handle (c.getChannel(), c);
                        } else {
                            connsToRegister.add (c);
                        }
                    }
                }
            } catch (IOException e) {
                logger.log (
                    Level.TRACE, "Dispatcher (1)", e
                );
                c.close();
            }
        }

        final LinkedList<HttpConnection> connsToRegister =
                new LinkedList<HttpConnection>();

        void reRegister (HttpConnection c) {
            /* re-register with selector */
            try {
                SocketChannel chan = c.getChannel();
                chan.configureBlocking (false);
                SelectionKey key = chan.register (selector, SelectionKey.OP_READ);
                key.attach (c);
                c.selectionKey = key;
                markIdle(c);
            } catch (IOException e) {
                dprint(e);
                logger.log (Level.TRACE, "Dispatcher(8)", e);
                c.close();
            }
        }

        public void run() {
            while (!finished) {
                try {
                    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();

                    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 ();
                        if (key.equals (listenerKey)) {
                            if (terminating) {
                                continue;
                            }
                            SocketChannel chan = schan.accept();
                            // optimist there's a channel
                            if (chan != null) {
                                if (MAX_CONNECTIONS > 0 && allConnections.size() >= MAX_CONNECTIONS) {
                                    // we've hit max limit of current open connections, so we go
                                    // ahead and close this connection without processing it
                                    try {
                                        chan.close();
                                    } catch (IOException ignore) {
                                    }
                                    // move on to next selected key
                                    continue;
                                }

                                // Set TCP_NODELAY, if appropriate
                                if (ServerConfig.noDelay()) {
                                    chan.socket().setTcpNoDelay(true);
                                }
                                chan.configureBlocking (false);
                                SelectionKey newkey =
                                    chan.register (selector, SelectionKey.OP_READ);
                                HttpConnection c = new HttpConnection ();
                                c.selectionKey = newkey;
                                c.setChannel (chan);
                                newkey.attach (c);
                                markNewlyAccepted(c);
                                allConnections.add (c);
                            }
                        } else {
                            try {
                                if (key.isReadable()) {
                                    SocketChannel chan = (SocketChannel)key.channel();
                                    HttpConnection conn = (HttpConnection)key.attachment();

                                    key.cancel();
                                    chan.configureBlocking (true);
                                    if (newlyAcceptedConnections.remove(conn)
                                            || idleConnections.remove(conn)) {
                                        // was either a newly accepted connection or an idle
                                        // connection. In either case, we mark that the request
                                        // has now started on this connection.
                                        requestStarted(conn);
                                    }
                                    handle (chan, conn);
                                } else {
                                    assert false : "Unexpected non-readable key:" + key;
                                }
                            } catch (CancelledKeyException e) {
                                handleException(key, null);
                            } catch (IOException e) {
                                handleException(key, e);
                            }
                        }
                    }
                    // call the selector just to process the cancelled keys
                    selector.selectNow();
                } catch (IOException e) {
                    logger.log (Level.TRACE, "Dispatcher (4)", e);
                } catch (Exception e) {
                    logger.log (Level.TRACE, "Dispatcher (7)", e);
                }
            }
            try {selector.close(); } catch (Exception e) {}
        }

        private void handleException (SelectionKey key, Exception e) {
            HttpConnection conn = (HttpConnection)key.attachment();
            if (e != null) {
                logger.log (Level.TRACE, "Dispatcher (2)", e);
            }
            closeConnection(conn);
        }

        public void handle (SocketChannel chan, HttpConnection conn)
        {
            try {
                Exchange t = new Exchange (chan, protocol, conn);
                executor.execute (t);
            } catch (HttpError e1) {
                logger.log (Level.TRACE, "Dispatcher (4)", e1);
                closeConnection(conn);
            } catch (IOException e) {
                logger.log (Level.TRACE, "Dispatcher (5)", e);
                closeConnection(conn);
            } catch (Throwable e) {
                logger.log (Level.TRACE, "Dispatcher (6)", e);
                closeConnection(conn);
            }
        }
    }

    static boolean debug = ServerConfig.debugEnabled ();

    static synchronized void dprint (String s) {
        if (debug) {
            System.out.println (s);
        }
    }

    static synchronized void dprint (Exception e) {
        if (debug) {
            System.out.println (e);
            e.printStackTrace();
        }
    }

    Logger getLogger () {
        return logger;
    }

    private void closeConnection(HttpConnection conn) {
        conn.close();
        allConnections.remove(conn);
        switch (conn.getState()) {
        case REQUEST:
            reqConnections.remove(conn);
            break;
        case RESPONSE:
            rspConnections.remove(conn);
            break;
        case IDLE:
            idleConnections.remove(conn);
            break;
        case NEWLY_ACCEPTED:
            newlyAcceptedConnections.remove(conn);
            break;
        }
        assert !reqConnections.remove(conn);
        assert !rspConnections.remove(conn);
        assert !idleConnections.remove(conn);
        assert !newlyAcceptedConnections.remove(conn);
    }

        /* per exchange task */

    class Exchange implements Runnable {
        SocketChannel chan;
        HttpConnection connection;
        HttpContextImpl context;
        InputStream rawin;
        OutputStream rawout;
        String protocol;
        ExchangeImpl tx;
        HttpContextImpl ctx;
        boolean rejected = false;

        Exchange (SocketChannel chan, String protocol, HttpConnection conn) throws IOException {
            this.chan = chan;
            this.connection = conn;
            this.protocol = protocol;
        }

        public void run () {
            /* context will be null for new connections */
            logger.log(Level.TRACE, "exchange started");

            context = connection.getHttpContext();
            boolean newconnection;
            SSLEngine engine = null;
            String requestLine = null;
            SSLStreams sslStreams = null;
            try {
                if (context != null ) {
                    this.rawin = connection.getInputStream();
                    this.rawout = connection.getRawOutputStream();
                    newconnection = false;
                } else {
                    /* figure out what kind of connection this is */
                    newconnection = true;
                    if (https) {
                        if (sslContext == null) {
                            logger.log (Level.WARNING,
                                "SSL connection received. No https context created");
                            throw new HttpError ("No SSL context established");
                        }
                        sslStreams = new SSLStreams (ServerImpl.this, sslContext, chan);
                        rawin = sslStreams.getInputStream();
                        rawout = sslStreams.getOutputStream();
                        engine = sslStreams.getSSLEngine();
                        connection.sslStreams = sslStreams;
                    } else {
                        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();
                if (requestLine == null) {
                    /* connection closed */
                    logger.log(Level.DEBUG, "no request line: closing");
                    closeConnection(connection);
                    return;
                }
                logger.log(Level.DEBUG, "Exchange request line: {0}", requestLine);
                int space = requestLine.indexOf (' ');
                if (space == -1) {
                    reject (Code.HTTP_BAD_REQUEST,
                            requestLine, "Bad request line");
                    return;
                }
                String method = requestLine.substring (0, space);
                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);
                start = space+1;
                String version = requestLine.substring (start);
                Headers headers = req.headers();
                /* check key for illegal characters */
                for (var k : headers.keySet()) {
                    if (!isValidHeaderKey(k)) {
                        reject(Code.HTTP_BAD_REQUEST, requestLine,
                                "Header key contains illegal characters");
                        return;
                    }
                }
                /* checks for unsupported combinations of lengths and encodings */
                if (headers.containsKey("Content-Length") &&
                        (headers.containsKey("Transfer-encoding") || headers.get("Content-Length").size() > 1)) {
                    reject(Code.HTTP_BAD_REQUEST, requestLine,
                            "Conflicting or malformed headers detected");
                    return;
                }
                long clen = 0L;
                String headerValue = null;
                List<String> teValueList = headers.get("Transfer-encoding");
                if (teValueList != null && !teValueList.isEmpty()) {
                    headerValue = teValueList.get(0);
                }
                if (headerValue != null) {
                    if (headerValue.equalsIgnoreCase("chunked") && teValueList.size() == 1) {
                        clen = -1L;
                    } else {
                        reject(Code.HTTP_NOT_IMPLEMENTED,
                                requestLine, "Unsupported Transfer-Encoding value");
                        return;
                    }
                } else {
                    headerValue = headers.getFirst("Content-Length");
                    if (headerValue != null) {
                        clen = Long.parseLong(headerValue);
                        if (clen < 0) {
                            reject(Code.HTTP_BAD_REQUEST, requestLine,
                                    "Illegal Content-Length value");
                            return;
                        }
                    }
                    if (clen == 0) {
                        requestCompleted(connection);
                    }
                }
                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 (ctx.getHandler() == null) {
                    reject (Code.HTTP_INTERNAL_ERROR,
                            requestLine, "No handler for context");
                    return;
                }
                tx = new ExchangeImpl (
                    method, uri, req, clen, connection
                );
                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 idleSeconds = (int) (ServerConfig.getIdleIntervalMillis() / 1000);
                        int max=ServerConfig.getMaxIdleConnections();
                        String val = "timeout="+idleSeconds+", max="+max;
                        rheaders.set ("Keep-Alive", val);
                    }
                }

                if (newconnection) {
                    connection.setParameters (
                        rawin, rawout, chan, engine, sslStreams,
                        sslContext, protocol, ctx, rawin
                    );
                }
                /* check if client sent an Expect 100 Continue.
                 * In that case, need to send an interim response.
                 * In future API may be modified to allow app to
                 * be involved in this process.
                 */
                String exp = headers.getFirst("Expect");
                if (exp != null && exp.equalsIgnoreCase ("100-continue")) {
                    logReply (100, requestLine, null);
                    sendReply (
                        Code.HTTP_CONTINUE, false, null
                    );
                }
                /* uf is the list of filters seen/set by the user.
                 * sf is the list of filters established internally
                 * and which are not visible to the user. uc and sc
                 * are the corresponding Filter.Chains.
                 * They are linked together by a LinkHandler
                 * so that they can both be invoked in one call.
                 */
                final List<Filter> sf = ctx.getSystemFilters();
                final List<Filter> uf = ctx.getFilters();

                final Filter.Chain sc = new Filter.Chain(sf, ctx.getHandler());
                final Filter.Chain uc = new Filter.Chain(uf, new LinkHandler (sc));

                /* set up the two stream references */
                tx.getRequestBody();
                tx.getResponseBody();
                if (https) {
                    uc.doFilter (new HttpsExchangeImpl (tx));
                } else {
                    uc.doFilter (new HttpExchangeImpl (tx));
                }

            } catch (IOException e1) {
                logger.log (Level.TRACE, "ServerImpl.Exchange (1)", e1);
                closeConnection(connection);
            } catch (NumberFormatException e2) {
                logger.log (Level.TRACE, "ServerImpl.Exchange (2)", e2);
                reject (Code.HTTP_BAD_REQUEST,
                        requestLine, "NumberFormatException thrown");
            } catch (URISyntaxException e3) {
                logger.log (Level.TRACE, "ServerImpl.Exchange (3)", e3);
                reject (Code.HTTP_BAD_REQUEST,
                        requestLine, "URISyntaxException thrown");
            } catch (Exception e4) {
                logger.log (Level.TRACE, "ServerImpl.Exchange (4)", e4);
                closeConnection(connection);
            } catch (Throwable t) {
                logger.log(Level.TRACE, "ServerImpl.Exchange (5)", t);
                throw t;
            }
        }

        /* used to link to 2 or more Filter.Chains together */

        class LinkHandler implements HttpHandler {
            Filter.Chain nextChain;

            LinkHandler (Filter.Chain nextChain) {
                this.nextChain = nextChain;
            }

            public void handle (HttpExchange exchange) throws IOException {
                nextChain.doFilter (exchange);
            }
        }

        void reject (int code, String requestStr, String message) {
            rejected = true;
            logReply (code, requestStr, message);
            sendReply (
                code, false, "<h1>"+code+Code.msg(code)+"</h1>"+message
            );
            closeConnection(connection);
        }

        void sendReply (
            int code, boolean closeNow, String text)
        {
            try {
                StringBuilder builder = new StringBuilder (512);
                builder.append ("HTTP/1.1 ")
                    .append (code).append (Code.msg(code)).append ("\r\n");

                if (text != null && text.length() != 0) {
                    builder.append ("Content-Length: ")
                        .append (text.length()).append ("\r\n")
                        .append ("Content-Type: text/html\r\n");
                } else {
                    builder.append ("Content-Length: 0\r\n");
                    text = "";
                }
                if (closeNow) {
                    builder.append ("Connection: close\r\n");
                }
                builder.append ("\r\n").append (text);
                String s = builder.toString();
                byte[] b = s.getBytes("ISO8859_1");
                rawout.write (b);
                rawout.flush();
                if (closeNow) {
                    closeConnection(connection);
                }
            } catch (IOException e) {
                logger.log (Level.TRACE, "ServerImpl.sendReply", e);
                closeConnection(connection);
            }
        }

    }

    void logReply (int code, String requestStr, String text) {
        if (!logger.isLoggable(Level.DEBUG)) {
            return;
        }
        if (text == null) {
            text = "";
        }
        String r;
        if (requestStr.length() > 80) {
           r = requestStr.substring (0, 80) + "<TRUNCATED>";
        } else {
           r = requestStr;
        }
        String message = r + " [" + code + " " +
                    Code.msg(code) + "] ("+text+")";
        logger.log (Level.DEBUG, message);
    }

    void delay () {
        Thread.yield();
        try {
            Thread.sleep (200);
        } catch (InterruptedException e) {}
    }

    private int exchangeCount = 0;

    synchronized void startExchange () {
        exchangeCount ++;
    }

    synchronized int endExchange () {
        exchangeCount --;
        assert exchangeCount >= 0;
        return exchangeCount;
    }

    HttpServer getWrapper () {
        return wrapper;
    }

    void requestStarted (HttpConnection c) {
        c.reqStartedTime = System.currentTimeMillis();
        c.setState (State.REQUEST);
        reqConnections.add (c);
    }

    void markIdle(HttpConnection c) {
        c.idleStartTime = System.currentTimeMillis();
        c.setState(State.IDLE);
        idleConnections.add(c);
    }

    void markNewlyAccepted(HttpConnection c) {
        c.idleStartTime = System.currentTimeMillis();
        c.setState(State.NEWLY_ACCEPTED);
        newlyAcceptedConnections.add(c);
    }

    // called after a request has been completely read
    // by the server. This stops the timer which would
    // close the connection if the request doesn't arrive
    // quickly enough. It then starts the timer
    // that ensures the client reads the response in a timely
    // fashion.

    void requestCompleted (HttpConnection c) {
        State s = c.getState();
        assert s == State.REQUEST : "State is not REQUEST ("+s+")";
        reqConnections.remove (c);
        c.rspStartedTime = System.currentTimeMillis();
        rspConnections.add (c);
        c.setState (State.RESPONSE);
    }

    // called after response has been sent
    void responseCompleted (HttpConnection c) {
        State s = c.getState();
        assert s == State.RESPONSE : "State is not RESPONSE ("+s+")";
        rspConnections.remove (c);
        c.setState (State.IDLE);
    }

    /**
     * Responsible for closing connections that have been idle.
     * TimerTask run every CLOCK_TICK ms
     */
    class IdleTimeoutTask extends TimerTask {
        public void run () {
            LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>();
            final long currentTime = System.currentTimeMillis();
            synchronized (idleConnections) {
                final Iterator<HttpConnection> it = idleConnections.iterator();
                while (it.hasNext()) {
                    final HttpConnection c = it.next();
                    if (currentTime - c.idleStartTime >= IDLE_INTERVAL) {
                        toClose.add(c);
                        it.remove();
                    }
                }
            }
            // if any newly accepted connection has been idle (i.e. no byte has been sent on that
            // connection during the configured idle timeout period) then close it as well
            synchronized (newlyAcceptedConnections) {
                final Iterator<HttpConnection> it = newlyAcceptedConnections.iterator();
                while (it.hasNext()) {
                    final HttpConnection c = it.next();
                    if (currentTime - c.idleStartTime >= NEWLY_ACCEPTED_CONN_IDLE_INTERVAL) {
                        toClose.add(c);
                        it.remove();
                    }
                }
            }
            for (HttpConnection c : toClose) {
                allConnections.remove(c);
                c.close();
                if (logger.isLoggable(Level.TRACE)) {
                    logger.log(Level.TRACE, "Closed idle connection " + c);
                }
            }
        }
    }

    /**
     * Responsible for closing connections which have timed out while in REQUEST or RESPONSE state
     */
    class ReqRspTimeoutTask extends TimerTask {

        // runs every TIMER_MILLIS
        public void run () {
            LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>();
            final long currentTime = System.currentTimeMillis();
            synchronized (reqConnections) {
                if (MAX_REQ_TIME != -1) {
                    for (HttpConnection c : reqConnections) {
                        if (currentTime - c.reqStartedTime >= MAX_REQ_TIME) {
                            toClose.add (c);
                        }
                    }
                    for (HttpConnection c : toClose) {
                        logger.log (Level.DEBUG, "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 (currentTime - c.rspStartedTime >= MAX_RSP_TIME) {
                            toClose.add (c);
                        }
                    }
                    for (HttpConnection c : toClose) {
                        logger.log (Level.DEBUG, "closing: no response: " + c);
                        rspConnections.remove (c);
                        allConnections.remove (c);
                        c.close();
                    }
                }
            }
        }
    }

    /*
     * Validates a RFC 7230 header-key.
     */
    static boolean isValidHeaderKey(String token) {
        if (token == null || token.isEmpty()) return false;

        boolean isValidChar;
        char[] chars = token.toCharArray();
        String validSpecialChars = "!#$%&'*+-.^_`|~";
        for (char c : chars) {
            isValidChar = ((c >= 'a') && (c <= 'z')) ||
                          ((c >= 'A') && (c <= 'Z')) ||
                          ((c >= '0') && (c <= '9'));
            if (!isValidChar && validSpecialChars.indexOf(c) == -1) {
                return false;
            }
        }
        return true;
    }

    /**
     * Converts and returns the passed {@code secs} as milli seconds. If the passed {@code secs}
     * is negative or zero or if the conversion from seconds to milli seconds results in a negative
     * number, then this method returns -1.
     */
    private static long getTimeMillis(long secs) {
        if (secs <= 0) {
            return -1;
        }
        final long milli = secs * 1000;
        // this handles potential numeric overflow that may have happened during conversion
        return milli > 0 ? milli : -1;
    }
}

sun/net/httpserver/ServerImpl.java

 

Or download all of them as a single archive file:

File name: jdk.httpserver-17.0.5-src.zip
File size: 75844 bytes
Release date: 2022-09-13
Download 

 

JDK 17 jdk.incubator.foreign.jmod - JDK Incubator Foreign

JDK 17 jdk.hotspot.agent.jmod - Hotspot Agent Module

JDK 17 JMod/Module Files

⇑⇑ FAQ for JDK (Java Development Kit) 17

2023-10-04, 2310👍, 0💬