HTTP Server Filters in a Jar

We discussed the HTTP Server ContextHandler in a Jar one of the methods was ContextHandlerInterface.getFilters() this method returns a list of Filters to apply to incoming HTTP Requests.  We subclass com.sun.net.httpserver.Filter for these classes. These can be used to do processing on the request and add attributes to the HTTP Exchange. Filters can block a particular call if needed, or parse data from the call, to set an attribute for you.  This example below is a very common example you can find by searching the Internet for Http Filters.

This example searches the HTTP Exchange for Get and Post parameters, to add to a Map<String,Object> that is adds as an attribute to the HttpExchange.  Making it a convient way for your ContextHandlerInterface to process the request as parameters are easily accessible.  Filters give you a lot of functionality for a simple piece of code.

/*******************************************************************************
 * Copyright 2018 MyMiller Consulting LLC.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy
 * of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 ******************************************************************************/
package name.mymiller.httpserver.filters;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

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

/**
 * @author jmiller Parses the requested URI for parameters and places them into
 *         a map
 */
@SuppressWarnings("restriction")
public class ParameterFilter extends Filter
{

	/**
	 * Retrieve the Parameters Attribute from the Exchange
	 *
	 * @param exchange
	 *            HTTPExchange containing the Parameters Attribute
	 * @return Map<String, Object> containing the parameters
	 */
	@SuppressWarnings("unchecked")
	public static Map<String, Object> getParametersFromExchange(final HttpExchange exchange)
	{
		return ((Map<String, Object>) exchange.getAttribute("parameters"));
	}

	/**
	 * Method to add a parameter to the HttpExchange.
	 *
	 * @param exchange
	 *            HttpExchange to add the parameter to.
	 * @param key
	 *            Key for the parameter
	 * @param value
	 *            Value of the parameter
	 */
	private void addParameter(final HttpExchange exchange, final String key, final String value)
	{
		final Map<String, Object> parameters = ParameterFilter.getParametersFromExchange(exchange);

		final AdvancedString advKey = new AdvancedString(key);
		final AdvancedString advValue = new AdvancedString(value);

		if (!advKey.containsHtml() && !advValue.containsHtml())
		{
			if (parameters.containsKey(key))
			{
				final Object obj = parameters.get(key);
				if (obj instanceof List<?>)
				{
					if (obj instanceof List)
					{
						@SuppressWarnings("unchecked")
						final List<String> values = (List<String>) obj;
						values.add(value);
					}
				} else if (obj instanceof String)
				{
					final List<String> values = new ArrayList<>();
					values.add((String) obj);
					values.add(value);
					parameters.put(key, values);
				}
			} else
			{
				parameters.put(key, advValue);
			}
		}
	}

	/**
	 * Create a Parameters attribute on the HTTP Exchange.
	 *
	 * @param exchange
	 *            HttpExchange to contain the attribute
	 * @return copy of the attribute.
	 */
	private Map<String, Object> createParametersOnExchange(final HttpExchange exchange)
	{
		exchange.setAttribute("parameters", new HashMap<String, Object>());
		return ParameterFilter.getParametersFromExchange(exchange);
	}

	/**
	 * Decode the Key Parameter from the URL
	 *
	 * @param param
	 *            Array of parameters from the Key/Value Pair
	 * @return Key
	 * @throws UnsupportedEncodingException
	 *             Parameter 0 not in standard encoding.
	 */
	private String decodeKey(final String[] param) throws UnsupportedEncodingException
	{
		String key = null;
		if (param.length > 0)
		{
			key = URLDecoder.decode(param[0], System.getProperty("file.encoding"));
		}
		return key;
	}

	/**
	 * Decode the Value Parameter from the URL
	 *
	 * @param param
	 *            Array of parameters from the Key/Value Pair
	 * @return Value
	 * @throws UnsupportedEncodingException
	 *             Parameter 1 not in standard encoding.
	 */
	private String decodeValue(final String[] param) throws UnsupportedEncodingException
	{
		String value = null;
		if (param.length > 1)
		{
			value = URLDecoder.decode(param[1], System.getProperty("file.encoding"));
		}
		return value;
	}

	@Override
	public String description()
	{
		return "Parses the requested URI for parameters";
	}

	@Override
	public void doFilter(final HttpExchange exchange, final Chain chain) throws IOException
	{
		this.createParametersOnExchange(exchange);
		this.parseGetParameters(exchange);
		this.parsePostParameters(exchange);
		chain.doFilter(exchange);
	}

	/**
	 * parse the Get Parameters and add them to the HttpExchange as attribute
	 * parameters
	 *
	 * @param exchange
	 *            HTTPExchange for this URI request
	 * @throws UnsupportedEncodingException
	 *             Unable to decode
	 */
	private void parseGetParameters(final HttpExchange exchange) throws UnsupportedEncodingException
	{
		final URI requestedUri = exchange.getRequestURI();
		final String query = requestedUri.getRawQuery();
		this.parseQuery(query, exchange);
	}

	/**
	 * Parse the Post Parameter and add them to the HttpExcahnge as attribute
	 * parameters
	 *
	 * @param exchange
	 *            HTTPExchange for this URI request
	 * @throws IOException
	 *             Unable to parse request body
	 */
	private void parsePostParameters(final HttpExchange exchange) throws IOException
	{

		if ("post".equalsIgnoreCase(exchange.getRequestMethod()))
		{
			final InputStreamReader isr = new InputStreamReader(exchange.getRequestBody(), "utf-8");
			final BufferedReader br = new BufferedReader(isr);
			final String query = br.readLine();
			this.parseQuery(query, exchange);
		}
	}

	/**
	 * Splits out the parameters from the URI
	 *
	 * @param query
	 *            URI Query for this request
	 * @param exchange
	 *            HttpExchange containing the information on this request
	 * @throws UnsupportedEncodingException
	 *             Unable to decode
	 */
	private void parseQuery(final String query, final HttpExchange exchange) throws UnsupportedEncodingException
	{
		if (query != null)
		{
			final String pairs[] = query.split("[" + HttpConstants.AND_DELIMITER + "]");

			for (final String pair : pairs)
			{
				this.parseQueryPair(exchange, pair);
			}
		}
	}

	/**
	 * Takes a Key / Value Pair from the Query for parsing.
	 *
	 * @param exchange
	 *            HttpExchange containing this query
	 * @param pair
	 *            String for the Key/Value Pair
	 * @throws UnsupportedEncodingException
	 *             Pair encoded incorrectly.
	 */
	private void parseQueryPair(final HttpExchange exchange, final String pair) throws UnsupportedEncodingException
	{
		final String param[] = pair.split("[" + HttpConstants.EQUAL_DELIMITER + "]");

		final String key = this.decodeKey(param);
		final String value = this.decodeValue(param);
		this.addParameter(exchange, key, value);
	}
}

Now if you wanted to create a com.sun.net.httpserver.Filter that could halt the execution, you only need to not call chain.doFilter(exchange) in the doFilter() method, but you must handle the response for the HttpExchange then. Also if you want to do processing after the ContextHandlerInterface has been called, you simple call chain.doFilter(exchange) first in your doFilter() method and do your processing afterward.

A few ideas of what you could do here:

  • Session Filter – attach a session object to the HttpExchange to persist data about a client.
  • Authentication Filter – verify authentication for the client
  • Role Filter – attach attributes indicating the roles a client has.
  • Embedded HTML Filter – prevent HTML code from being embedded.

Just a few of the examples of things you could easily do.

  • Ghostprogrammer
  • 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 Comment

  • you got a very good website, Sword lily I detected it through yahoo.

Leave a Comment

Your email address will not be published. Required fields are marked *

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

%d bloggers like this: