Russell Bateman
January 2018
last update:
This is hastily excerpted code from a project and is incomplete. I use it only for reference and comparison. (See also here.)
We're going to write code for three different APIs:
Let's pretend we need a client that always posts one kind of data and queries that kind of data from a server via fairly static URLs. We wrote the code for these three, different clients in a way to be able to switch between them for benchmarking.
import java.io.IOException; import java.io.InputStream; import java.net.URL; public interface HttpClientOperations { Integer QUERY_TIMEOUT = 1000; // milliseconds String ACCEPT_CHARSET = "Accept-Charset"; String UTF8 = "UTF-8"; String postRecord( final String record ) throws IOException; InputStream getQuery ( final String query ) throws IOException; }
import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import org.slf4j.Logger; public class HttpUrlOperations implements HttpClientOperations { private HttpURLConnection connection; private static Logger logger; private static String postUrlString; // something like "http://localhost:30000/cache/addrecords?store=0" private static URL postURL; public static void injectPostUrl( final String url ) { postUrlString = url; } public static void injectLogger( Logger aLogger ) { logger = aLogger; } private static class OperationsHolder { private static final HttpUrlOperations INSTANCE = new HttpUrlOperations( logger); } public static HttpUrlOperations getInstance() { return OperationsHolder.INSTANCE; } /** * The URL is for POST operations. Note that * connection.setRequestProperty("Connection", "Keep-Alive"); * ...is, in fact, the default so we don't need to make it explicit. * * @param logger if known. */ private HttpUrlOperations( Logger logger ) { HttpUrlOperations.logger = logger; try { postURL = new URL( postUrlString ); } catch( IOException e ) { postURL = null; } logInfo( "Using OkHttpClient client" ); } /** * This posts data being written to the server. */ public String postRecord( final String record ) throws IOException { if( postURL == null ) throw new IOException( "URL to post to was botched at initialization" ); connection = ( HttpURLConnection ) ( postURL.openConnection() ); connection.setRequestMethod( "POST" ); connection.setUseCaches( false ); connection.setDoOutput( true ); connection.setRequestProperty( "Accept", "application/xml,application/json" ); connection.addRequestProperty( "Content-type", "application/xml" ); try { DataOutputStream writer = new DataOutputStream( connection.getOutputStream() ); writer.writeBytes( record ); writer.close(); } catch( IOException e ) { Throwable t = e.getCause(); String message = e.getMessage() + " occurred posting record (" + t.getMessage() + ')'; throw new IOException( message ); } // This is where the request is actually sent. No matter what, // get the response either to return or drain it. InputStream inputStream = connection.getInputStream(); if( inputStream != null ) { try { return StreamUtilities.fromStream( inputStream ); } catch( IOException e ) { Throwable t = e.getCause(); String message = e.getMessage() + " occurred copying response (" + t.getMessage() + ')'; throw new IOException( message ); } finally { inputStream.close(); } } InputStream errorStream = connection.getErrorStream(); try { // the stream must be read through, so we do that. if( inputStream != null ) { StreamUtilities.drainStream( inputStream, logger ); inputStream.close(); } // do similarly for the error stream. if( errorStream != null ) { StreamUtilities.drainStream( errorStream, logger ); errorStream.close(); } } catch( IOException e ) { Throwable t = e.getCause(); String message = e.getMessage() + " occurred draining response and/or error (" + t.getMessage() + ')'; throw new IOException( message ); } finally { if( inputStream != null ) inputStream.close(); if( errorStream != null ) errorStream.close(); } return null; } /** * This gets data from the server. * @param query something like "http://localhost:30000/query?q=" plus the query * @return the server's response as an InputStream. * @throws IOException upon failure. */ public InputStream getQuery( final String query ) throws IOException { connection = ( HttpURLConnection ) ( query.openConnection() ); connection.setRequestProperty( ACCEPT_CHARSET, UTF8 ); connection.setReadTimeout( QUERY_TIMEOUT ); connection.setRequestMethod( "GET" ); connection.setUseCaches( false ); connection.setDoOutput( true ); connection.setRequestProperty( "Accept", "application/xml,application/json" ); try { return connection.getInputStream(); } catch( IOException e ) { Throwable t = e.getCause(); String message = e.getMessage() + " occurred during query (" + t.getMessage() + ')'; throw new IOException( message ); } } public void injectLogging( Logger logger ) { HttpUrlOperations.logger = logger; } private void logInfo( final String info ) { if( logger != null ) logger.info( info ); } }
import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; import org.apache.http.HttpEntity; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; public class ApacheClientOperations implements HttpClientOperations { private static Logger logger; private static CloseableHttpClient client; private static String postURL; // something like "http://localhost:30000/cache/addrecords?store=0" public static void injectPostUrl( final String url ) { postURL = url; } public static void injectLogger( Logger aLogger ) { logger = aLogger; } private static class OperationsHolder { private static final ApacheClientOperations INSTANCE = new ApacheClientOperations( logger); } public static ApacheClientOperations getInstance() { return OperationsHolder.INSTANCE; } /** * The URL is for POST operations. * * @param logger if known. */ private ApacheClientOperations( Logger logger ) { ApacheClientOperations.logger = logger; ApacheClientOperations.client = HttpClients.createDefault(); logInfo( "Using Apache HTTP client" ); } /** * This posts data being written to the server. */ @Override public String postRecord( String record ) throws IOException { HttpPost post = new HttpPost( postURL ); List< NameValuePair > properties = new ArrayList<>(); properties.add( new BasicNameValuePair( "Accept", "application/xml,application/json" ) ); properties.add( new BasicNameValuePair( "Accept-Charset", "charset=utf-8" ) ); properties.add( new BasicNameValuePair( "Content-type", "application/xml" ) ); post.setEntity( new UrlEncodedFormEntity( properties ) ); CloseableHttpResponse response = client.execute( post ); HttpEntity entity = response.getEntity(); try { EntityUtils.consume( entity ); return null; } catch( IOException e ) { Throwable t = e.getCause(); String message = e.getMessage() + " occurred discarding response (" + t.getMessage() + ')'; throw new IOException( message ); } finally { response.close(); } } /** * This gets data from the server. The query is built as a URL before calling. * @param query something like "http://localhost:30000/query?q=" plus the query * @return the server's response as an InputStream. * @throws IOException upon failure. */ @Override public InputStream getQuery( String query ) throws IOException { HttpGet get = new HttpGet( query ); get.setHeader( ACCEPT_CHARSET, UTF8 ); get.setHeader( "Accept", "application/xml,application/json" ); CloseableHttpResponse response = client.execute( get ); HttpEntity entity = response.getEntity(); try { return entity.getContent(); } catch( IOException e ) { Throwable t = e.getCause(); String message = e.getMessage() + " occurred getting query response (" + t.getMessage() + ')'; throw new IOException( message ); } finally { // TODO: Ha! this renders the InputStream we want to return invalid! Whatever shall we do? //response.close(); } } public void injectLogging( Logger logger ) { ApacheClientOperations.logger = logger; } private void logInfo( final String info ) { if( logger != null ) logger.info( info ); } }
import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.MediaType; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; public class OkHttpOperations implements HttpClientOperations { private static Logger logger; private static OkHttpClient okHttpClient; private static String postURL; // something like "http://localhost:30000/cache/addrecords?store=0" public static void injectPostUrl( final String url ) { postURL = url; } public static void injectLogger( Logger aLogger ) { logger = aLogger; } private static class OperationsHolder { private static final OkHttpOperations INSTANCE = new OkHttpOperations( logger); } public static OkHttpOperations getInstance() { return OperationsHolder.INSTANCE; } /** * The URL is for POST operations. * * @param logger if known. */ private OkHttpOperations( Logger logger ) { OkHttpOperations.logger = logger; okHttpClient = new OkHttpClient.Builder() .addInterceptor( new DefaultContentTypeInterceptor( "application/xml" ) ) .readTimeout( QUERY_TIMEOUT, TimeUnit.MILLISECONDS ) .build(); logInfo( "Using OkHttpClient client" ); } private class DefaultContentTypeInterceptor implements Interceptor { private String contentType; public DefaultContentTypeInterceptor( final String contentType ) { this.contentType = contentType; } @Override public Response intercept( Interceptor.Chain chain ) throws IOException { Request originalRequest = chain.request(); Request requestWithUserAgent = originalRequest.newBuilder() .header("Content-Type", contentType ) .build(); return chain.proceed( requestWithUserAgent ); } } /** * This posts data being written to the server. */ public String postRecord( final String record ) throws IOException { Request request = new Request.Builder() .url( postURL ) .post( RequestBody.create( MediaType.parse( "application/xml; charset=utf-8" ), record ) ) .build(); try { Response response = okHttpClient.newCall( request ).execute(); ResponseBody body = response.body(); return( body != null ) ? body.string() : null; } catch( IOException e ) { Throwable t = e.getCause(); String message = e.getMessage() + " occurred posting record (" + t.getMessage() + ')'; throw new IOException( message ); } } /** * This gets data from the server. The query is built as a URL before calling. * @param query something like "http://localhost:30000/query?q=" plus the query * @return the server's response as an InputStream. * @throws IOException upon failure. */ public InputStream getQuery( final String query ) throws IOException { try { Request request = new Request.Builder().url( query ).get().build(); Response response = okHttpClient.newCall( request ).execute(); ResponseBody body = response.body(); if( body == null ) // TODO: learn a lot more about OkHttpClient failures! throw new IOException( "No response or other failure" ); return body.byteStream(); } catch( IOException e ) { Throwable t = e.getCause(); String message = e.getMessage() + " occurred during query (" + t.getMessage() + ')'; throw new IOException( message ); } } public void injectLogging( Logger logger ) { OkHttpOperations.logger = logger; } private void logInfo( final String info ) { if( logger != null ) logger.info( info ); } }
Implemenation of String-from-Stream and drainStream().
import org.slf4j.Logger; import java.io.IOException; import java.io.InputStream; public class StreamUtilities { public static String fromStream( InputStream inputStream ) throws IOException { int ch; StringBuilder sb = new StringBuilder(); while( ( ch = inputStream.read() ) != -1 ) sb.append( ( char ) ch ); return sb.toString(); } private static final Integer DRAINBUFFERSIZE = 65536; private static byte[] drainBuffer = new byte[ DRAINBUFFERSIZE ]; /** * Per https://stackoverflow.com/questions/4767553/safe-use-of-httpurlconnection * and https://stackoverflow.com/questions/9943351/httpsurlconnection-and-keep-alive, * in order for the HttpURLConnection to remain open for continued use, * the response must be completely drained otherwise the connection will have to * close. * * We figure that the underlying HttpURLConnection layer knows when to stop * reading and won't attempt to read too much. * * TODO: how to optimize this? I considered available() and skip(), but I hear * bad things about them. * * @param inputStream with crap in it we don't care about, but must drain away. */ public static void drainStream( InputStream inputStream, Logger logger ) { int thisRead; long bytesRead = 0; try { while( ( thisRead = inputStream.read( drainBuffer ) ) != -1 ) bytesRead += thisRead; } catch( IOException e ) { if( logger != null ) logger.warn( "Failed while draining stream after reading " + bytesRead + " bytes" ); } } }
Some more useful code.
Useful in JUnit-testing web service (especially Jersey) end-points.
package com.windofkeltia.utilities; import java.io.BufferedReader; import java.security.Principal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import javax.servlet.AsyncContext; import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpUpgradeHandler; import javax.servlet.http.Part; import static java.util.Objects.nonNull; /** * This was created as a testing aid. It uses a builder interface * added to (a work in progress) as needs arise. * @author Russell Bateman */ public class HttpServletRequestImpl implements HttpServletRequest { private final Map< String, ? > headers; private Enumeration< String > headerNames; private final String method; private final String url; // protocol :// hostname:port / extra path ? query parameters private final String protocol; private final String hostname; private final String portNumber; private final String extraPath; private final String queryString; private Map< String, String > queryParameters; private List< String > queryParameterNames; protected HttpServletRequestImpl( Builder builder ) { if( nonNull( headers = builder.headers ) ) headerNames = Collections.enumeration( headers.keySet() ); method = builder.method; url = builder.url; String work = url; int colon = work.indexOf( ':' ); protocol = work.substring( 0, colon ); work = work.substring( colon+3 ); colon = work.indexOf( ':' ); hostname = work.substring( 0, colon ); work = work.substring( colon+1 ); int slash = work.indexOf( '/' ); portNumber = work.substring( 0, slash ); work = work.substring( portNumber.length() ); int question = work.indexOf( '?' ); extraPath = work.substring( 0, question ); queryString = work.substring( question+1 ); String[] parametersArray = queryString.split( "&" ); if( parametersArray.length > 0 ) { queryParameters = new HashMap<>(); for( String parameter : parametersArray ) { int equals = parameter.indexOf( '=' ); if( equals == -1 ) { queryParameters.put( parameter, null ); continue; } queryParameters.put( parameter.substring( 0, equals ), parameter.substring( equals+1 ) ); } if( queryParameters.size() > 0 ) { queryParameterNames = new ArrayList<>(); queryParameterNames.addAll( queryParameters.keySet() ); } } } public static class Builder { private Map< String, ? > headers = null; private String method; private String url; public Builder headers ( Map< String, ? > headers ) { this.headers = headers; return this; } public Builder method ( final String method ) { this.method = method; return this; } public Builder url ( final String url ) { this.url = url; return this; } public HttpServletRequestImpl build() { return new HttpServletRequestImpl( this ); } } /** Returns the value of the specified request parameter, a String. */ @Override public String getParameter( String s ) { return queryParameters.get( s ); } @Override public Enumeration< String > getParameterNames() { return Collections.enumeration( queryParameterNames ); } @Override public String[] getParameterValues( String s ) { // TODO: when needed; we don't understand a parameter value as an array yet... return new String[ 0 ]; } @Override public Map< String, String[] > getParameterMap() { return null; } /** Returns the value of the specified request header as a String. */ @Override public String getHeader( String s ) { return headers.get( s ).toString(); } /** Returns the value of the specified request header as an int. */ @Override public int getIntHeader( String s ) { String value = headers.get( s ).toString(); return Integer.parseInt( value ); } /** Returns the value of the specified request header as a long value that represents a Date object. */ @Override public long getDateHeader( String s ) { // String value = headers.get( s ).toString(); // Date date = new Date( value ); return 0L; /* TODO: finish as soon as really needed... */ } /** Returns an enumeration of all the header names this request contains. */ @Override public Enumeration< String > getHeaderNames() { return headerNames; } /** Returns all the values of the specified request header as an Enumeration of String objects. */ @Override public Enumeration< String > getHeaders( String s ) { List< String > values = new ArrayList<>( headers.size() ); for( Map.Entry< String, ? > element : headers.entrySet() ) { Object value = element.getValue(); if( value instanceof String ) { values.add( ( String ) value ); continue; } // TODO: this may need some investigation when 'value' isn't a String... values.add( value.toString() ); } return Collections.enumeration( values ); } @Override public String getAuthType() { return null; /* if not authenticated */ } @Override public Cookie[] getCookies() { return new Cookie[ 0 ]; } @Override public String getMethod() { return method; } @Override public String getPathInfo() { return extraPath; } @Override public String getPathTranslated() { return extraPath; } @Override public String getContextPath() { int slash = extraPath.indexOf( '/', 1 ); return ( slash == -1 ) ? extraPath : extraPath.substring( 0, slash ); } @Override public String getQueryString() { return queryString; } @Override public String getRemoteUser() { return null; } @Override public boolean isUserInRole( String s ) { return false; } @Override public Principal getUserPrincipal() { return null; } @Override public String getRequestedSessionId() { return null; } @Override public String getRequestURI() { return url; } @Override public StringBuffer getRequestURL() { return null; } @Override public String getServletPath() { return null; } @Override public HttpSession getSession( boolean b ) { return null; } @Override public HttpSession getSession() { return null; } @Override public String changeSessionId() { return null; } @Override public boolean isRequestedSessionIdValid() { return false; } @Override public boolean isRequestedSessionIdFromCookie() { return false; } @Override public boolean isRequestedSessionIdFromURL() { return false; } @Override public boolean isRequestedSessionIdFromUrl() { return false; } @Override public boolean authenticate( HttpServletResponse httpServletResponse ) { return false; } @Override public void login( String s, String s1 ) { } @Override public void logout() { } @Override public Collection<Part> getParts() { return null; } @Override public Part getPart( String s ) { return null; } @Override public <T extends HttpUpgradeHandler> T upgrade( Class<T> aClass ) { return null; } @Override public Object getAttribute( String s ) { return null; } @Override public Enumeration<String> getAttributeNames() { return null; } @Override public String getCharacterEncoding() { return null; } @Override public void setCharacterEncoding( String s ) { } @Override public int getContentLength() { return 0; } @Override public long getContentLengthLong() { return 0; } @Override public String getContentType() { return null; } @Override public ServletInputStream getInputStream() { return null; } @Override public String getProtocol() { return protocol; } @Override public String getScheme() { return null; } @Override public String getServerName() { return hostname; } @Override public int getServerPort() { return Integer.parseInt( portNumber ); } @Override public BufferedReader getReader() { return null; } @Override public String getRemoteAddr() { return null; } @Override public String getRemoteHost() { return null; } @Override public void setAttribute( String s, Object o ) { } @Override public void removeAttribute( String s ) { } @Override public Locale getLocale() { return null; } @Override public Enumeration<Locale> getLocales() { return null; } @Override public boolean isSecure() { return false; } @Override public RequestDispatcher getRequestDispatcher( String s ) { return null; } @Override public String getRealPath( String s ) { return null; } @Override public int getRemotePort() { return 0; } @Override public String getLocalName() { return null; } @Override public String getLocalAddr() { return null; } @Override public int getLocalPort() { return 0; } @Override public ServletContext getServletContext() { return null; } @Override public AsyncContext startAsync() throws IllegalStateException { return null; } @Override public AsyncContext startAsync( ServletRequest servletRequest, ServletResponse servletResponse ) throws IllegalStateException { return null; } @Override public boolean isAsyncStarted() { return false; } @Override public boolean isAsyncSupported() { return false; } @Override public AsyncContext getAsyncContext() { return null; } @Override public DispatcherType getDispatcherType() { return null; } }
package com.windofkeltia.trials; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import static java.util.Objects.isNull; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import com.windofkeltia.utilities.HttpServletRequestImpl; import com.windofkeltia.utilities.StringUtilities; import com.windofkeltia.utilities.TestUtilities; /** * Vets the class above. * @author Russell Bateman */ public class HttpServletRequestImplTest { @Rule public TestName name = new TestName(); @After public void tearDown() { } @Before public void setUp() { TestUtilities.setUp( name ); Map< String, String > headerMappings = new HashMap<>( 3 ); headerMappings.put( "ContentType", "application/xml" ); headerMappings.put( "Accept", "application/json" ); request = new HttpServletRequestImpl.Builder() .url( "http://localhost:8080/extra/path?one=this&two=that&three=the%20other%20thing" ) .headers( headerMappings ) .build(); } private static final boolean VERBOSE = TestUtilities.VERBOSE; private HttpServletRequest request; @Test public void testBasics() { String protocol = request.getProtocol(); String hostname = request.getServerName(); int port = request.getServerPort(); if( VERBOSE ) { System.out.println( "Protocol:<n " + protocol ); System.out.println( "Hostname:<n " + hostname ); System.out.println( "Port:<n " + port ); } assertEquals( protocol, "http" ); assertEquals( hostname, "localhost" ); assertEquals( port, 8080 ); String pathInfo = request.getPathInfo(); String contextPath = request.getContextPath(); String pathTranslated = request.getPathTranslated(); if( VERBOSE ) { System.out.println( "Path info:<n " + pathInfo ); System.out.println( "Context path:<n " + contextPath ); System.out.println( "Path translated:<n " + pathTranslated ); } assertEquals( pathInfo, "/extra/path" ); assertEquals( contextPath, "/extra" ); assertEquals( pathTranslated, "/extra/path" ); Enumeration< String > parameterNames = request.getParameterNames(); Enumeration< String > headerNames = request.getHeaderNames(); if( VERBOSE ) { System.out.println( "Parameter names:" ); while( parameterNames.hasMoreElements() ) System.out.println( " " + parameterNames.nextElement() ); System.out.println( "Header names:" ); while( headerNames.hasMoreElements() ) System.out.println( " " + headerNames.nextElement() ); } String accept = request.getHeader( "Accept" ); if( VERBOSE ) System.out.println( "Accept:<n <"" + accept + '<"' ); assertEquals( accept, "application/json" ); String queryString = request.getQueryString(); if( VERBOSE ) System.out.println( "Query parameters:<n <"" + queryString + '<"' ); assertEquals( queryString, "one=this&two=that&three=the%20other%20thing" ); String three = request.getParameter( "three" ); if( VERBOSE ) System.out.println( "Parameter <"three<":<n <"" + three + '<"' ); assertEquals( three, "the%20other%20thing" ); } /** * Demonstrates proper way to dig through headers. Along with getHeader(), * there exists also <tt>getIntHeader()</tt> and getDateHeader() for use * when you know the value will be <tt>int</tt>> or Date. */ @Test public void testHeaders() { Enumeration< String > requestHeaderNames = request.getHeaderNames(); if( isNull( requestHeaderNames ) ) fail( "There was nothing in the request headers" ); int maxNameWidth = 0; // note that once you exhaust the enumeration, you're done--no way around this, so copy it first... List< String > headerNames = new ArrayList<>(); while( requestHeaderNames.hasMoreElements() ) headerNames.add( requestHeaderNames.nextElement() ); // now we can make sensible and reusable use of the list of header names... for( String name : headerNames ) maxNameWidth = Math.max( maxNameWidth, name.length() ); for( String name : headerNames ) { String headerValue = request.getHeader( name ); if( VERBOSE ) System.out.println( " " + StringUtilities.padStringLeft( name, maxNameWidth ) + " = " + headerValue ); } } @Test public void testParameters() { Enumeration< String > requestParameterNames = request.getParameterNames(); if( isNull( requestParameterNames ) ) fail( "There were no request parameters" ); int maxNameWidth = 0; // note that once you exhaust the enumeration, you're done--no way around this, so copy it first... List< String > parameterNames = new ArrayList<>(); while( requestParameterNames.hasMoreElements() ) parameterNames.add( requestParameterNames.nextElement() ); // now we can make sensible and reusable use of the list of header names... for( String name : parameterNames ) maxNameWidth = Math.max( maxNameWidth, name.length() ); for( String name : parameterNames ) { String value = request.getParameter( name ); if( VERBOSE ) System.out.println( " " + StringUtilities.padStringLeft( name, maxNameWidth ) + " = " + value ); } } }
—in case you've never messed with Enumerations collection before.
package com.windofkeltia.trials; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.NoSuchElementException; import org.junit.Test; /** * Enumerations are used in HttpServletRequest. To see how they work, examine * {@link com.windofkeltia.http.HttpServletRequestImplTest}. * @author Russell Bateman */ public class EnumerationTest { @Test public void test() { List< String > headerNames = new ArrayList<>(); headerNames.add( "ContentType" ); headerNames.add( "Accept" ); Enumeration< String > headers = Collections.enumeration( headerNames ); try { while( headers.hasMoreElements() ) { System.out.println( headers.nextElement() ); } } catch( IllegalArgumentException e ) { e.printStackTrace(); } catch( NoSuchElementException e ) { e.printStackTrace(); } } }