Tuesday, 14 November 2023

Building a tiny HTTP Server with com.sun.net.httpserver.HttpServer

 

In this article, I'll explain how to create a simple HTTP Server using the com.sun.net.httpserver.HttpServer class.

 

The com.sun.net.httpserver.HttpServer class is a part of the Java Development Kit (JDK) that helps you build a basic HTTP server. It gives you a straightforward way to handle HTTP requests and responses without needing external libraries or frameworks.

 

Advantages of this small server:

 

Embedded Applications: You can put this server inside other applications that need a basic HTTP server. For example, it's handy for serving static files or handling specific data access.

 

Limited Resources: This server is excellent for places where resources are limited. It's designed to be minimal and efficient, making it a good choice when resources are scarce.

 

Learning Core Java: It lets you practice using fundamental Java features like networking, I/O (input/output), and threading. This hands-on experience with core Java APIs can enhance your programming skills.

 

Quick Testing: If you want to test and refine your ideas for HTTP-based applications rapidly, this server is helpful. You can prototype your application swiftly before committing to more complicated frameworks.

 

Educational Purposes: It's a great tool for learning about HTTP server architecture, how requests are handled, and how responses are generated.

 

To sum it up, creating a lightweight HTTP server with the com.sun.net.httpserver.HttpServer class is not only a valuable learning experience but also practical for building small HTTP-based applications in places with limited resources or for quickly testing new ideas.

 

Implementation of tiny http server

At high level TinyHttpServer object is constructed like below.

public TinyHttpServer(int port) throws IOException {
	this.port = port;

	// Create an HttpServer that listens on port.
	httpServer = HttpServer.create(new InetSocketAddress(port), 0);

	// Create a context for the server, specifying the path and the handler to use.
	httpServer.createContext("/", new RootHandler());

	Runtime.getRuntime().addShutdownHook(new Thread() {
		@Override
		public void run() {
			try {
				System.out.println("Stopping server safely");
				httpServer.stop(0);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	});
}

 

As you see the constructor, I set the context to RootHandler. RootHandler implementation is given below.

class RootHandler implements HttpHandler {

	@Override
	public void handle(HttpExchange exchange) throws IOException {
		String requestMethod = exchange.getRequestMethod();
		URI uri = exchange.getRequestURI();
		String requestUrl = uri.getPath();

		try {
			HttpRequestHandler requestHandler = (HttpRequestHandler) newInstance(requestUrl);
			switch (requestMethod) {
			case "GET":
				requestHandler.handleGetRequest(exchange);
				break;
			case "POST":
				requestHandler.handlePostRequest(exchange);
				break;
			case "PUT":
				requestHandler.handlePutRequest(exchange);
				break;
			case "OPTIONS":
				requestHandler.handleOptionsRequest(exchange);
				break;
			case "DELETE":
				requestHandler.handleDeleteRequest(exchange);
				break;
			default:
				HttpResponseUtil.sendResponse(exchange, HttpStatus.METHOD_NOT_ALLOWED, "Method Not Allowed",
						HttpContentType.PLAIN_TEXT);
				break;
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}

	}

}

HttpRequestHandler requestHandler = (HttpRequestHandler) newInstance(requestUrl);

Above statement create a handler instance that is responsible to process given request.

 

HttpRequestHandler interface is defined like below.

public interface HttpRequestHandler {

	public default void handleGetRequest(HttpExchange exchange) throws HttpRequestException {
		throw new HttpRequestException("Functionality is not implemented");
	}

	public default void handlePutRequest(HttpExchange exchange) throws HttpRequestException {
		throw new HttpRequestException("Functionality is not implemented");
	}

	public default void handlePostRequest(HttpExchange exchange) throws HttpRequestException {
		throw new HttpRequestException("Functionality is not implemented");
	}

	public default void handleDeleteRequest(HttpExchange exchange) throws HttpRequestException {
		throw new HttpRequestException("Functionality is not implemented");
	}

	public default void handleOptionsRequest(HttpExchange exchange) throws HttpRequestException {
		System.out.println("Received OPTIONS request for " + HttpRequestPathUtil.path(exchange));
		HttpRequestHeaderUtil.setCorsHeaders(exchange);
		HttpResponseUtil.sendResponse(exchange, HttpStatus.OK);
	}

}

Now we can implement the custom handler by implementing HttpRequestHandler interface.

public class HealthHandler implements HttpRequestHandler {

	private static Map<String, Object> RESPONSE = new HashMap<>();

	static {
		RESPONSE.put("status", "ok");
	}

	@Override
	public void handleGetRequest(HttpExchange exchange) throws HttpRequestException {
		try {

			String json = JsonUtil.marshal(RESPONSE);
			HttpResponseUtil.sendResponse(exchange, HttpStatus.OK, json, HttpContentType.JSON);
		} catch (Exception e) {
			throw new HttpRequestException(e);
		}

	}
}

Set the url path prefix to the handler class. TinyServer class provides an utility method, where you can specify the handler class to process given request uri.

myServer.addMappingClassForRequestPrefix("/health", HealthHandler.class.getName());

Any request that starts with /health is handed over to the HealthHandler class.

 

You can download complete working application from this link.




 

 

No comments:

Post a Comment