/* * Copyright (c) 2009-2015 * IT-Consulting Stephan Schloepke (http://www.schloepke.de/) * klemm software consulting Mirko Klemm (http://www.klemm-scs.com/) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.jbasics.net.http; import org.jbasics.checker.ContractCheck; import org.jbasics.codec.RFC3548Base64Codec; import org.jbasics.net.mediatype.MediaType; import org.jbasics.types.tuples.Pair; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.util.Date; import java.util.Map; /** * Implementation of {@link HttpAccessor} using the Java URI and URLs as well as Java supplied URLConnection. * * @author Stephan Schloepke */ public class JavaURLHttpAccessor implements HttpAccessor { private static final String HTTP_TRACE_VERB = "TRACE"; //$NON-NLS-1$ private static final String HTTP_OPTIONS_VERB = "OPTIONS"; //$NON-NLS-1$ private static final String HTTP_HEAD_VERB = "HEAD"; //$NON-NLS-1$ private static final String HTTP_DELETE_VERB = "DELETE"; //$NON-NLS-1$ private static final String HTTP_PUT_VERB = "PUT"; //$NON-NLS-1$ private static final String HTTP_POST_VERB = "POST"; //$NON-NLS-1$ private static final String HTTP_GET_VERB = "GET"; //$NON-NLS-1$ private String defaultUserInfo; private URI baseURL; /** * Standard constructor. */ public JavaURLHttpAccessor() { // Nothing to do here; } /** * Creates a JavaURLHttpAccessor with the default basic authentication information. * * @param username The user name for basic authentication (must not be null). * @param password The password for basic authentication (can be null). */ public JavaURLHttpAccessor(final String username, final String password) { ContractCheck.mustNotBeNull(username, "username"); //$NON-NLS-1$ this.defaultUserInfo = username + ":" + (password != null ? password : ""); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Set the base URI any request relies on. If null all requests must be absolute requests. * * @param uri The base URI for relative requests. * * @return The old base URI. */ public URI setBase(final URI uri) { if (uri != null && !uri.isAbsolute()) { throw new RuntimeException("Base URI must be absolute " + uri); //$NON-NLS-1$ } URI temp = this.baseURL; this.baseURL = uri; return temp; } /** * Executes a GET method against the given URI. If the URI is relative the base must be set already. * * @param uri The URI to execute the method on (if relative the base must be set already). * @param meta The meta data for the request. * @param handler The handler to handle the request result. * * @return The HTTP status code of the request. * * @throws IOException If an error reading or writing occurred. */ public int get(final URI uri, final RequestHeaders meta, final RequestHandler handler) throws IOException { return method(JavaURLHttpAccessor.HTTP_GET_VERB, uri, meta, handler); } /** * Executes a POST method against the given URI. If the URI is relative the base must be set already. * * @param uri The URI to execute the method on (if relative the base must be set already). * @param meta The meta data for the request. * @param handler The handler to handle the request result. * * @return The HTTP status code of the request. * * @throws IOException If an error reading or writing occurred. */ public int post(final URI uri, final RequestEntity<?> meta, final RequestHandler handler) throws IOException { return method(JavaURLHttpAccessor.HTTP_POST_VERB, uri, meta, handler); } /** * Executes a PUT method against the given URI. If the URI is relative the base must be set already. * * @param uri The URI to execute the method on (if relative the base must be set already). * @param meta The meta data for the request. * @param handler The handler to handle the request result. * * @return The HTTP status code of the request. * * @throws IOException If an error reading or writing occurred. */ public int put(final URI uri, final RequestEntity<?> meta, final RequestHandler handler) throws IOException { return method(JavaURLHttpAccessor.HTTP_PUT_VERB, uri, meta, handler); } /** * Executes a DELETE method against the given URI. If the URI is relative the base must be set already. * * @param uri The URI to execute the method on (if relative the base must be set already). * @param meta The meta data for the request. * @param handler The handler to handle the request result. * * @return The HTTP status code of the request. * * @throws IOException If an error reading or writing occurred. */ public int delete(final URI uri, final RequestHeaders meta, final RequestHandler handler) throws IOException { return method(JavaURLHttpAccessor.HTTP_DELETE_VERB, uri, meta, handler); } /** * Executes a HEAD method against the given URI. If the URI is relative the base must be set already. * * @param uri The URI to execute the method on (if relative the base must be set already). * @param meta The meta data for the request. * @param handler The handler to handle the request result. * * @return The HTTP status code of the request. * * @throws IOException If an error reading or writing occurred. */ public int head(final URI uri, final RequestHeaders meta, final RequestHandler handler) throws IOException { return method(JavaURLHttpAccessor.HTTP_HEAD_VERB, uri, meta, handler); } /** * Executes a OPTIONS method against the given URI. If the URI is relative the base must be set already. * * @param uri The URI to execute the method on (if relative the base must be set already). * @param meta The meta data for the request. * @param handler The handler to handle the request result. * * @return The HTTP status code of the request. * * @throws IOException If an error reading or writing occurred. */ public int options(final URI uri, final RequestHeaders meta, final RequestHandler handler) throws IOException { return method(JavaURLHttpAccessor.HTTP_OPTIONS_VERB, uri, meta, handler); } /** * Executes a TRACE method against the given URI. If the URI is relative the base must be set already. * * @param uri The URI to execute the method on (if relative the base must be set already). * @param meta The meta data for the request. * @param handler The handler to handle the request result. * * @return The HTTP status code of the request. * * @throws IOException If an error reading or writing occurred. */ public int trace(final URI uri, final RequestHeaders meta, final RequestHandler handler) throws IOException { return method(JavaURLHttpAccessor.HTTP_TRACE_VERB, uri, meta, handler); } /** * Executes the given method against the given URI. If the URI is relative the base must be set already. * * @param method The method to execute (Usually this is GET, PUT, POST or DELETE). * @param uri The URI to execute the method on (if relative the base must be set already). * @param meta The meta data for the request. * @param handler The handler to handle the request result. * * @return The HTTP status code of the request. * * @throws IOException If an error reading or writing occurred. */ private int method(final String method, final URI uri, final RequestHeaders meta, final RequestHandler handler) throws IOException { ContractCheck.mustNotBeNullOrEmpty(method, "method"); //$NON-NLS-1$ ContractCheck.mustNotBeNull(uri, "uri"); //$NON-NLS-1$ ContractCheck.mustNotBeNull(meta, "meta"); //$NON-NLS-1$ ContractCheck.mustNotBeNull(handler, "handler"); //$NON-NLS-1$ HttpURLConnection connection = null; RequestEntity<?> entity = null; if (meta instanceof RequestEntity<?>) { entity = (RequestEntity<?>) meta; } InputStream in = null; try { URL requestURL = resolveURL(uri); connection = (HttpURLConnection) requestURL.openConnection(); connection.setDoInput(true); connection.setDoOutput(entity != null); connection.setRequestMethod(method); fillHeaders(connection, meta); if (entity != null) { fillEntityHeaders(connection, entity); } String userInfo = requestURL.getUserInfo(); if (userInfo == null) { userInfo = this.defaultUserInfo; } if (userInfo != null) { Pair<String, String> authHeader = HttpHeaderCreator.createBasicAuthorization(userInfo); connection.setRequestProperty(authHeader.left(), authHeader.right()); } connection.connect(); if (entity != null) { OutputStream out = null; try { out = connection.getOutputStream(); entity.serializeEntity(out); } finally { if (out != null) { out.flush(); out.close(); } } } in = connection.getInputStream(); ResponseMeta responseMeta = new ResponseMeta(); responseMeta.setMediaType(MediaType.valueOf(connection.getContentType())); responseMeta.setContentLength(connection.getContentLength()); responseMeta.setDate(new Date(connection.getDate())); responseMeta.setLastModified(new Date(connection.getLastModified())); responseMeta.setExpires(new Date(connection.getExpiration())); handler.processInput(responseMeta, in); return connection.getResponseCode(); } finally { try { if (in != null) { in.close(); } } finally { if (connection != null) { connection.disconnect(); } } } } private URL resolveURL(final URI uri) throws IOException { assert uri != null; if (uri.isAbsolute()) { return uri.normalize().toURL(); } else if (this.baseURL != null) { return this.baseURL.resolve(uri).normalize().toURL(); } else { throw new RelativeURIException("URI is relative but no base URI is set. Cannot resolve URL from given URI"); //$NON-NLS-1$ } } private void fillHeaders(final HttpURLConnection connection, final RequestHeaders headers) { assert connection != null && headers != null; addHeader(connection, HTTPHeaderConstants.ACCEPT_HEADER, false, (Object[]) headers.getAcceptMediaTypes()); addHeader(connection, HTTPHeaderConstants.ACCEPT_CHARSET_HEADER, false, (Object[]) headers.getAcceptCharsets()); addHeader(connection, HTTPHeaderConstants.ACCEPT_ENCODING_HEADER, false, (Object[]) headers.getAcceptEncodings()); addHeader(connection, HTTPHeaderConstants.ACCEPT_LANGUAGE_HEADER, false, (Object[]) headers.getAcceptLanguages()); // TODO EntityTAG! // addHeader(connection, HTTPHeaderConstants.IF_MATCH_HEADER, true, headers.getIfMatch()); if (headers.getIfModifiedSince() != null) { connection.setIfModifiedSince(headers.getIfModifiedSince().getTime()); } addHeader(connection, HTTPHeaderConstants.FROM_HEADER, false, headers.getFromEmail()); for (Map.Entry<String, Object[]> temp : headers.getExtensionHeaders().entrySet()) { addHeader(connection, temp.getKey(), false, temp.getValue()); } } private void fillEntityHeaders(final HttpURLConnection connection, final RequestEntity<?> entity) { assert connection != null && entity != null; addHeader(connection, HTTPHeaderConstants.CONTENT_TYPE_HEADER, false, entity.getContentType()); int contentLength = entity.getContentLength(); if (contentLength > 0) { connection.setFixedLengthStreamingMode(contentLength); } addHeader(connection, HTTPHeaderConstants.CONTENT_LANGUAGE_HEADER, false, entity.getContentLanguage()); addHeader(connection, HTTPHeaderConstants.CONTENT_ENCODING_HEADER, false, entity.getContentEncoding()); addHeader(connection, HTTPHeaderConstants.CONTENT_MD5_HEADER, false, RFC3548Base64Codec.INSTANCE.encode(entity.getContentMD5())); } private void addHeader(final URLConnection connection, final String headerName, final boolean quoted, final Object... headers) { assert connection != null && headerName != null; if (headers != null && headers.length > 0) { StringBuilder headerBuilder = new StringBuilder(); for (Object header : headers) { if (header == null) { continue; } if (headerBuilder.length() > 0) { headerBuilder.append(", "); //$NON-NLS-1$ } if (quoted) { headerBuilder.append("\"").append(header.toString()).append("\""); //$NON-NLS-1$ //$NON-NLS-2$ } else { headerBuilder.append(header.toString()); } } if (headerBuilder.length() > 0) { connection.setRequestProperty(headerName, headerBuilder.toString()); } } } }