Clients like REST part 2
October 8th, 2010 § Leave a Comment
This post is the second one of a mini-series:
| Clients Like REST part 1 | Apache HTTP Commons Client |
| Clients Like REST part 2 | Native J2SE API Client |
| Clients Like REST part 3 | JAX-WS client API |
This post will show you how to access the same service – defined through the interface IAutoStatService – using the native J2SE API and in particular how to take full advantage of the HttpURLConnection class. I think that this is a pretty powerful concept: You can access a web service from a Java client without a 3rd party web-service library and without the need to write some type of parser.
Here are some notes about this client implementation:
- It relies on the lowest common denominator to all java apps only, theĀ JDK
- To be complete I will mention that I am using the Jackson JSON processor to construct JSON strings from a POJO and vice versa de-serializing a JSON string coming on an input stream into a POJO; it’s just a convenience tool that does not contradict the above point (using just the native J2SE API)
- Again, there is nothing in this API that is REST-specific or even Web Services-specific; this point is worth stressing, by properly setting up the @javax.ws.rs.Path annotation on the server class the client will be able to call the addAutoStatistics() operation, for example, simply by constructing a POST request in the form http://<host>:<port>/autoStats/AutoStatService/add
- Since we are mainly relying on the HttpURLConnection class you will be forced to think in terms of the details of the HTTP protocol: What to set for Content-Type, Accept or Content-Length request properties? How to decode the HTTP response code? etc… But it’s not that bad and as a bonus your code would be easily portable to another language since these constructs are pretty generic
- You are not limited to a particular data format: if the server side produces JSON, you can de-serialize JSON into POJO, if the server side produces XML, you can de-serialize XML into POJO or use XQuery, etc…
- The testHttpURLConnection(HttpURLConnection connection) method is completely optional but helpful when debugging. It’s mainly included to show you how the various parts of the HTTP message (header/body) are constructed in each of the following cases: POST, DELETE and GET
package com.apptotest.client;
import static org.junit.Assert.*;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.Permission;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
* TODO: We'll use a standard J2SE client (based on {@link HttpURLConnection} to connect
* and test the following REST operations:
* - POST
* - DELETE
* - GET
* - PUT
* This type of coding requires (some) help from the Jackson JSON library to interpret the
* {@link InputStream}.
*/
public class AutoStatServiceImplJ2SEClientTest {
private static final String hostname = "http://localhost:7650/autoStats/AutoStatService";
private URL url;
private HttpURLConnection conn;
private InputStream in;
private OutputStream out;
private ObjectMapper mapper;
@Before
public void setUp() throws Exception {
mapper = new ObjectMapper();
}
@After
public void tearDown() throws Exception {
if (in != null) { in.close(); }
if (out != null) { out.close(); }
if (conn != null) { conn.disconnect(); }
}
/**
* corresponding to URL: http://localhost:7650/autoStats/AutoStatService/add
* @throws Exception
*/
@Test
public final void testAddAutoStat() throws Exception {
AutoStatistics stat = new AutoStatistics("BMW 335i", 9000007L, 150F, 4.7F, 13.3F, 119F);
String jsonValue = mapper.writeValueAsString(stat);
url = new URL(hostname + "/add");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Content-Length", Integer.toString(jsonValue.length()));
conn.getOutputStream().write(jsonValue.getBytes());
conn.getOutputStream().flush();
conn.connect();
testHttpURLConnection(conn);
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
fail("POST method failed: " + conn.getResponseCode() + "\t" + conn.getResponseMessage());
} else {
InputStream responseContent = (InputStream) conn.getContent();
AutoStatistics respStat1 = mapper.readValue(responseContent, AutoStatistics.class);
assertNotNull(respStat1);
assertEquals(stat, respStat1);
}
}
/**
* corresponding to URL: http://localhost:7650/cxfweb_ajax/cxf/rest/AutoStatService/delete/{id}
* where {id} gets expanded by the REST template
*/
@Test
public final void testDeleteAutoStat() throws Exception {
url = new URL(hostname + "/delete/1000001");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("DELETE");
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.connect();
testHttpURLConnection(conn);
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
fail("DELETE method failed: " + conn.getResponseCode() + "\t" + conn.getResponseMessage());
} else {
InputStream responseContent = (InputStream) conn.getContent();
AutoStatistics respStat1 = mapper.readValue(responseContent, AutoStatistics.class);
assertNotNull(respStat1);
assertEquals(new AutoStatistics(), respStat1);
}
}
/**
* corresponding URL: http://localhost:7650/autoStats/AutoStatService/all
* @throws Exception
*/
@Test
public final void testGetAutoStats() throws Exception {
url = new URL(hostname + "/all");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.connect();
in = conn.getInputStream();
testHttpURLConnection(conn);
if (conn.getContentType().equals("application/json")) {
mapper = new ObjectMapper();
AllAutoStatistics stats = mapper.readValue(in, AllAutoStatistics.class);
assertNotNull(stats);
assertNotNull(stats.getAllStats());
Collection<AutoStatistics> allStats = (Collection<AutoStatistics>) stats.getAllStats();
assertTrue(allStats.contains(new AutoStatistics("Alfa Romeo 8C Competizione", 1000002L, 181F, 4.2F, 12.4F, 105F)));
assertTrue(allStats.contains(new AutoStatistics("Cadillac CTS-V", 1000004L, 191F, 4.1F, 12.3F, 114F)));
}
}
/**
* corresponding URL: http://localhost:7650/autoStats/AutoStatService/autostats/1000002
* @throws Exception
*/
@Test
public final void testGetAutoStatAsXml() throws Exception {
url = new URL(hostname + "/autostats/1000002");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Content-Type", "application/xml;charset=UTF-8");
conn.setRequestProperty("Accept", "application/xml");
conn.connect();
in = conn.getInputStream();
testHttpURLConnection(conn);
if (conn.getContentType().equals("application/xml")) {
char[] buff = new char[128];
InputStreamReader isr = new InputStreamReader(in);
StringBuilder builder = new StringBuilder();
while (isr.read(buff) != -1) {
builder.append(buff);
}
assertTrue(builder.length() > 0);
String expectedResponse = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><AutoStatistics><id>1000002</id><maxSpeed>181.0</maxSpeed><name>Alfa Romeo 8C Competizione</name><quarterMileTimeInSecs>12.4</quarterMileTimeInSecs><sixtyToZeroDistanceInFt>105.0</sixtyToZeroDistanceInFt><zeroToSixtyTimeInSecs>4.2</zeroToSixtyTimeInSecs></AutoStatistics>";
assertTrue(builder.toString().contains(expectedResponse));
}
}
/**
* corresponding URL: http://localhost:7650/autoStats/AutoStatService/autostats/1000002
*
* @throws Exception
*/
@Test
public final void testGetAutoStatAsJson() throws Exception {
url = new URL(hostname + "/autostats/1000002");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.connect();
in = conn.getInputStream();
testHttpURLConnection(conn);
if (conn.getContentType().equals("application/json")) {
mapper = new ObjectMapper();
AutoStatistics stat = mapper.readValue(in, AutoStatistics.class);
assertNotNull(stat);
assertEquals("name", "Alfa Romeo 8C Competizione", stat.getName());
assertEquals("id", 1000002L, stat.getId().longValue());
assertEquals("maxSpeed", 181.0F, stat.getMaxSpeed().floatValue(), 0.0);
assertEquals("zeroToSixtyTimeInSecs", 4.2F, stat.getZeroToSixtyTimeInSecs().floatValue(), 0.0);
assertEquals("quarterMileTimeInSecs", 12.4F, stat.getQuarterMileTimeInSecs().floatValue(), 0.0);
assertEquals("sixtyToZeroDistanceInFt", 105.0F, stat.getSixtyToZeroDistanceInFt().floatValue(), 0.0);
}
}
/**
* corresponding URL: http://localhost:7650/autoStats/AutoStatService/edit
* @throws Exception
*/
@Test
public final void testUpdateAutoStat() throws Exception {
AutoStatistics stat = new AutoStatistics("Audi A5 2.0T Quattro - Updated J2SE", 1000003L, 130F, 6.2F, 14.8F, 130F);
String jsonValue = mapper.writeValueAsString(stat);
url = new URL(hostname + "/edit");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Content-Length", Integer.toString(jsonValue.length()));
conn.getOutputStream().write(jsonValue.getBytes());
conn.getOutputStream().flush();
conn.connect();
testHttpURLConnection(conn);
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
fail("POST method failed: " + conn.getResponseCode() + "\t" + conn.getResponseMessage());
} else {
InputStream responseContent = (InputStream) conn.getContent();
AutoStatistics respStat1 = mapper.readValue(responseContent, AutoStatistics.class);
assertNotNull(respStat1);
assertEquals(stat, respStat1);
// and test
url = new URL(hostname + "/all");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.connect();
in = conn.getInputStream();
AllAutoStatistics stats = mapper.readValue(in, AllAutoStatistics.class);
assertNotNull(stats);
assertNotNull(stats.getAllStats());
Collection<AutoStatistics> allStats = stats.getAllStats();
assertTrue(allStats.contains(new AutoStatistics("Audi A5 2.0T Quattro - Updated J2SE", 1000003L, 130F, 6.2F, 14.8F, 130F)));
}
}
/**
* @param connection
* @throws Exception
*/
private void testHttpURLConnection(HttpURLConnection connection) throws Exception {
boolean connAllowUserInteraction = connection.getAllowUserInteraction();
String connContentType = connection.getContentType();
String connContentEncoding = connection.getContentEncoding();
String connRequestMethod = connection.getRequestMethod();
boolean connDoInput = connection.getDoInput();
boolean connDoOutput = connection.getDoOutput();
Permission connPermission = connection.getPermission();
URL connURL = connection.getURL();
Map<String, List<String>> connHeaderFields = connection.getHeaderFields();
System.out.println("connAllowUserInteraction: " + connAllowUserInteraction);
System.out.println("connContentType: " + connContentType);
System.out.println("connContentEncoding: " + connContentEncoding);
System.out.println("connRequestMethod: " + connRequestMethod);
System.out.println("connDoInput: " + connDoInput);
System.out.println("connDoOutput: " + connDoOutput);
System.out.println("connPermission: " + connPermission);
System.out.println("connURL: " + connURL);
if (connHeaderFields != null) {
Set<Entry<String, List<String>>> connHeaderFieldsEntries = connHeaderFields.entrySet();
for (Entry<String, List<String>> entry : connHeaderFieldsEntries) {
System.out.println("connHeaderField: " + entry);
}
}
}
}
Advertisement