Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java HttpsServer multi threaded

I have set up an HttpsServer in Java. All of my communication works perfectly. I set up multiple contexts, load a self-signed certificate, and even start up based on an external configuration file.

My problem now is getting multiple clients to be able to hit my secure server. To do so, I would like to somehow multi-thread the requests that come in from the HttpsServer but cannot figure out how to do so. Below is my basic HttpsConfiguration.

  HttpsServer server = HttpsServer.create(new InetSocketAddress(secureConnection.getPort()), 0);
  SSLContext sslContext = SSLContext.getInstance("TLS");

  sslContext.init(secureConnection.getKeyManager().getKeyManagers(), secureConnection.getTrustManager().getTrustManagers(), null);

  server.setHttpsConfigurator(new SecureServerConfiguration(sslContext));
  server.createContext("/", new RootHandler());
  server.createContext("/test", new TestHandler());
  server.setExecutor(Executors.newCachedThreadPool());
  server.start();

Where secureConnection is a custom class containing server setup and certificate information.

I attempted to set the executor to Executors.newCachedThreadPool() and a couple of other ones. However, they all produced the same result. Each managed the threads differently but the first request had to finish before the second could process.

I also tried writing my own Executor

public class AsyncExecutor extends ThreadPoolExecutor implements Executor
{
   public static Executor create()
   {
      return new AsyncExecutor();
   }

   public AsyncExecutor()
   {
      super(5, 10, 10000, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(12));
   }

   @Override
   public void execute(Runnable process)
   {
      System.out.println("New Process");

      Thread newProcess = new Thread(process);
      newProcess.setDaemon(false);

      newProcess.start();

      System.out.println("Thread created");
   }
}

Unfortunately, with the same result as the other Executors.

To test I am using Postman to hit the /Test endpoint which is simulating a long running task by doing a Thread.sleep(10000). While that is running, I am using my Chrome browser to hit the root endpoint. The root page does not load until the 10 second sleep is over.

Any thoughts on how to handle multiple concurrent requests to the HTTPS server?

For ease of testing, I replicated my scenario using the standard HttpServer and condensed everything into a single java program.

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

public class Example
{
   private final static int PORT = 80;
   private final static int BACKLOG = 10;

   /**
    * To test hit:
    * <p><b>http://localhost/test</b></p>
    * <p>This will hit the endoint with the thread sleep<br>
    * Then hit:</p>
    * <p><b>http://localhost</b></p>
    * <p>I would expect this to come back right away. However, it does not come back until the
    * first request finishes. This can be tested with only a basic browser.</p>
    * @param args
    * @throws Exception
    */
   public static void main(String[] args) throws Exception
   {
      new Example().start();
   }

   private void start() throws Exception
   {
      HttpServer server = HttpServer.create(new InetSocketAddress(PORT), BACKLOG);

      server.createContext("/", new RootHandler());
      server.createContext("/test", new TestHandler());
      server.setExecutor(Executors.newCachedThreadPool());
      server.start();

      System.out.println("Server Started on " + PORT);
   }

   class RootHandler implements HttpHandler
   {
      @Override
      public void handle(HttpExchange httpExchange) throws IOException
      {
         String body = "<html>Hello World</html>";

         httpExchange.sendResponseHeaders(200, body.length());
         OutputStream outputStream = httpExchange.getResponseBody();

         outputStream.write(body.getBytes("UTF-8"));
         outputStream.close();
      }
   }

   class TestHandler implements HttpHandler
   {
      @Override
      public void handle(HttpExchange httpExchange) throws IOException
      {
         try
         {
            Thread.sleep(10000);
         }
         catch (InterruptedException e)
         {
            e.printStackTrace();
         }

         String body = "<html>Test Handled</html>";

         httpExchange.sendResponseHeaders(200, body.length());
         OutputStream outputStream = httpExchange.getResponseBody();

         outputStream.write(body.getBytes("UTF-8"));
         outputStream.close();
      }
   }
}
like image 480
cain4355 Avatar asked Nov 07 '22 23:11

cain4355


1 Answers

TL;DR: It's OK, just use two different browsers, or specialized tool to test it.

You original implementation is OK and it work as expected, no custom Executor needed. For each request it executes method of "shared" handler class instance. It always picks up free thread from pool, so each method call is executed in different thread.

The problem seems to be, that when you use multiple windows of the same browser to test this behavior... for some reason requests get executed in serialised way (only one at the time). Tested with latest Firefox, Chrome, Edge and Postman. Edge and Postman work as expected. Also anonymous mode of Firefox and Chrome helps.

Same local URL opened at the same time from two Chrome windows. In first the page loaded after 5s, I got Thread.sleep(5000) so that's OK. Second window loaded respons in 8,71s, so there is 3,71s delay of unknown origin.

Chrome windows 1 Chrome windows 2

My guess? Probably some browser internal optimization or failsafe mechanism.

like image 175
teejay Avatar answered Nov 15 '22 12:11

teejay