Using Sun Java 6 HttpServer to write a functional HTTP test

(I originally planned this to be a single article, but because of the scope decided to split it into two parts. This first part explores the basics of using Sun’s HttpServer to conduct functional HTTP testing. Part 2 revisits the following test using JUnit 4.7’s new interceptors (rules) feature and demonstrates a simpler HTTP handler.)

Forces

At work, we recently had the need to perform functional testing of a custom client that used HTTP as a transport. This isn’t strictly unit testing since we’re conducting actual HTTP over a socket & port instead of stubbing out or mocking the server, but in this case that was the only real way to test the client.

I could’ve fired up a standalone Web server and used that, but decided against it for a couple of reasons.

First, I wanted to have the server respond in a specific way to a particular client request. For example, if the request was for GET /1234.xml I might want to respond with an HTTP 200 and an XML response body. Another request for GET /0.xml might return an HTTP 404 instead.

To do that using, say, a Servlet container would mean writing multiple Servlets (mapped to various request URI) or a ‘rich’ Servlet with additional complexity. I didn’t want to have to write tests to test my test scaffolding!

Secondly, a standalone server would have to be started and stopped outside of our standard compile/test/package process (using Maven). Other people wouldn’t be able to run the tests successfully without having the test server up as well.

Clearly, the best way to go was to use an embedded HTTP server, which would allow us to provide specific responses tailored for each unit test.

As luck would have it, it turns out that Sun’s Java 6 implementation comes with a lightweight HTTP server API built in. Read on as I demonstrate the basic use of Sun’s HTTP server classes to write a functional test.

HTTP server in a box

The heart of our test solution involves taking advantage of the lightweight HTTP server API included in Sun’s Java 6 implementation. Note that since this isn’t part of the Java core API this package may not be available on all Java platforms. If this is a problem, you might be better off using another embedded HTTP server such as Jetty.

The class itself is com.sun.net.httpserver.HttpServer, and here’s how to use it in a nutshell:

  1. Create the server
  2. Create a server context and register a request handler
  3. Start the server
  4. Perform your test
  5. Stop the server, and verify/assert your expected behavior

Now let’s look at each step in detail with corresponding code.

Create the server

We create an HttpServer using HttpServer.create(). To specify a port, we need to pass in an InetSocketAddress:

    InetSocketAddress address = new InetSocketAddress(8000);
    HttpServer httpServer = HttpServer.create(address, 0);

The second parameter to HttpServer.create() is the ‘backlog’, “the maximum number of queued incoming connections to allow on the listening socket”. Since that doesn’t really affect us, we can just pass in a 0 and a system default value is used.

Creating and registering a request handler

Now we get to the meat of actually stubbing our server’s behavior by creating and registering a request handler. HttpServer provides the createContext(String path, HttpHandler handler) method to do just that.

Now, HttpHandler is an interface which declares one method: handle(HttpExchange)

Obviously, HttpExchange is the class we need to work with when responding to HTTP requests. Let’s look at some code and I’ll explain what it does afterwards:

    HttpHandler handler = new HttpHandler() {

        public void handle(HttpExchange exchange) throws IOException {
            byte[] response = "<?xml version=\"1.0\"?>\n<resource id=\"1234\" name=\"test\" />\n".getBytes();
            exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK,
                response.length);
            exchange.getResponseBody().write(response);
            exchange.close();
        }
    };

Basically: we convert our response string into a byte array. We send an HTTP 200 (OK) along with the number of bytes we’re about to send as the response body. We then write out the bytes of our response body, then close the HttpExchange. The complete HttpExchange life cycle is detailed in its API documentation.

We now create a context for the URI we’re interested in, passing in our newly created HttpHandler, then start the server:

    httpServer.createContext("/1234.xml", handler);
    httpServer.start();

At this point, an actual HTTP server will start running in a background thread ready to respond to requests. Let’s exercise our client code:

    URL url = new URL("http://localhost:8000/1234.xml&quot;);
    URLConnection conn = url.openConnection();
    BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
    assertEquals("<?xml version=\"1.0\"?>", in.readLine());
    assertEquals("<resource id=\"1234\" name=\"test\" />", in.readLine());

Our client code connects to our server, retrieves the associated URI, then, using a BufferedReader reads in lines from the input stream. We make a few JUnit assertions on what we received.

The only thing left to do is to stop our HttpServer:

     httpServer.stop(0);

The parameter is the number of seconds (NOTE: not milliseconds) to block to wait for our HttpServer to shutdown properly. Since we know we’re not serving any other requests, we can safely tell our HttpServer to shut down immediately.

At a glance

Here’s our functional test at a glance:

    // create the HttpServer
    InetSocketAddress address = new InetSocketAddress(8000);
    HttpServer httpServer = HttpServer.create(address, 0);

    // create and register our handler
    HttpHandler handler = new HttpHandler() {

        public void handle(HttpExchange exchange) throws IOException {
            byte[] response = "<?xml version=\"1.0\"?>\n<resource id=\"1234\" name=\"test\" />\n".getBytes();
            exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK,
                response.length);
            exchange.getResponseBody().write(response);
            exchange.close();
        }
    };
    httpServer.createContext("/1234.xml", handler);

    // start the server
    httpServer.start();

    // verify our client code
    URL url = new URL("http://localhost:8000/1234.xml&quot;);
    URLConnection conn = url.openConnection();
    BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
    assertEquals("<?xml version=\"1.0\"?>", in.readLine());
    assertEquals("<resource id=\"1234\" name=\"test\" />", in.readLine());

    // stop the server
    httpServer.stop(0);

While functional, I find the above code rather verbose. If I want to write more HTTP tests, I certainly don’t want to keep repeating myself going httpServer.start();httpServer.stop(0);. I especially find it tedious to have to compute the length of the HTTP response body beforehand, then send it along with our HTTP response code before writing out the actual body.

In the next part of this article, we look at using some shiny new features in JUnit 4.7 that lets us write cleaner, more reusable tests that involve pre and post test behavior. Also, I’ll exhibit a helper class that simplifies some of the effort involved in implementing an HttpHandler.

7 thoughts on “Using Sun Java 6 HttpServer to write a functional HTTP test

  1. Hi AliStair,

    I would like to highlight some points here:

    Problem: The embedded http Server is blocking, means if there are 2 HTTP requests, the second request will not be processed till handler for first request returns.

    Workaround:
    Here, i would suggest to add some value (instead of 0) to the backlog count (second param while creating the HTTP server). This will ensure that your second request does not die for some time.

    Also, I am looking out for some code to overcome the above problem, hope I get some inbuilt method for this.
    ~Abhay

    1. Thanks for the comments, Abhay.

      For JUnit testing, unless you’re using a parallel runner then blocking really shouldn’t be a problem—tests are executed sequentially using the standard runner.

      Of course, this may change in the future, or you could be running in an environment that provides transparent parallelism.

      In that case, you might run into compound problems when multiple HttpServers try to bind to the same port!

      In any case, changing the backlog parameter to something other than zero sounds like a good idea. Will probably do that. Thanks!

      Oh, and you might want to look at JettyServerRule, part of junit-rules for code that uses the Jetty Web server instead of Sun’s HttpServer, if that’ll work for you.

  2. To allow parallel requests all that is required is to call setExecutor() on HttpServer prior to calling start(). e.g.
    LinkedBlockingQueue queue = new LinkedBlockingQueue();
    ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 10, 1, TimeUnit.MINUTES, queue);
    httpServer.setExecutor(executor);

Leave a comment