Simple Java HTTPS server Simple Java HTTPS server java java

Simple Java HTTPS server


What I eventually used was this:

try {    // Set up the socket address    InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(), config.getHttpsPort());    // Initialise the HTTPS server    HttpsServer httpsServer = HttpsServer.create(address, 0);    SSLContext sslContext = SSLContext.getInstance("TLS");    // Initialise the keystore    char[] password = "simulator".toCharArray();    KeyStore ks = KeyStore.getInstance("JKS");    FileInputStream fis = new FileInputStream("lig.keystore");    ks.load(fis, password);    // Set up the key manager factory    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");    kmf.init(ks, password);    // Set up the trust manager factory    TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");    tmf.init(ks);    // Set up the HTTPS context and parameters    sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);    httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext) {        public void configure(HttpsParameters params) {            try {                // Initialise the SSL context                SSLContext c = SSLContext.getDefault();                SSLEngine engine = c.createSSLEngine();                params.setNeedClientAuth(false);                params.setCipherSuites(engine.getEnabledCipherSuites());                params.setProtocols(engine.getEnabledProtocols());                // Get the default parameters                SSLParameters defaultSSLParameters = c.getDefaultSSLParameters();                params.setSSLParameters(defaultSSLParameters);            } catch (Exception ex) {                ILogger log = new LoggerFactory().getLogger();                log.exception(ex);                log.error("Failed to create HTTPS port");            }        }    });    LigServer server = new LigServer(httpsServer);    joinableThreadList.add(server.getJoinableThread());} catch (Exception exception) {    log.exception(exception);    log.error("Failed to create HTTPS server on port " + config.getHttpsPort() + " of localhost");}

To generate a keystore:

$ keytool -genkeypair -keyalg RSA -alias self_signed -keypass simulator \  -keystore lig.keystore -storepass simulator

See also here.

Potentially storepass and keypass might be different, in which case the ks.load and kmf.init must use storepass and keypass, respectively.


I updated your answer for a HTTPS server (not socket-based). It might help with CSRF and AJAX calls.

import java.io.*;import java.net.InetSocketAddress;import java.lang.*;import java.net.URL;import com.sun.net.httpserver.HttpsServer;import java.security.KeyStore;import javax.net.ssl.KeyManagerFactory;import javax.net.ssl.TrustManagerFactory;import com.sun.net.httpserver.*;import javax.net.ssl.SSLEngine;import javax.net.ssl.SSLParameters;import java.io.InputStreamReader;import java.io.Reader;import java.net.URLConnection;import javax.net.ssl.HostnameVerifier;import javax.net.ssl.HttpsURLConnection;import javax.net.ssl.SSLContext;import javax.net.ssl.SSLSession;import javax.net.ssl.TrustManager;import javax.net.ssl.X509TrustManager;import java.security.cert.X509Certificate;import java.net.InetAddress;import com.sun.net.httpserver.HttpExchange;import com.sun.net.httpserver.HttpHandler;import com.sun.net.httpserver.HttpServer;import com.sun.net.httpserver.HttpsExchange;public class SimpleHTTPSServer {    public static class MyHandler implements HttpHandler {        @Override        public void handle(HttpExchange t) throws IOException {            String response = "This is the response";            HttpsExchange httpsExchange = (HttpsExchange) t;            t.getResponseHeaders().add("Access-Control-Allow-Origin", "*");            t.sendResponseHeaders(200, response.getBytes().length);            OutputStream os = t.getResponseBody();            os.write(response.getBytes());            os.close();        }    }    /**     * @param args     */    public static void main(String[] args) throws Exception {        try {            // setup the socket address            InetSocketAddress address = new InetSocketAddress(8000);            // initialise the HTTPS server            HttpsServer httpsServer = HttpsServer.create(address, 0);            SSLContext sslContext = SSLContext.getInstance("TLS");            // initialise the keystore            char[] password = "password".toCharArray();            KeyStore ks = KeyStore.getInstance("JKS");            FileInputStream fis = new FileInputStream("testkey.jks");            ks.load(fis, password);            // setup the key manager factory            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");            kmf.init(ks, password);            // setup the trust manager factory            TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");            tmf.init(ks);            // setup the HTTPS context and parameters            sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);            httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext) {                public void configure(HttpsParameters params) {                    try {                        // initialise the SSL context                        SSLContext context = getSSLContext();                        SSLEngine engine = context.createSSLEngine();                        params.setNeedClientAuth(false);                        params.setCipherSuites(engine.getEnabledCipherSuites());                        params.setProtocols(engine.getEnabledProtocols());                        // Set the SSL parameters                        SSLParameters sslParameters = context.getSupportedSSLParameters();                        params.setSSLParameters(sslParameters);                    } catch (Exception ex) {                        System.out.println("Failed to create HTTPS port");                    }                }            });            httpsServer.createContext("/test", new MyHandler());            httpsServer.setExecutor(null); // creates a default executor            httpsServer.start();        } catch (Exception exception) {            System.out.println("Failed to create HTTPS server on port " + 8000 + " of localhost");            exception.printStackTrace();        }    }}

To create a self-signed certificate:

keytool -genkeypair -keyalg RSA -alias selfsigned -keystore testkey.jks -storepass password -validity 360 -keysize 2048


With ServerSocket

You can use the class that HttpsServer is built around to be even more light-weight: ServerSocket.

Single-threaded

The following program is a very simple, single-threaded server listening on port 8443. Messages are encrypted with TLS using the keys in ./keystore.jks:

public static void main(String... args) {    var address = new InetSocketAddress("0.0.0.0", 8443);    startSingleThreaded(address);}public static void startSingleThreaded(InetSocketAddress address) {    System.out.println("Start single-threaded server at " + address);    try (var serverSocket = getServerSocket(address)) {        var encoding = StandardCharsets.UTF_8;        // This infinite loop is not CPU-intensive since method "accept" blocks        // until a client has made a connection to the socket        while (true) {            try (var socket = serverSocket.accept();                 // Use the socket to read the client's request                 var reader = new BufferedReader(new InputStreamReader(                         socket.getInputStream(), encoding.name()));                 // Writing to the output stream and then closing it sends                 // data to the client                 var writer = new BufferedWriter(new OutputStreamWriter(                         socket.getOutputStream(), encoding.name()))            ) {                getHeaderLines(reader).forEach(System.out::println);                writer.write(getResponse(encoding));                writer.flush();            } catch (IOException e) {                System.err.println("Exception while handling connection");                e.printStackTrace();            }        }    } catch (Exception e) {        System.err.println("Could not create socket at " + address);        e.printStackTrace();    }}private static ServerSocket getServerSocket(InetSocketAddress address)        throws Exception {    // Backlog is the maximum number of pending connections on the socket,    // 0 means that an implementation-specific default is used    int backlog = 0;    var keyStorePath = Path.of("./keystore.jks");    char[] keyStorePassword = "pass_for_self_signed_cert".toCharArray();    // Bind the socket to the given port and address    var serverSocket = getSslContext(keyStorePath, keyStorePassword)            .getServerSocketFactory()            .createServerSocket(address.getPort(), backlog, address.getAddress());    // We don't need the password anymore → Overwrite it    Arrays.fill(keyStorePassword, '0');    return serverSocket;}private static SSLContext getSslContext(Path keyStorePath, char[] keyStorePass)        throws Exception {    var keyStore = KeyStore.getInstance("JKS");    keyStore.load(new FileInputStream(keyStorePath.toFile()), keyStorePass);    var keyManagerFactory = KeyManagerFactory.getInstance("SunX509");    keyManagerFactory.init(keyStore, keyStorePass);    var sslContext = SSLContext.getInstance("TLS");    // Null means using default implementations for TrustManager and SecureRandom    sslContext.init(keyManagerFactory.getKeyManagers(), null, null);    return sslContext;}private static String getResponse(Charset encoding) {    var body = "The server says hi 👋\r\n";    var contentLength = body.getBytes(encoding).length;    return "HTTP/1.1 200 OK\r\n" +            String.format("Content-Length: %d\r\n", contentLength) +            String.format("Content-Type: text/plain; charset=%s\r\n",                    encoding.displayName()) +            // An empty line marks the end of the response's header            "\r\n" +            body;}private static List<String> getHeaderLines(BufferedReader reader)        throws IOException {    var lines = new ArrayList<String>();    var line = reader.readLine();    // An empty line marks the end of the request's header    while (!line.isEmpty()) {        lines.add(line);        line = reader.readLine();    }    return lines;}

Here's a project using this socket-based approach.

Multi-threaded

To use more than one thread for the server, you can employ a thread pool:

public static void startMultiThreaded(InetSocketAddress address) {    try (var serverSocket = getServerSocket(address)) {        System.out.println("Started multi-threaded server at " + address);        // A cached thread pool with a limited number of threads        var threadPool = newCachedThreadPool(8);        var encoding = StandardCharsets.UTF_8;        // This infinite loop is not CPU-intensive since method "accept" blocks        // until a client has made a connection to the socket        while (true) {            try {                var socket = serverSocket.accept();                // Create a response to the request on a separate thread to                // handle multiple requests simultaneously                threadPool.submit(() -> {                    try ( // Use the socket to read the client's request                          var reader = new BufferedReader(new InputStreamReader(                                  socket.getInputStream(), encoding.name()));                          // Writing to the output stream and then closing it                          // sends data to the client                          var writer = new BufferedWriter(new OutputStreamWriter(                                  socket.getOutputStream(), encoding.name()))                    ) {                        getHeaderLines(reader).forEach(System.out::println);                        writer.write(getResponse(encoding));                        writer.flush();                        // We're done with the connection → Close the socket                        socket.close();                    } catch (Exception e) {                        System.err.println("Exception while creating response");                        e.printStackTrace();                    }                });            } catch (IOException e) {                System.err.println("Exception while handling connection");                e.printStackTrace();            }        }    } catch (Exception e) {        System.err.println("Could not create socket at " + address);        e.printStackTrace();    }}private static ExecutorService newCachedThreadPool(int maximumNumberOfThreads) {    return new ThreadPoolExecutor(0, maximumNumberOfThreads,            60L, TimeUnit.SECONDS,            new SynchronousQueue<>());}

Create a certificate

Use the keytool to create a self-signed certificate (you can get a proper certificate from Let's Encrypt for free):

keytool -genkeypair -keyalg RSA -alias selfsigned -keystore keystore.jks \        -storepass pass_for_self_signed_cert \        -dname "CN=localhost, OU=Developers, O=Bull Bytes, L=Linz, C=AT"

Contact the server

After starting the server, connect to it with curl:

curl -k https://localhost:8443

This will fetch a message from the server:

The server says hi 👋

Inspect which protocol and cipher suite were established by curl and your server with

curl -kv https://localhost:8443

Using JDK 13 and curl 7.66.0, this produced

SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384


Refer to Java Network Programming by Elliotte Rusty Harold for more on the topic.