Thu. Mar 28th, 2024
filters

Last time I showed you how to create a basic HTTP Server that you can put into a JAR easily.  Today I’m going to show you how to extend that to include a ContextHandler and AbstractContextHandler to make it easier to create Handlers for your HTTP Server.  If you remember our properties file from before had a “Handlers” property which was a comma separated list of classes to load as ContextHandlers for our HTTP Server.

Those ContextHandlers are load and initialized in the HttpSystem.start() method.  It takes a ContextHandlerInterface.  Here is the implementation for that interface:

package name.mymiller.httpserver.handlers;

import java.util.List;

import com.sun.net.httpserver.Filter;
import com.sun.net.httpserver.HttpHandler;

/**
 * @author jmiller Interface to create ContextHandler for the HTTP Server
 */
@SuppressWarnings("restriction")
public interface ContextHandlerInterface extends HttpHandler
{
	/**
	 *
	 * @return List of Filter's this handler needs.
	 */
	List<Filter> getFilters();
}

This is really a simple interface.  First notice it extends HttpHandler, this is important and our derived classes will need to implement that API which is a single HttpHandler.handle(HttpExchange exchange).  Next we have a method ContextHandlerInterface.getContext() for returning the Context of this handler.  This is important as our HttpSystem needs to know the URI this ContextHandler will be processing.  Next we have a method ContextHandlerInterface.getFilters() that returns any filters this context needs.  We will discuss those in another post later on.

Now we create an AbstractContextHandler to take care of some of the more mundane items that we don’t want to keep implementing.  Here is the code for AbstractContextHandler:

package name.mymiller.httpserver.handlers;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;

import com.sun.net.httpserver.HttpExchange;

import name.mymiller.extensions.lang.AdvancedString;
import name.mymiller.extensions.log.LogManager;
import name.mymiller.httpserver.HttpConstants;

/**
 * @author jmiller Abstract class to build handlers for URI Requests coming into
 *         the HTTPS erver
 */
@SuppressWarnings("restriction")
public abstract class AbstractContextHandler implements ContextHandlerInterface
{
	/**
	 * Method to handle Delete URI Requests
	 *
	 * @param exchange
	 *            HttpExchange containing the URI information
	 * @param pathInfo
	 *            URI Path specified
	 * @param parameters
	 *            Parameters parsed fromt he URI Quests
	 */
	public void doDelete(final HttpExchange exchange, final String pathInfo, final Map<String, Object> parameters) {

	}

	/**
	 * Method to handle Get URI Requests
	 *
	 * @param exchange
	 *            HttpExchange containing the URI information
	 * @param pathInfo
	 *            URI Path specified
	 * @param parameters
	 *            Parameters parsed fromt he URI Quests
	 * @throws IOException
	 *             Error responding to request
	 */

	public void doGet(final HttpExchange exchange, final String pathInfo, final Map<String, Object> parameters)
			throws IOException
	{

	}

	/**
	 * Method to handle Head URI Requests
	 *
	 * @param exchange
	 *            HttpExchange containing the URI information
	 * @param pathInfo
	 *            URI Path specified
	 * @param parameters
	 *            Parameters parsed fromt he URI Quests
	 * @throws IOException
	 *             Error responding to request
	 */

	public void doHead(final HttpExchange exchange, final String pathInfo, final Map<String, Object> parameters)
			throws IOException
	{

	}

	/**
	 * Method to handle Post URI Requests
	 *
	 * @param exchange
	 *            HttpExchange containing the URI information
	 * @param pathInfo
	 *            URI Path specified
	 * @param parameters
	 *            Parameters parsed fromt he URI Quests
	 * @throws IOException
	 *             Error responding to request
	 */

	public void doPost(final HttpExchange exchange, final String pathInfo, final Map<String, Object> parameters)
			throws IOException
	{

	}

	/**
	 * Method to handle Put URI Requests
	 *
	 * @param exchange
	 *            HttpExchange containing the URI information
	 * @param pathInfo
	 *            URI Path specified
	 * @param parameters
	 *            Parameters parsed fromt he URI Quests
	 */

	public void doPut(final HttpExchange exchange, final String pathInfo, final Map<String, Object> parameters) {

	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.sun.net.httpserver.HttpHandler#handle(com.sun.net.httpserver.
	 * HttpExchange)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void handle(final HttpExchange exchange) throws IOException
	{
		final String context = exchange.getHttpContext().getPath();
		final String pathInfo = exchange.getRequestURI().getRawPath().substring(context.length());
		final AdvancedString advPathInfo = new AdvancedString(pathInfo);
		if (advPathInfo.containsHtml())
		{
			this.sendBadRequest(exchange);
		} else
		{
			Map<String, Object> parameters = null;
			if (exchange.getAttribute("parameters") instanceof Map<?, ?>)
			{
				parameters = (Map<String, Object>) exchange.getAttribute("parameters");
			}

			if (this.isDelete(exchange))
			{
				this.doDelete(exchange, pathInfo, parameters);
			} else if (this.isGet(exchange))
			{
				this.doGet(exchange, pathInfo, parameters);
			} else if (this.isHead(exchange))
			{
				this.doHead(exchange, pathInfo, parameters);
			} else if (this.isPost(exchange))
			{
				this.doPost(exchange, pathInfo, parameters);
			} else if (this.isPut(exchange))
			{
				this.doPut(exchange, pathInfo, parameters);
			} else
			{
				this.sendBadRequest(exchange);
			}
		}
	}

	/**
	 * @param exchange
	 *            HttpExchange containing the information on this request
	 * @return True if this is a Delete URI Request
	 */
	private boolean isDelete(final HttpExchange exchange)
	{
		return HttpConstants.DELETE_REQUEST.equalsIgnoreCase(exchange.getRequestMethod());
	}

	/**
	 * @param exchange
	 *            HttpExchange containing the information on this request
	 * @return True if this is a Get URI Request
	 */

	private boolean isGet(final HttpExchange exchange)
	{
		return HttpConstants.GET_REQUEST.equalsIgnoreCase(exchange.getRequestMethod());
	}

	/**
	 * @param exchange
	 *            HttpExchange containing the information on this request
	 * @return True if this is a Head URI Request
	 */

	private boolean isHead(final HttpExchange exchange)
	{
		return HttpConstants.HEAD_REQUEST.equalsIgnoreCase(exchange.getRequestMethod());
	}

	/**
	 * @param exchange
	 *            HttpExchange containing the information on this request
	 * @return True if this is a Post URI Request
	 */

	private boolean isPost(final HttpExchange exchange)
	{
		return HttpConstants.POST_REQUEST.equalsIgnoreCase(exchange.getRequestMethod());
	}

	/**
	 * @param exchange
	 *            HttpExchange containing the information on this request
	 * @return True if this is a Put URI Request
	 */

	private boolean isPut(final HttpExchange exchange)
	{
		return HttpConstants.PUT_REQUEST.equalsIgnoreCase(exchange.getRequestMethod());
	}

	/**
	 * Send an HTTP Error for Bad Request
	 *
	 * @param exchange
	 *            HttpExchange containing the information on this request
	 * @throws IOException
	 *             Error responding to request
	 *
	 */
	private void sendBadRequest(final HttpExchange exchange) throws IOException
	{
		LogManager.getLogger(this.getClass()).info("HTML found in PathInfo, rejecting");
		exchange.sendResponseHeaders(HttpConstants.HTTP_BAD_REQUEST_STATUS, 0);
		// Write the response string
		final OutputStream os = exchange.getResponseBody();
		os.write("Bad Request Status".getBytes());
		os.close();
	}

	/**
	 * Method to send the HTTP Response
	 *
	 * @param exchange
	 *            HttpExchange to send the response
	 * @param responseCode
	 *            Response code, one of HttpConstants
	 * @param responseBody
	 *            byte[] containing the response to send
	 * @throws IOException
	 *             Error in sending response.
	 */
	protected void sendResponse(final HttpExchange exchange, final int responseCode, final byte[] responseBody)
			throws IOException
	{
		this.sendResponse(exchange, responseCode, responseBody.length);

		if (responseBody.length > 0)
		{
			final OutputStream os = exchange.getResponseBody();
			os.write(responseBody);
			os.close();
		}
	}

	/**
	 * Method to send the HTTP Response
	 *
	 * @param exchange
	 *            HttpExchange to send the response
	 * @param responseCode
	 *            Response code, one of HttpConstants
	 * @param inputStream
	 *            inputStream containing the response to send
	 * @throws IOException
	 *             Error in sending response.
	 */
	protected void sendResponse(final HttpExchange exchange, final int responseCode, final InputStream inputStream)
			throws IOException
	{
		final byte[] bytesToSend = new byte[inputStream.available()];

		final BufferedInputStream bis = new BufferedInputStream(inputStream);
		bis.read(bytesToSend, 0, bytesToSend.length);
		bis.close();

		this.sendResponse(exchange, responseCode, bytesToSend);
	}

	/**
	 * Method to send the HTTP Response Header with the size of the response,
	 *
	 * @param exchange
	 *            HttpExchange to send the response
	 * @param responseCode
	 *            Response code, one of HttpConstants
	 * @param responseSize
	 *            Int indicating the size of the response
	 * @throws IOException
	 *             Error in sending response.
	 */
	protected void sendResponse(final HttpExchange exchange, final int responseCode, final long responseSize)
			throws IOException
	{
		exchange.sendResponseHeaders(responseCode, responseSize);
	}

	/**
	 * Method to send the HTTP Response
	 *
	 * @param exchange
	 *            HttpExchange to send the response
	 * @param responseCode
	 *            Response code, one of HttpConstants
	 * @param responseBody
	 *            String containing the response to send
	 * @throws IOException
	 *             Error in sending response.
	 */
	protected void sendResponse(final HttpExchange exchange, final int responseCode, final String responseBody)
			throws IOException
	{
		if (responseBody != null)
		{
			this.sendResponse(exchange, responseCode, responseBody.getBytes());
		} else
		{
			this.sendResponse(exchange, responseCode, new byte[0]);
		}
	}
}

First thing to notice is that we implement the HttpHandler.handle(HttpExchange exchange) method.  It uses the implemented helper functions to determine the type of HTTP Request that is incoming, and calls out to stub functions based on type; HEAD, GET, POST, PUT, & DELETE.  All you need to do is override the methods your interested in and implement your code. Now we use an AdvancedString to determine if there is any HTML embedded in the URL, if so we abort the request and send an HTTP_BAD_REQUEST_STATUS.

Now your ready to implement your own ContextHandlers, by extending AbstractContextHandler, and implement the ContextHandlerInterface.getContext() & ContextHandlerInterface.getFilters() methods to provide your specifics.  Then override the doHead(), doGet(), doPost(), doPut(), and doDelete()  to implement the final pieces of your ContextHandler. You have multiple methods of sendResponse() to use for sending your response.

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.

One thought on “HTTP Server ContextHandler in a Jar”

Leave a Reply

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

%d