Thu. Mar 28th, 2024
filters

I recently had the need to be able to access a couple of files from a number of computers and didn’t want to setup network shares and things.  These were a simple text files, that I was logging information into. However I was going to be moving around a number of systems and needed to be able to check on those logs occasionally.  Obviously I didn’t want to install a full blown web-server for a couple of hours of work, to access those files.  Why not create my own HTTP server?  Well it’s much easier than it used to be, which even then it was amazingly easy, but in today’s java world it’s even easier.  In fact I got carried away a little bit on it, and made it a bit fancier than it needed to be.

WARNING: This is NOT a robust, secure HTTP Server, this is a simple HTTP Server, for when you need one, without the hassle of installing one.  If you need robust, and secure, then go install Apache HTTP, Apache Tomcat or something else!

First step you need to use com.sun.net.httpserver.HttpServer.  The main step you need is to create a couple of methods:

  • main() to use to start from the command line easily and to be able to restart if necessary.
  • start() to use to setup the properties for the HttpServer.

Next you’ll want to create a ThreadPoolExecutor to handle the incoming requests.  That’s basically it.  I have included the entire file below along with the properties files for configuration.  I trust that you can follow along!

package name.mymiller.httpserver;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

import com.sun.net.httpserver.HttpServer;
import name.mymiller.extensions.job.AbstractService;
import name.mymiller.extensions.job.JobManager;
import name.mymiller.extensions.lang.reflect.ReflectionUtil;
import name.mymiller.extensions.lang.singleton.SingletonInterface;
import name.mymiller.extensions.log.LogManager;
import name.mymiller.httpserver.handlers.ContextHandlerInterface;
import name.mymiller.httpserver.handlers.HttpHandlerAnnotation;

import javax.management.ReflectionException;


/**
 * @author jmiller HTTP System to start an HTTP Server and load handlers
 */
@SuppressWarnings("restriction")
public class HttpSystem extends AbstractService implements SingletonInterface<HttpSystem>
{
	/**
	 * Configuration Node
	 */
	private static final String	HTTP			= "/http";
	/**
	 * Thread Pool size for handling requests
	 */
	private static int			POOL_SIZE		= 10;
	/**
	 * Max Thread Pool size
	 */
	private static int			MAX_POOL_SIZE	= 25;
	/**
	 * Thread Keep Alive Time
	 */
	private static int			KEEP_ALIVE_TIME	= 30;

	/**
	 * Global Instance for the HTTP System
	 */
	private static HttpSystem globalInstance = null;

	/**
	 * Port to list on
	 */
	private static int			LISTEN_PORT = 8080;

	/**
	 * Backlog for HTTP System
	 */
	private static int			BACK_LOG = 10;

	/**
	 *
	 * @return Global Instance of the HTTPS System
	 */
	public static HttpSystem getInstance()
	{
		if (HttpSystem.globalInstance == null)
		{
			HttpSystem.globalInstance = new HttpSystem();
		}
		return HttpSystem.globalInstance;
	}

	public static int getMaxPoolSize() {
		return MAX_POOL_SIZE;
	}

	public static void setMaxPoolSize(int maxPoolSize) {
		MAX_POOL_SIZE = maxPoolSize;
	}

	public static int getListenPort() {
		return LISTEN_PORT;
	}

	public static void setListenPort(int listenPort) {
		LISTEN_PORT = listenPort;
	}

	public static int getBackLog() {
		return BACK_LOG;
	}

	public static void setBackLog(int backLog) {
		BACK_LOG = backLog;
	}

	/**
	 * HTTP Server reference
	 */
	private HttpServer						httpServer	= null;
	/**
	 * Queue to hold incoming requests
	 */
	private ArrayBlockingQueue<Runnable>	queue		= null;
	/**
	 * Thread Pool Executor for the HTTP Serever
	 */
	private ThreadPoolExecutor				threadPool	= null;

	/**
	 * Instantiates a new simple http server.
	 */
	private HttpSystem()
	{
	}

	/**
	 * @return Active Thread Count
	 */
	public int getActiveCount()
	{
		return this.threadPool.getActiveCount();
	}

	/**
	 *
	 * @return InetSocketAddress of the HTTP Server
	 */
	public InetSocketAddress getAddress()
	{
		return this.httpServer.getAddress();
	}

	/**
	 *
	 * @return Count of Completed Tasks by the Thread Pool
	 */
	public long getCompletedTaskCount()
	{
		return this.threadPool.getCompletedTaskCount();
	}

	/**
	 *
	 * @return Maximum size of the Thread Pool has reached
	 */
	public int getLargestPoolSize()
	{
		return this.threadPool.getLargestPoolSize();
	}

	/**
	 *
	 * @return Maximum size the thread pool can reach
	 */
	public int getMaximumPoolSize()
	{
		return this.threadPool.getMaximumPoolSize();
	}

	/**
	 *
	 * @return Current size of the thread pool
	 */
	public int getPoolSize()
	{
		return this.threadPool.getPoolSize();
	}

	/**
	 *
	 * @return Current Task Count for the Thread Pool
	 */
	public long getTaskCount()
	{
		return this.threadPool.getTaskCount();
	}

	@Override
	protected void service()
	{
		final HttpSystem system = HttpSystem.getInstance();

		synchronized (system)
		{
			while (!system.isShutdown())
			{
				try
				{
					system.wait(1000);
				} catch (final InterruptedException e)
				{
					LogManager.getLogger(HttpSystem.class).info("Http System Thread Interupted:" + e.getMessage());
				}
				if (system.isRestart())
				{
					LogManager.getLogger().info("Restarting HTTP Server System");
					system.stop(HttpConstants.DELAY_TIME);
					system.start();
					system.setRestart(false);
				}
			}
		}
	}

	/**
	 * Start.
	 */
	@Override
	public void start()
	{
		this.start(null);
	}

	/**
	 * Start method accpeing handlers to use for this HTTP Server.
	 * @param handlers
	 */
	public void start(ContextHandlerInterface[] handlers)
	{
		try
		{
			LogManager.getLogger().info("Starting HTTP Server System");

			if (this.threadPool != null)
			{
				this.threadPool.shutdown();
			}
			if (this.queue != null)
			{
				this.queue.clear();
			}
			// Create a default executor
			this.queue = new ArrayBlockingQueue<>(HttpSystem.MAX_POOL_SIZE);
			this.threadPool = new ThreadPoolExecutor(HttpSystem.POOL_SIZE, HttpSystem.MAX_POOL_SIZE,
					HttpSystem.KEEP_ALIVE_TIME, TimeUnit.SECONDS, this.queue);

			// Create HttpServer which is listening on the given port
			this.httpServer = HttpServer.create(
					new InetSocketAddress(HttpSystem.getListenPort()),HttpSystem.getBackLog());
			this.httpServer.setExecutor(this.threadPool);

			for (final ContextHandlerInterface handler : handlers)
			{
				LogManager.getLogger(HttpSystem.class)
						.info("Loading Handler: " + handler.getClass().getCanonicalName());

				final String context = handler.getClass().getAnnotation(HttpHandlerAnnotation.class).context();
				LogManager.getLogger(HttpSystem.class).info("Adding Handler: " + handler + " Context: " + context);

				this.httpServer.createContext(context, handler);
			}
			LogManager.getLogger(HttpSystem.class)
					.info("Server is started and listening on port " + this.httpServer.getAddress().getPort());
			this.httpServer.start();

			JobManager.getInstance().createService("HTTP System", HttpSystem.getInstance());
		} catch (final IOException e)
		{
			LogManager.getLogger(this.getClass()).log(Level.SEVERE, "IOException Error in starting HTTPSystem", e);
		} catch (final SecurityException e)
		{
			LogManager.getLogger(this.getClass()).log(Level.SEVERE, "SecurityException", e);
		} catch (final IllegalArgumentException e)
		{
			LogManager.getLogger(this.getClass()).log(Level.SEVERE, "IllegalArgumentException", e);
		}
	}

	/**
	 * Force a stop of the server
	 *
	 * @param delay
	 *            the maximum time in seconds to wait until exchanges have
	 *            finished.
	 */
	@Override
	protected void stop(final int delay)
	{
		this.httpServer.stop(delay);
		LogManager.getLogger(HttpSystem.class).info("Server is stopped.");
	}
}

Next thing is you need some handlers, and one of my next posts will discuss those!

By Jeffery Miller

I am known for being able to quickly decipher difficult problems to assist development teams in producing a solution. I have been called upon to be the Team Lead for multiple large-scale projects. I have a keen interest in learning new technologies, always ready for a new challenge.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d