Using Sun Java 6 HttpServer to write a functional HTTP test
2012-01-17 17:47
597 查看
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, butin 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.xmlI might want to respond with an
HTTP 200and an XML response body. Another request for
GET /0.xmlmight return an
HTTP 404instead.
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 isa 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:
Create the server
Create a server context and register a request handler
Start the server
Perform your test
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 anHttpServerusing
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
0and 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.HttpServerprovides the
createContext(String path, HttpHandler handler)method to do just that.
Now,
HttpHandleris an interface which declares one method:
handle(HttpExchange)
Obviously,
HttpExchangeis 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
HttpExchangelife 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");
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
BufferedReaderreads 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
HttpServerto shutdown properly. Since we know we’re not serving any other requests, we can safely tell our
HttpServerto 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");
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.
相关文章推荐
- but could not connect over HTTP to server: 'java.sun.com', port: '80'
- java HttpExchange返回中文报错 too many bytes to write to stream
- how to write a minimal http server/client
- JAVA的HttpClient问题:The server failed to respond with a valid HTTP response
- Java Socket Programming in Client/Server Applications - 转自 http://www.developer.com/
- 严重: Failed to destroy end point associated with ProtocolHandler ["http-nio-8080"] java.lang.NullPoin
- solrcloud 报 HTTP Status 503 - Server is shutting down or failed to initialize
- How to Write an Equality Method in Java(之三)Java中如何写equals()方法
- How to write to TO CHAR and TO DATE SQL SERVER
- 【已解决】Exception java.net.ConnectException: Error opening socket to server Connection timed out.
- java.io.FileNotFoundException: http://www.xxxxx.net:8080/test/test/ 403错误
- The server failed to respond with a valid HTTP response
- HTTP Status 500 - java.lang.Long cannot be cast to java.lang.Integer
- 【tomcat】启动报错:Failed to initialize end point associated with ProtocolHandler ["http-apr-8080"] java.lang.Exception: Socket bind failed 和java.net.BindException: Address already in use: JVM_Bind错误解决
- CS0016: 未能写入(A compilation error has occurred.HttpCompileException: error CS0016: Could not write to
- Web service request SetParameters to Report Server http://host/reportserver failed. Error: 请求因 HTTP 状态 401 失败: Unauthorized
- Could not publish to the server. java.lang.NullPointerException
- Jquery UI dialog leverages ajax to load server resource via Httphandler
- 远程mysql_java.sql.SQLException: null, message from server: "Host 'xxx' is not allowed to connect
- java_tomcat_Server at localhost was unable to start within 45 seconds 小喵咪死活启动报错-二