diff --git a/.gitignore b/.gitignore index dc94cc4..b7cb0f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +*.iml +.idea/ + /build/expath-http-client-java-*/ /build/expath-http-client-saxon-*/ /build/expath-http-client-java-*.zip @@ -13,3 +16,6 @@ /http-client-saxon/build/ /http-client-saxon/dist/ /http-client-saxon/src/org/expath/httpclient/saxon/version.properties + +/http-client-java/target/ +/http-client-saxon/target/ diff --git a/http-client-java/pom.xml b/http-client-java/pom.xml new file mode 100644 index 0000000..33ac73b --- /dev/null +++ b/http-client-java/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + + org.expath.http.client + http-client-parent + 1.0-SNAPSHOT + ../http-client-parent/pom.xml + + + http-client-java + jar + + EXPath HTTP Client Java Library + Java Library implementing the core HTTP Client module features + + + scm:git:git://github.com/adamretter/expath-http-client-java.git + scm:git:git://github.com/adamretter/expath-http-client-java.git + http://github.com/adamretter/expath-http-client-java + + + + + + maven-assembly-plugin + + + package + + single + + + + + + src/main/assembly/assembly.xml + + + + + + + + + org.expath.tools + tools-java + + + org.apache.httpcomponents + httpcore + + + org.apache.httpcomponents + httpclient + + + org.ccil.cowan.tagsoup + tagsoup + 1.2.1 + + + org.apache.james + apache-mime4j-core + 0.8.3 + + + commons-logging + commons-logging + + + net.jcip + jcip-annotations + + + com.google.code.findbugs + jsr305 + + + junit + junit + test + + + + diff --git a/http-client-java/src/main/assembly/assembly.xml b/http-client-java/src/main/assembly/assembly.xml new file mode 100644 index 0000000..001c3d9 --- /dev/null +++ b/http-client-java/src/main/assembly/assembly.xml @@ -0,0 +1,19 @@ + + + application + + dir + + + true + + + + / + true + runtime + + + \ No newline at end of file diff --git a/http-client-java/src/net/iharder/Base64.java b/http-client-java/src/main/java/net/iharder/Base64.java similarity index 100% rename from http-client-java/src/net/iharder/Base64.java rename to http-client-java/src/main/java/net/iharder/Base64.java diff --git a/http-client-java/src/main/java/org/expath/httpclient/ContentType.java b/http-client-java/src/main/java/org/expath/httpclient/ContentType.java new file mode 100644 index 0000000..94af7b9 --- /dev/null +++ b/http-client-java/src/main/java/org/expath/httpclient/ContentType.java @@ -0,0 +1,177 @@ +/****************************************************************************/ +/* File: ContentType.java */ +/* Author: F. Georges - fgeorges.org */ +/* Date: 2009-02-22 */ +/* Tags: */ +/* Copyright (c) 2009 Florent Georges (see end of file.) */ +/* ------------------------------------------------------------------------ */ + + +package org.expath.httpclient; + +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.NameValuePair; + +import javax.annotation.Nullable; + +/** + * Represent a Content-Type header. + *

+ * Provide the ability to get the boundary param in case of a multipart + * content type on the one hand, and the ability to get only the MIME type + * string without any param on the other hand. + * + * @author Florent Georges + */ +public class ContentType { + + public ContentType(final String type, final String charset, final String boundary) { + this.myType = type; + this.myCharset = charset; + this.myBoundary = boundary; + } + + public static ContentType parse(@Nullable final Header header, @Nullable final String overrideType, @Nullable final String defaultCharset) throws HttpClientException { + final String type; + final String charset; + final String boundary; + + if (overrideType != null) { + // get the internet media type from the override + type = extractMediaTypeFromContentType(overrideType); + + // does the override contain a charset? + if (overrideType.indexOf("charset=") > -1) { + // get the charset from the override + charset = overrideType.replaceFirst(".+charset=([^;\\s]+).*", "$1"); + } else { + // get the charset from the header or the default + if (header == null || !"Content-Type".equalsIgnoreCase(header.getName())) { + throw new HttpClientException("Header is not content type"); + } + final HeaderElement[] headerElements = header.getElements(); + if (headerElements.length > 1) { + throw new HttpClientException("Multiple Content-Type headers"); + } + + final NameValuePair headerCharset = headerElements[0].getParameterByName("charset"); + if (headerCharset != null) { + charset = headerCharset.getValue(); + } else { + charset = defaultCharset; + } + } + + // does the override contain a boundary? + if (overrideType.indexOf("boundary=") > -1) { + boundary = overrideType.replaceFirst(".+boundary=([^;\\s]+).*", "$1"); + } else { + // get the boundary from the header or null + if (header == null || !"Content-Type".equalsIgnoreCase(header.getName())) { + throw new HttpClientException("Header is not content type"); + } + final HeaderElement[] headerElements = header.getElements(); + if (headerElements.length > 1) { + throw new HttpClientException("Multiple Content-Type headers"); + } + + final NameValuePair headerBoundary = headerElements[0].getParameterByName("boundary"); + boundary = headerBoundary == null ? null : headerBoundary.getValue(); + } + + } else { + // get the internet media type from the header + if (header == null || !"Content-Type".equalsIgnoreCase(header.getName())) { + throw new HttpClientException("Header is not content type"); + } + final HeaderElement[] headerElements = header.getElements(); + if (headerElements.length > 1) { + throw new HttpClientException("Multiple Content-Type headers"); + } + + type = extractMediaTypeFromContentType(header.getValue()); + + // get the charset from the header or the default + final NameValuePair headerCharset = headerElements[0].getParameterByName("charset"); + if (headerCharset != null) { + charset = headerCharset.getValue(); + } else { + charset = defaultCharset; + } + + // get the boundary from the header + final NameValuePair headerBoundary = headerElements[0].getParameterByName("boundary"); + boundary = headerBoundary == null ? null : headerBoundary.getValue(); + } + + return new ContentType(type, charset, boundary); + } + + private static String extractMediaTypeFromContentType(final String contentType) { + final int idxParamSeparator = contentType.indexOf(';'); + if (idxParamSeparator > -1) { + return contentType.substring(0, idxParamSeparator); + } else { + return contentType; + } + } + + @Nullable + public String getType() { + return myType; + } + + @Nullable + public String getCharset() { + return myCharset; + } + + @Nullable + public String getBoundary() { + return myBoundary; + } + + @Nullable + public String getValue() { + final StringBuilder builder = new StringBuilder(myType); + if (myCharset != null) { + builder.append("; charset=").append(myCharset); + } + if (myBoundary != null) { + builder.append("; boundary=").append(myCharset); + } + + return builder.toString(); + } + + @Override + public String toString() { + return getValue(); + } + + private final String myType; + private final String myCharset; + private final String myBoundary; +} + + +/* ------------------------------------------------------------------------ */ +/* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS COMMENT. */ +/* */ +/* The contents of this file are subject to the Mozilla Public License */ +/* Version 1.0 (the "License"); you may not use this file except in */ +/* compliance with the License. You may obtain a copy of the License at */ +/* http://www.mozilla.org/MPL/. */ +/* */ +/* Software distributed under the License is distributed on an "AS IS" */ +/* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See */ +/* the License for the specific language governing rights and limitations */ +/* under the License. */ +/* */ +/* The Original Code is: all this file. */ +/* */ +/* The Initial Developer of the Original Code is Florent Georges. */ +/* */ +/* Contributor(s): none. */ +/* ------------------------------------------------------------------------ */ diff --git a/http-client-java/src/org/expath/httpclient/HeaderSet.java b/http-client-java/src/main/java/org/expath/httpclient/HeaderSet.java similarity index 80% rename from http-client-java/src/org/expath/httpclient/HeaderSet.java rename to http-client-java/src/main/java/org/expath/httpclient/HeaderSet.java index 5354cfc..8fb4933 100644 --- a/http-client-java/src/org/expath/httpclient/HeaderSet.java +++ b/http-client-java/src/main/java/org/expath/httpclient/HeaderSet.java @@ -101,39 +101,6 @@ public Header getFirstHeader(String name) return null; } - public String getFirstHeaderWithoutParam(String name) - throws HttpClientException - { - Header h = getFirstHeader(name); - return getHeaderWithoutParam(h); - } - - public static String getValueWithoutParam(String header_value) - throws HttpClientException - { - Header h = new BasicHeader("X-Dummy", header_value); - return getHeaderWithoutParam(h); - } - - public static String getHeaderWithoutParam(Header header) - throws HttpClientException - { - // get the content type, only the mime string, like "type/subtype" - if ( header != null ) { - HeaderElement[] elems = header.getElements(); - if ( elems == null ) { - return null; - } - else if ( elems.length == 1 ) { - return elems[0].getName(); - } - else { - throw new HttpClientException("Multiple Content-Type headers"); - } - } - return null; - } - private List

myHeaders; } diff --git a/http-client-java/src/org/expath/httpclient/HttpClient.java b/http-client-java/src/main/java/org/expath/httpclient/HttpClient.java similarity index 92% rename from http-client-java/src/org/expath/httpclient/HttpClient.java rename to http-client-java/src/main/java/org/expath/httpclient/HttpClient.java index 4de2782..7ee5233 100644 --- a/http-client-java/src/org/expath/httpclient/HttpClient.java +++ b/http-client-java/src/main/java/org/expath/httpclient/HttpClient.java @@ -118,19 +118,14 @@ private Result sendOnce(Result result, URI uri, HttpRequest request, RequestPars { HttpConnection conn = new ApacheHttpConnection(uri); try { - if ( parser.getSendAuth() ) { + HttpResponse response = request.send(result, conn, parser.getCredentials()); + if ( response.getStatus() == 401 ) { + conn.disconnect(); + conn = new ApacheHttpConnection(uri); + // create a new result, and throw the old one away + result = result.makeNewResult(); request.send(result, conn, parser.getCredentials()); } - else { - HttpResponse response = request.send(result, conn, null); - if ( response.getStatus() == 401 ) { - conn.disconnect(); - conn = new ApacheHttpConnection(uri); - // create a new result, and throw the old one away - result = result.makeNewResult(); - request.send(result, conn, parser.getCredentials()); - } - } } finally { conn.disconnect(); diff --git a/http-client-java/src/org/expath/httpclient/HttpClientException.java b/http-client-java/src/main/java/org/expath/httpclient/HttpClientException.java similarity index 100% rename from http-client-java/src/org/expath/httpclient/HttpClientException.java rename to http-client-java/src/main/java/org/expath/httpclient/HttpClientException.java diff --git a/http-client-java/src/org/expath/httpclient/HttpConnection.java b/http-client-java/src/main/java/org/expath/httpclient/HttpConnection.java similarity index 95% rename from http-client-java/src/org/expath/httpclient/HttpConnection.java rename to http-client-java/src/main/java/org/expath/httpclient/HttpConnection.java index 1e662e1..7ea9632 100644 --- a/http-client-java/src/org/expath/httpclient/HttpConnection.java +++ b/http-client-java/src/main/java/org/expath/httpclient/HttpConnection.java @@ -34,6 +34,9 @@ public void setRequestMethod(String method, boolean with_content) throws HttpClientException; public void setFollowRedirect(boolean follow); public void setTimeout(int seconds); + public void setGzip(boolean gzip); + public void setChunked(boolean chunked); + public void setPreemptiveAuthentication(boolean preemptiveAuthentication); // responses... public int getResponseStatus() throws HttpClientException; diff --git a/http-client-java/src/org/expath/httpclient/HttpConstants.java b/http-client-java/src/main/java/org/expath/httpclient/HttpConstants.java similarity index 100% rename from http-client-java/src/org/expath/httpclient/HttpConstants.java rename to http-client-java/src/main/java/org/expath/httpclient/HttpConstants.java diff --git a/http-client-java/src/org/expath/httpclient/HttpCredentials.java b/http-client-java/src/main/java/org/expath/httpclient/HttpCredentials.java similarity index 100% rename from http-client-java/src/org/expath/httpclient/HttpCredentials.java rename to http-client-java/src/main/java/org/expath/httpclient/HttpCredentials.java diff --git a/http-client-java/src/org/expath/httpclient/HttpRequest.java b/http-client-java/src/main/java/org/expath/httpclient/HttpRequest.java similarity index 91% rename from http-client-java/src/org/expath/httpclient/HttpRequest.java rename to http-client-java/src/main/java/org/expath/httpclient/HttpRequest.java index 3c25686..46a8030 100644 --- a/http-client-java/src/org/expath/httpclient/HttpRequest.java +++ b/http-client-java/src/main/java/org/expath/httpclient/HttpRequest.java @@ -27,6 +27,7 @@ public HttpResponse send(Result result, HttpConnection conn, HttpCredentials cre public String getHttpVersion(); public void setHttpVersion(String ver) throws HttpClientException; + public void setDefaultCharset(String charset); public void setOverrideType(String type); public void setHeaders(HeaderSet headers); public void setBody(HttpRequestBody body) @@ -34,6 +35,11 @@ public void setBody(HttpRequestBody body) public void setStatusOnly(boolean only); public void setFollowRedirect(boolean follow); public void setTimeout(Integer seconds); + public void setGzip(boolean gzip); + public boolean isChunked(); + public void setChunked(boolean chunked); + boolean isPreemptiveAuthentication(); + void setPreemptiveAuthentication(final boolean preemptiveAuthentication); } diff --git a/http-client-java/src/org/expath/httpclient/HttpRequestBody.java b/http-client-java/src/main/java/org/expath/httpclient/HttpRequestBody.java similarity index 100% rename from http-client-java/src/org/expath/httpclient/HttpRequestBody.java rename to http-client-java/src/main/java/org/expath/httpclient/HttpRequestBody.java diff --git a/http-client-java/src/org/expath/httpclient/HttpResponse.java b/http-client-java/src/main/java/org/expath/httpclient/HttpResponse.java similarity index 100% rename from http-client-java/src/org/expath/httpclient/HttpResponse.java rename to http-client-java/src/main/java/org/expath/httpclient/HttpResponse.java diff --git a/http-client-java/src/org/expath/httpclient/HttpResponseBody.java b/http-client-java/src/main/java/org/expath/httpclient/HttpResponseBody.java similarity index 100% rename from http-client-java/src/org/expath/httpclient/HttpResponseBody.java rename to http-client-java/src/main/java/org/expath/httpclient/HttpResponseBody.java diff --git a/http-client-java/src/org/expath/httpclient/impl/AnyEmptyMethod.java b/http-client-java/src/main/java/org/expath/httpclient/impl/AnyEmptyMethod.java similarity index 100% rename from http-client-java/src/org/expath/httpclient/impl/AnyEmptyMethod.java rename to http-client-java/src/main/java/org/expath/httpclient/impl/AnyEmptyMethod.java diff --git a/http-client-java/src/org/expath/httpclient/impl/AnyEntityMethod.java b/http-client-java/src/main/java/org/expath/httpclient/impl/AnyEntityMethod.java similarity index 100% rename from http-client-java/src/org/expath/httpclient/impl/AnyEntityMethod.java rename to http-client-java/src/main/java/org/expath/httpclient/impl/AnyEntityMethod.java diff --git a/http-client-java/src/org/expath/httpclient/impl/ApacheHttpConnection.java b/http-client-java/src/main/java/org/expath/httpclient/impl/ApacheHttpConnection.java similarity index 55% rename from http-client-java/src/org/expath/httpclient/impl/ApacheHttpConnection.java rename to http-client-java/src/main/java/org/expath/httpclient/impl/ApacheHttpConnection.java index f22db54..139173f 100644 --- a/http-client-java/src/org/expath/httpclient/impl/ApacheHttpConnection.java +++ b/http-client-java/src/main/java/org/expath/httpclient/impl/ApacheHttpConnection.java @@ -9,42 +9,47 @@ package org.expath.httpclient.impl; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.ProxySelector; +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.Socket; import java.net.URI; +import java.util.concurrent.TimeUnit; +import java.util.zip.GZIPOutputStream; + +import net.jcip.annotations.NotThreadSafe; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.Header; import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; +import org.apache.http.HttpHost; import org.apache.http.HttpVersion; +import org.apache.http.auth.AuthScheme; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.AuthCache; import org.apache.http.client.CookieStore; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.client.methods.HttpOptions; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpTrace; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.client.params.ClientPNames; -import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.entity.GzipCompressingEntity; +import org.apache.http.client.methods.*; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentProducer; import org.apache.http.entity.EntityTemplate; -import org.apache.http.impl.client.AbstractHttpClient; -import org.apache.http.impl.client.BasicCookieStore; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.conn.ProxySelectorRoutePlanner; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.impl.auth.DigestScheme; +import org.apache.http.impl.client.*; +import org.apache.http.impl.conn.*; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.apache.http.ssl.SSLContexts; import org.expath.httpclient.HeaderSet; import org.expath.httpclient.HttpClientException; import org.expath.httpclient.HttpConnection; @@ -52,11 +57,15 @@ import org.expath.httpclient.HttpCredentials; import org.expath.httpclient.HttpRequestBody; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; + /** * An implementation of an HTTP connection using Apachhe HTTP Client. * * @author Florent Georges */ +@NotThreadSafe public class ApacheHttpConnection implements HttpConnection { @@ -66,20 +75,31 @@ public ApacheHttpConnection(URI uri) myRequest = null; myResponse = null; myVersion = DEFAULT_HTTP_VERSION; - myClient = null; } - public void connect(HttpRequestBody body, HttpCredentials cred) + @Override + public void connect(final HttpRequestBody body, final HttpCredentials cred) throws HttpClientException { if ( myRequest == null ) { throw new HttpClientException("setRequestMethod has not been called before"); } + + myRequest.setProtocolVersion(myVersion); + try { // make a new client - myClient = makeClient(); + if(myClient == null) { + myClient = makeClient(); + } + + if(myResponse != null) { + // close any previous response + myResponse.close(); + } + // set the credentials (if any) - setCredentials(cred); + final HttpClientContext clientContext = setCredentials(cred); // set the request entity body (if any) setRequestEntity(body); // log the request headers? @@ -90,7 +110,7 @@ public void connect(HttpRequestBody body, HttpCredentials cred) LoggerHelper.logCookies(LOG, "COOKIES", COOKIES.getCookies()); } // send the request - myResponse = myClient.execute(myRequest); + myResponse = myClient.execute(myRequest, clientContext); // TODO: Handle 'Connection' headers (for instance "Connection: close") // See for instance http://www.jmarshall.com/easy/http/. @@ -104,21 +124,56 @@ public void connect(HttpRequestBody body, HttpCredentials cred) } } catch ( IOException ex ) { - throw new HttpClientException("Error executing the HTTP method: " + ex.getMessage(), ex); + final String message = getMessage(ex); + throw new HttpClientException("Error executing the HTTP method: " + message != null ? message : "", ex); + } finally { + state = State.POST_CONNECT; } } - public void disconnect() - { - if ( myClient != null ) { - myClient.getConnectionManager().shutdown(); + /** + * Retrieves a message from the Throwable + * or its cause (recursively). + * + * @param throwable A thrown exception + * + * @return The first message, or null if there are no messages + * at all. + */ + private String getMessage(final Throwable throwable) { + if(throwable.getMessage() != null) { + return throwable.getMessage(); + } + + final Throwable cause = throwable.getCause(); + if(cause == null || cause == throwable) { + return null; } + + return getMessage(cause); } - public void setHttpVersion(String ver) + @Override + public void disconnect() throws HttpClientException { + try { + if(myResponse != null) { + myResponse.close(); + myResponse = null; + } + + myClient.close(); + myClient = null; + } catch (final IOException ex) { + final String message = getMessage(ex); + throw new HttpClientException(message, ex); + } + } + + @Override + public void setHttpVersion(final String ver) throws HttpClientException { - if ( myClient != null ) { + if ( state != State.INITIAL ) { String msg = "Internal error, HTTP version cannot been " + "set after connect() has been called."; throw new HttpClientException(msg); @@ -193,6 +248,21 @@ public void setTimeout(int seconds) myTimeout = seconds; } + @Override + public void setGzip(final boolean gzip) { + myGzip = gzip; + } + + @Override + public void setChunked(final boolean chunked) { + myChunked = chunked; + } + + @Override + public void setPreemptiveAuthentication(final boolean preemptiveAuthentication) { + myPreemptiveAuthentication = preemptiveAuthentication; + } + /** * Check the method name does match the HTTP/1.1 production rules. * @@ -283,64 +353,106 @@ public InputStream getResponseStream() /** * Make a new Apache HTTP client, in order to serve this request. */ - private AbstractHttpClient makeClient() - { - AbstractHttpClient client = new DefaultHttpClient(); - HttpParams params = client.getParams(); + private CloseableHttpClient makeClient() { + + final HttpClientBuilder clientBuilder = HttpClientBuilder.create() + .setConnectionManager(POOLING_CONNECTION_MANAGER) + .setConnectionManagerShared(true); + // use the default JVM proxy settings (http.proxyHost, etc.) - HttpRoutePlanner route = new ProxySelectorRoutePlanner( - client.getConnectionManager().getSchemeRegistry(), - ProxySelector.getDefault()); - client.setRoutePlanner(route); + clientBuilder.setRoutePlanner(new SystemDefaultRoutePlanner(null)); + // do follow redirections? - params.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, myFollowRedirect); + if(myFollowRedirect) { + clientBuilder.setRedirectStrategy(LaxRedirectStrategy.INSTANCE); + } + + // the shared cookie store + clientBuilder.setDefaultCookieStore(COOKIES); + // set the timeout if any - if ( myTimeout != null ) { + final RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); + if(myTimeout != null) { // See http://blog.jayway.com/2009/03/17/configuring-timeout-with-apache-httpclient-40/ - HttpConnectionParams.setConnectionTimeout(params, myTimeout * 1000); - HttpConnectionParams.setSoTimeout(params, myTimeout * 1000); + requestConfigBuilder + .setConnectTimeout(myTimeout * 1000) + .setSocketTimeout(myTimeout * 1000); } - // the shared cookie store - client.setCookieStore(COOKIES); - // the HTTP version (1.0 or 1.1) - params.setParameter("http.protocol.version", myVersion); - // return the just built client + clientBuilder.setDefaultRequestConfig(requestConfigBuilder.build()); + + final CloseableHttpClient client = clientBuilder.build(); return client; } /** * Set the credentials on the client, based on the {@link HttpCredentials} object. */ - private void setCredentials(HttpCredentials cred) - throws HttpClientException - { - if ( cred == null ) { - return; + private HttpClientContext setCredentials(HttpCredentials cred) + throws HttpClientException { + final HttpClientContext clientContext = HttpClientContext.create(); + + if (cred == null) { + return clientContext; } - URI uri = myRequest.getURI(); + + final URI uri = myRequest.getURI(); + final String scheme = uri.getScheme(); int port = uri.getPort(); - if ( port == -1 ) { - String scheme = uri.getScheme(); - if ( "http".equals(scheme) ) { + if (port == -1) { + if ("http".equals(scheme)) { port = 80; - } - else if ( "https".equals(scheme) ) { + } else if ("https".equals(scheme)) { port = 443; - } - else { + } else { throw new HttpClientException("Unknown scheme: " + uri); } } - String host = uri.getHost(); - String user = cred.getUser(); - String pwd = cred.getPwd(); - if ( LOG.isDebugEnabled() ) { - LOG.debug("Set credentials for " + host + ":" + port + final String host = uri.getHost(); + + final HttpHost targetHost = new HttpHost(host, port, scheme); + + final String user = cred.getUser(); + final String pwd = cred.getPwd(); + + if (LOG.isDebugEnabled()) { + LOG.debug("Set credentials for " + targetHost.getHostName() + ":" + targetHost.getPort() + " - " + user + " - ***"); } - Credentials c = new UsernamePasswordCredentials(user, pwd); - AuthScope scope = new AuthScope(host, port); - myClient.getCredentialsProvider().setCredentials(scope, c); + + final Credentials c = new UsernamePasswordCredentials(user, pwd); + final AuthScope scope = new AuthScope(targetHost); + + if (clientContext.getCredentialsProvider() == null) { + clientContext.setCredentialsProvider(new BasicCredentialsProvider()); + } else { + clientContext.getCredentialsProvider().clear(); + } + + clientContext.getCredentialsProvider().setCredentials(scope, c); + + // force preemptive authentication? + // see - https://hc.apache.org/httpcomponents-client-ga/tutorial/html/authentication.html#d5e717 + if (myPreemptiveAuthentication) { + + // is there already an auth cache? + if (clientContext.getAuthCache() == null) { + // no, so create one + final AuthCache authCache = new BasicAuthCache(); + clientContext.setAuthCache(authCache); + } + + // set the auth cache scheme + final AuthScheme authScheme; + if (cred.getMethod().equals("DIGEST")) { + authScheme = new DigestScheme(); + } else { + authScheme = new BasicScheme(); + } + + clientContext.getAuthCache().put(targetHost, authScheme); + } + + return clientContext; } /** @@ -353,23 +465,58 @@ private void setRequestEntity(HttpRequestBody body) return; } // make the entity from a new producer - HttpEntity entity; + final HttpEntity entity; if ( myVersion == HttpVersion.HTTP_1_1 ) { - // Take advantage of HTTP 1.1 chunked encoding to stream the - // payload directly to the request. - ContentProducer producer = new RequestBodyProducer(body); - EntityTemplate template = new EntityTemplate(producer); - template.setContentType(body.getContentType()); - entity = template; + + final HttpEntity template; + if(myChunked) { + // Take advantage of HTTP 1.1 chunked encoding to stream the + // payload directly to the request. + final ContentProducer producer = new RequestBodyProducer(body); + final EntityTemplate entityTemplate = new EntityTemplate(producer); + entityTemplate.setContentType(body.getContentType()); + entityTemplate.setChunked(true); + template = entityTemplate; + + } else { + /* + NOTE: for some reason even if you set EntityTemplate#setChunked(false), + Apache insists on chunking anyway... So, instead we manually buffer here + to foce non-chunked transfer encoding. + */ + try (final ByteArrayOutputStream buffer = new ByteArrayOutputStream()) { + body.serialize(buffer); + template = new ByteArrayEntity(buffer.toByteArray()); + } catch (final IOException e) { + throw new HttpClientException(e.getMessage(), e); + } + } + + if(myGzip) { + entity = new GzipCompressingEntity(template); + } else { + entity = template; + } } else { // With HTTP 1.0, chunked encoding is not supported, so first // serialize into memory and use the resulting byte array as the // entity payload. - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - body.serialize(buffer); - entity = new ByteArrayEntity(buffer.toByteArray()); + try (final ByteArrayOutputStream buffer = new ByteArrayOutputStream()) { + if(myGzip) { + try (final GZIPOutputStream gzip = new GZIPOutputStream(buffer)) { + body.serialize(gzip); + } + myRequest.setHeader(HTTP.CONTENT_ENCODING, "gzip"); + } else { + body.serialize(buffer); + } + entity = new ByteArrayEntity(buffer.toByteArray()); + } catch (final IOException e) { + throw new HttpClientException(e.getMessage(), e); + } } + // cast the request HttpEntityEnclosingRequestBase req = null; if ( ! (myRequest instanceof HttpEntityEnclosingRequestBase) ) { @@ -383,20 +530,80 @@ private void setRequestEntity(HttpRequestBody body) req.setEntity(entity); } + private static PoolingHttpClientConnectionManager setupConnectionPool() { + final SSLContext sslContext = SSLContexts. + createSystemDefault(); + + final SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLSocketFactoryWithSNI(sslContext); + + final Registry socketFactoryRegistry = RegistryBuilder.create() + .register("https", sslConnectionSocketFactory) + .register("http", PlainConnectionSocketFactory.INSTANCE) + .build(); + + final PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, 15, TimeUnit.MINUTES); //TODO(AR) TTL is currently 15 minutes, make configurable? + poolingHttpClientConnectionManager.setMaxTotal(40); //TODO(AR) total pooled connections is 40, make configurable? + poolingHttpClientConnectionManager.setDefaultMaxPerRoute(2); //TODO(AR) max default connections per route is 2, make configurable? + return poolingHttpClientConnectionManager; + } + + /** + * Implements SNI (Server Name Identification) for SSL + * + * @see https://github.com/fgeorges/expath-http-client-java/issues/5 + */ + private static class SSLSocketFactoryWithSNI extends SSLConnectionSocketFactory { + public SSLSocketFactoryWithSNI(final SSLContext sslContext) { + super(sslContext); + } + + @Override + public Socket connectSocket(final int connectTimeout, final Socket socket, final HttpHost host, + final InetSocketAddress remoteAddress, final InetSocketAddress localAddress, final HttpContext context) + throws IOException { + if (socket instanceof SSLSocket) { + try { + final Class socketClazz = socket.getClass(); + final Method m = socketClazz.getDeclaredMethod("setHost", String.class); + m.setAccessible(true); + m.invoke(socket, host.getHostName()); + } catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + LOG.warn("Problem whilst setting SNI: " + e.getMessage(), e); + } + } + + return super.connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, context); + } + } + + private enum State { + INITIAL, + POST_CONNECT + } + + private static final PoolingHttpClientConnectionManager POOLING_CONNECTION_MANAGER = setupConnectionPool(); + + private State state = State.INITIAL; + /** The target URI. */ private URI myUri; + /** The Apache client. */ + private CloseableHttpClient myClient; /** The Apache request. */ - private HttpUriRequest myRequest; + private HttpRequestBase myRequest; /** The Apache response. */ - private HttpResponse myResponse; + private CloseableHttpResponse myResponse; /** The HTTP protocol version. */ private HttpVersion myVersion; - /** The Apache client. */ - private AbstractHttpClient myClient; /** Follow HTTP redirect? */ private boolean myFollowRedirect = true; /** The timeout to use, in seconds, or null for default. */ private Integer myTimeout = null; + /** whether we should use gzip transfer encoding */ + private boolean myGzip = false; + private boolean myChunked = true; + private boolean myPreemptiveAuthentication = false; + /** * The shared cookie store. * diff --git a/http-client-java/src/org/expath/httpclient/impl/BinaryResponseBody.java b/http-client-java/src/main/java/org/expath/httpclient/impl/BinaryResponseBody.java similarity index 84% rename from http-client-java/src/org/expath/httpclient/impl/BinaryResponseBody.java rename to http-client-java/src/main/java/org/expath/httpclient/impl/BinaryResponseBody.java index 06f12f4..f274da6 100644 --- a/http-client-java/src/org/expath/httpclient/impl/BinaryResponseBody.java +++ b/http-client-java/src/main/java/org/expath/httpclient/impl/BinaryResponseBody.java @@ -28,13 +28,6 @@ public class BinaryResponseBody implements HttpResponseBody { - public BinaryResponseBody(Result result, byte[] value, ContentType type, HeaderSet headers) - throws HttpClientException - { - myContentType = type; - myHeaders = headers; - result.add(value); - } // TODO: Work only for binary response. What if the response is encoded // with base64? @@ -48,19 +41,7 @@ public BinaryResponseBody(Result result, InputStream in, ContentType type, Heade { myContentType = type; myHeaders = headers; - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] buf = new byte[4096]; - int read = 0; - while ( (read = in.read(buf)) > 0 ) { - out.write(buf, 0, read); - } - byte[] bytes = out.toByteArray(); - result.add(bytes); - } - catch ( IOException ex ) { - throw new HttpClientException("error reading HTTP response", ex); - } + result.add(in); } @Override diff --git a/http-client-java/src/org/expath/httpclient/impl/BodyFactory.java b/http-client-java/src/main/java/org/expath/httpclient/impl/BodyFactory.java similarity index 65% rename from http-client-java/src/org/expath/httpclient/impl/BodyFactory.java rename to http-client-java/src/main/java/org/expath/httpclient/impl/BodyFactory.java index e9aab45..649d5bd 100644 --- a/http-client-java/src/org/expath/httpclient/impl/BodyFactory.java +++ b/http-client-java/src/main/java/org/expath/httpclient/impl/BodyFactory.java @@ -12,9 +12,10 @@ import java.io.InputStream; import java.util.HashSet; import java.util.Set; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; + import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.message.BasicHeader; import org.expath.httpclient.ContentType; import org.expath.httpclient.HeaderSet; import org.expath.httpclient.HttpClientException; @@ -31,19 +32,17 @@ * * @author Florent Georges */ -public class BodyFactory -{ +public class BodyFactory { // TODO: Take new methods into account (XHTML, BASE64 and HEX). - public static HttpRequestBody makeRequestBody(Element elem, Sequence bodies, String ns) - throws HttpClientException - { + public static HttpRequestBody makeRequestBody(final Element elem, final Sequence bodies, final String ns) + throws HttpClientException { // method is got from @method if any... Type method = parseMethod(elem); // ...or from @media-type if no @method - if ( method == null ) { + if (method == null) { method = parseType(elem); } - switch ( method ) { + switch (method) { case MULTIPART: return new MultipartRequestBody(elem, bodies, ns); case SRC: @@ -61,36 +60,33 @@ public static HttpRequestBody makeRequestBody(Element elem, Sequence bodies, Str } } - public static HttpResponseBody makeResponseBody(Result result, ContentType type, HttpConnection conn) - throws HttpClientException - { - if ( type == null ) { + public static HttpResponseBody makeResponseBody(final Result result, final ContentType type, final HttpConnection conn) + throws HttpClientException { + if (type == null) { // it is legitimate to not have a body in a response; for instance // on a "304 Not Modified" return null; } String t = type.getType(); - if ( t == null ) { + if (t == null) { return null; } - InputStream in = conn.getResponseStream(); - if ( in == null ) { + final InputStream in = conn.getResponseStream(); + if (in == null) { return null; } - if ( t.startsWith("multipart/") ) { - return new MultipartResponseBody(result, in, type, conn); - } - else { + if (t.startsWith("multipart/")) { + return new MultipartResponseBody(result, in, type); + } else { return makeResponsePart(result, null, in, type); } } // package-level to be used within MultipartResponseBody ctor // TODO: Take new methods into account (XHTML, BASE64 and HEX). - static HttpResponseBody makeResponsePart(Result result, HeaderSet headers, InputStream in, ContentType ctype) - throws HttpClientException - { - switch ( parseType(ctype) ) { + static HttpResponseBody makeResponsePart(final Result result, final HeaderSet headers, final InputStream in, final ContentType ctype) + throws HttpClientException { + switch (parseType(ctype)) { case XML: // TODO: 'content_type' is the header Content-Type without any param // (i.e. "text/xml".) Should we keep this, or put the whole header @@ -107,8 +103,7 @@ static HttpResponseBody makeResponsePart(Result result, HeaderSet headers, Input } } - public static enum Type - { + public enum Type { XML, HTML, XHTML, @@ -120,20 +115,22 @@ public static enum Type SRC } - /** Media types that must be treated as text types (in addition to text/*). */ - private static Set TEXT_TYPES; + /** + * Media types that must be treated as text types (in addition to text/*). + */ + private static final Set TEXT_TYPES = new HashSet<>(); static { - TEXT_TYPES = new HashSet(); TEXT_TYPES.add("application/x-www-form-urlencoded"); TEXT_TYPES.add("application/xml-dtd"); } - /** Media types that must be treated as XML types (in addition to *+xml). */ - private static Set XML_TYPES; + /** + * Media types that must be treated as XML types (in addition to *+xml). + */ + private static final Set XML_TYPES = new HashSet<>(); static { // Doc: does not handle "application/xml-dtd" as XML // TODO: What about ".../xml-external-parsed-entity" ? - XML_TYPES = new HashSet(); XML_TYPES.add("text/xml"); XML_TYPES.add("application/xml"); XML_TYPES.add("text/xml-external-parsed-entity"); @@ -142,53 +139,44 @@ public static enum Type /** * Decode the content type from a MIME type string. - * + *

* TODO: Take new methods into account (XHTML, BASE64 and HEX). */ - private static Type parseType(String type) - { - if ( type.startsWith("multipart/") ) { + private static Type parseType(final String type) { + if (type.startsWith("multipart/")) { return Type.MULTIPART; - } - else if ( "text/html".equals(type) ) { + } else if ("text/html".equals(type)) { return Type.HTML; - } - else if ( type.endsWith("+xml") || XML_TYPES.contains(type) ) { + } else if (type.endsWith("+xml") || XML_TYPES.contains(type)) { return Type.XML; - } - else if ( type.startsWith("text/") || TEXT_TYPES.contains(type) ) { + } else if (type.startsWith("text/") || TEXT_TYPES.contains(type)) { return Type.TEXT; - } - else { + } else { return Type.BINARY; } } /** - * Look for the header COntent-Type in a header set and decode it. + * Look for the header Content-Type in a header set and decode it. */ - public static Type parseType(HeaderSet headers) - throws HttpClientException - { - Header h = headers.getFirstHeader("Content-Type"); - if ( h == null ) { + public static Type parseType(final HeaderSet headers) throws HttpClientException { + final Header h = headers.getFirstHeader("Content-Type"); + if (h == null) { throw new HttpClientException("impossible to find the content type"); } - ContentType ct = new ContentType(h); + final ContentType ct = ContentType.parse(h, null, null); return parseType(ct); } /** * Decode the content type from a ContentType object. */ - public static Type parseType(ContentType type) - throws HttpClientException - { - if ( type == null ) { + public static Type parseType(final ContentType type) throws HttpClientException { + if (type == null) { throw new HttpClientException("impossible to find the content type"); } - String t = type.getType(); - if ( t == null ) { + final String t = type.getType(); + if (t == null) { throw new HttpClientException("impossible to find the content type"); } return parseType(t); @@ -197,74 +185,67 @@ public static Type parseType(ContentType type) /** * Parse the @media-type from a http:body or http:multipart element. */ - private static Type parseType(Element elem) - throws HttpClientException - { - String local = elem.getLocalName(); - if ( "multipart".equals(local) ) { + private static Type parseType(final Element elem) throws HttpClientException { + final String local = elem.getLocalName(); + if ("multipart".equals(local)) { return Type.MULTIPART; - } - else if ( ! "body".equals(local) ) { + } else if (!"body".equals(local)) { throw new HttpClientException("INTERNAL ERROR: cannot happen, checked before"); - } - else { - if ( elem.getAttribute("src") != null ) { + } else { + if (elem.getAttribute("src") != null) { return Type.SRC; } - String content_type = elem.getAttribute("media-type"); - if ( content_type == null ) { + final String mediaType = elem.getAttribute("media-type"); + if (mediaType == null) { throw new HttpClientException("@media-type is not set on http:body"); } - Type type = parseType(HeaderSet.getValueWithoutParam(content_type)); - if ( type == Type.MULTIPART ) { - String msg = "multipart type not allowed for http:body: " + content_type; - throw new HttpClientException(msg); + final Header mediaTypeHeader = new BasicHeader("Media-Type", mediaType); + final HeaderElement[] mediaTypeHeaderElems = mediaTypeHeader.getElements(); + if (mediaTypeHeaderElems == null || mediaTypeHeaderElems.length == 0) { + throw new HttpClientException("@media-type is not set on http:body"); + } else if (mediaTypeHeaderElems.length > 1) { + throw new HttpClientException("Multiple @media-type internet media types present"); + } else { + final Type type = parseType(mediaTypeHeaderElems[0].getName()); + if (type == Type.MULTIPART) { + final String msg = "multipart type not allowed for http:body: " + mediaType; + throw new HttpClientException(msg); + } + return type; } - return type; } } /** * Parse the @method from a http:body or http:multipart element. - * + *

* Return null if there is no @method. */ - private static Type parseMethod(Element elem) - throws HttpClientException - { - String m = elem.getAttribute("method"); - if ( m == null ) { + private static Type parseMethod(final Element elem) throws HttpClientException { + final String m = elem.getAttribute("method"); + if (m == null) { return null; - } - else if ( "xml".equals(m) ) { + } else if ("xml".equals(m)) { return Type.XML; - } - else if ( "html".equals(m) ) { + } else if ("html".equals(m)) { return Type.HTML; - } - else if ( "xhtml".equals(m) ) { + } else if ("xhtml".equals(m)) { return Type.XHTML; - } - else if ( "text".equals(m) ) { + } else if ("text".equals(m)) { return Type.TEXT; - } - else if ( "binary".equals(m) ) { + } else if ("binary".equals(m)) { return Type.BINARY; } // FIXME: The spec says "binary", but I think we need "base64" and "hex" // instead (or in addition, if "binary" is left implementation-defined). - else if ( "base64".equals(m) ) { + else if ("base64".equals(m)) { return Type.BASE64; - } - else if ( "hex".equals(m) ) { + } else if ("hex".equals(m)) { return Type.HEX; - } - else { + } else { throw new HttpClientException("Incorrect value for @method: " + m); } } - - private static Log LOG = LogFactory.getLog(BodyFactory.class); } diff --git a/http-client-java/src/org/expath/httpclient/impl/HrefRequestBody.java b/http-client-java/src/main/java/org/expath/httpclient/impl/HrefRequestBody.java similarity index 86% rename from http-client-java/src/org/expath/httpclient/impl/HrefRequestBody.java rename to http-client-java/src/main/java/org/expath/httpclient/impl/HrefRequestBody.java index fbcdb5a..34df06f 100644 --- a/http-client-java/src/org/expath/httpclient/impl/HrefRequestBody.java +++ b/http-client-java/src/main/java/org/expath/httpclient/impl/HrefRequestBody.java @@ -62,23 +62,18 @@ public void serialize(OutputStream out) { try { String filename = new URI(myHref).getPath(); - InputStream in = new FileInputStream(new File(filename)); - byte[] buf = new byte[4096]; - int l = -1; - while ( (l = in.read(buf)) != -1 ) { - out.write(buf, 0, l); + try (final InputStream in = new FileInputStream(new File(filename))) { + byte[] buf = new byte[4096]; + int l = -1; + while ((l = in.read(buf)) != -1) { + out.write(buf, 0, l); + } + } catch (IOException ex) { + throw new HttpClientException("Error sending the file content", ex); } - in.close(); - } - catch ( URISyntaxException ex ) { + } catch ( URISyntaxException ex ) { throw new HttpClientException("Bad URI: " + myHref, ex); } - catch ( FileNotFoundException ex ) { - throw new HttpClientException("Error sending the file content", ex); - } - catch ( IOException ex ) { - throw new HttpClientException("Error sending the file content", ex); - } } private String myHref; diff --git a/http-client-java/src/org/expath/httpclient/impl/HttpRequestImpl.java b/http-client-java/src/main/java/org/expath/httpclient/impl/HttpRequestImpl.java similarity index 81% rename from http-client-java/src/org/expath/httpclient/impl/HttpRequestImpl.java rename to http-client-java/src/main/java/org/expath/httpclient/impl/HttpRequestImpl.java index ba5e1b4..23ff4b5 100644 --- a/http-client-java/src/org/expath/httpclient/impl/HttpRequestImpl.java +++ b/http-client-java/src/main/java/org/expath/httpclient/impl/HttpRequestImpl.java @@ -47,6 +47,12 @@ public HttpResponse send(Result result, HttpConnection conn, HttpCredentials cre if ( myTimeout != null ) { conn.setTimeout(myTimeout); } + if ( myGzip ) { + conn.setGzip(true); + } + conn.setChunked(isChunked()); + conn.setPreemptiveAuthentication(isPreemptiveAuthentication()); + conn.setFollowRedirect(myFollowRedirect); conn.connect(myBody, cred); int status = conn.getResponseStatus(); @@ -69,21 +75,11 @@ public HttpResponse send(Result result, HttpConnection conn, HttpCredentials cre return resp; } - private ContentType getContentType(HeaderSet headers) + private ContentType getContentType(final HeaderSet headers) throws HttpClientException { - if ( myOverrideType == null ) { - Header h = headers.getFirstHeader("Content-Type"); - if ( h == null ) { - return null; - } - else { - return new ContentType(h); - } - } - else { - return new ContentType(myOverrideType, null); - } + final Header header = headers.getFirstHeader("Content-Type"); + return ContentType.parse(header, myOverrideType, myDefaultCharset); } @Override @@ -131,6 +127,11 @@ else if ( HttpConstants.HTTP_1_1.equals(ver) ) { } } + @Override + public void setDefaultCharset(final String charset) { + myDefaultCharset = charset; + } + @Override public void setOverrideType(String type) { @@ -169,13 +170,50 @@ public void setTimeout(Integer seconds) myTimeout = seconds; } + @Override + public void setGzip(final boolean gzip) { + myGzip = gzip; + } + + @Override + public boolean isChunked() { + if(myChunked == null) { + if(myHttpVer != null && myHttpVer.equals(HttpConstants.HTTP_1_0)) { + return false; + } else { + return true; + } + } else { + return myChunked; + } + } + + @Override + public void setChunked(boolean chunked) { + this.myChunked = chunked; + } + + @Override + public boolean isPreemptiveAuthentication() { + return myPreemptiveAuthentication; + } + + @Override + public void setPreemptiveAuthentication(final boolean preemptiveAuthentication) { + this.myPreemptiveAuthentication = preemptiveAuthentication; + } + private String myMethod; private String myHref; private String myHttpVer; + private String myDefaultCharset; private String myOverrideType; private boolean myStatusOnly; private boolean myFollowRedirect = true; private Integer myTimeout = null; + private boolean myGzip = false; + private Boolean myChunked = null; + private boolean myPreemptiveAuthentication = false; private HeaderSet myHeaders; private HttpRequestBody myBody; private static final Log LOG = LogFactory.getLog(HttpRequestImpl.class); diff --git a/http-client-java/src/org/expath/httpclient/impl/LoggerHelper.java b/http-client-java/src/main/java/org/expath/httpclient/impl/LoggerHelper.java similarity index 100% rename from http-client-java/src/org/expath/httpclient/impl/LoggerHelper.java rename to http-client-java/src/main/java/org/expath/httpclient/impl/LoggerHelper.java diff --git a/http-client-java/src/org/expath/httpclient/impl/MultipartRequestBody.java b/http-client-java/src/main/java/org/expath/httpclient/impl/MultipartRequestBody.java similarity index 100% rename from http-client-java/src/org/expath/httpclient/impl/MultipartRequestBody.java rename to http-client-java/src/main/java/org/expath/httpclient/impl/MultipartRequestBody.java diff --git a/http-client-java/src/org/expath/httpclient/impl/MultipartResponseBody.java b/http-client-java/src/main/java/org/expath/httpclient/impl/MultipartResponseBody.java similarity index 62% rename from http-client-java/src/org/expath/httpclient/impl/MultipartResponseBody.java rename to http-client-java/src/main/java/org/expath/httpclient/impl/MultipartResponseBody.java index 6e0f783..61f11ba 100644 --- a/http-client-java/src/org/expath/httpclient/impl/MultipartResponseBody.java +++ b/http-client-java/src/main/java/org/expath/httpclient/impl/MultipartResponseBody.java @@ -12,8 +12,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.Header; @@ -24,7 +26,6 @@ import org.expath.httpclient.ContentType; import org.expath.httpclient.HeaderSet; import org.expath.httpclient.HttpClientException; -import org.expath.httpclient.HttpConnection; import org.expath.httpclient.HttpResponseBody; import org.expath.httpclient.model.Result; import org.expath.httpclient.model.TreeBuilder; @@ -35,80 +36,75 @@ * * @author Florent Georges */ -public class MultipartResponseBody - implements HttpResponseBody -{ - public MultipartResponseBody(Result result, InputStream in, ContentType type, HttpConnection conn) - throws HttpClientException - { - myContentType = type; - myParts = new ArrayList(); - Header h = conn.getResponseHeaders().getFirstHeader("Content-Type"); - if ( h == null ) { +public class MultipartResponseBody implements HttpResponseBody { + + public MultipartResponseBody(final Result result, final InputStream in, final ContentType type) + throws HttpClientException { + if (type == null || type.getType() == null) { throw new HttpClientException("No content type"); } + + myContentType = type; + myParts = new ArrayList<>(); + myBoundary = type.getBoundary(); - if ( myBoundary == null ) { + if (myBoundary == null) { throw new HttpClientException("No boundary"); } try { - analyzeParts(result, in, h.getValue()); - } - catch ( IOException ex ) { + analyzeParts(result, in); + } catch (IOException ex) { throw new HttpClientException("error reading the response stream", ex); } } @Override - public void outputBody(TreeBuilder b) - throws HttpClientException - { + public void outputBody(final TreeBuilder b) throws HttpClientException { try { b.startElem("multipart"); b.attribute("media-type", myContentType.getValue()); b.attribute("boundary", myBoundary); b.startContent(); - for ( HttpResponseBody part : myParts ) { + for (final HttpResponseBody part : myParts) { part.outputBody(b); } b.endElem(); - } - catch ( ToolsException ex ) { + } catch (final ToolsException ex) { throw new HttpClientException("Error building the body", ex); } } - private void analyzeParts(Result result, InputStream in, String type) - throws IOException - , HttpClientException - { - MimeTokenStream parser = new MimeTokenStream(); - parser.parseHeadless(in, type); + private void analyzeParts(final Result result, final InputStream in) throws IOException, HttpClientException { + final MimeTokenStream parser = new MimeTokenStream(); + + final String contentType; + if (myContentType.getCharset() != null) { + contentType = myContentType.getType() + "; charset=" + myContentType.getCharset(); + } else { + contentType = myContentType.getType(); + } + parser.parseHeadless(in, contentType); try { HeaderSet headers = null; - for ( EntityState state = parser.getState(); - state != EntityState.T_END_OF_STREAM; - state = parser.next() ) - { - if ( state == EntityState.T_START_HEADER ) { + for (EntityState state = parser.getState(); + state != EntityState.T_END_OF_STREAM; + state = parser.next()) { + if (state == EntityState.T_START_HEADER) { headers = new HeaderSet(); } handleParserState(result, parser, headers); } - } - catch ( MimeException ex ) { + } catch (final MimeException ex) { throw new HttpClientException("The response content is ill-formed.", ex); } } - private void handleParserState(Result result, MimeTokenStream parser, HeaderSet headers) - throws HttpClientException - { - EntityState state = parser.getState(); - if ( LOG.isDebugEnabled() ) { + private void handleParserState(final Result result, final MimeTokenStream parser, final HeaderSet headers) throws HttpClientException { + final EntityState state = parser.getState(); + if (LOG.isDebugEnabled()) { LOG.debug(MimeTokenStream.stateToString(state)); } - switch ( state ) { + switch (state) { // It seems that in a headless parsing, END_HEADER appears // right after START_MESSAGE (without the corresponding // START_HEADER). So if headers == null, we can just ignore @@ -117,17 +113,17 @@ private void handleParserState(Result result, MimeTokenStream parser, HeaderSet // TODO: Just ignore anyway...? break; case T_FIELD: - Field f = parser.getField(); - if ( LOG.isDebugEnabled() ) { + final Field f = parser.getField(); + if (LOG.isDebugEnabled()) { LOG.debug(" field: " + f); } headers.add(f.getName(), parseFieldBody(f)); break; case T_BODY: - if ( LOG.isDebugEnabled() ) { + if (LOG.isDebugEnabled()) { LOG.debug(" body desc: " + parser.getBodyDescriptor()); } - HttpResponseBody b = makeResponsePart(result, headers, parser); + final HttpResponseBody b = makeResponsePart(result, headers, parser); myParts.add(b); break; // START_HEADER is handled in the calling analyzeParts() @@ -147,21 +143,19 @@ private void handleParserState(Result result, MimeTokenStream parser, HeaderSet // should discover slowly that we should probably just // ignore some of them. default: - String s = MimeTokenStream.stateToString(state); + final String s = MimeTokenStream.stateToString(state); throw new HttpClientException("Unknown parsing state: " + s); } } - private String parseFieldBody(Field f) - throws HttpClientException - { + private String parseFieldBody(final Field f) { // try { - // WHy did I use AbstractField in the first place? - String b = f.getBody() /* AbstractField.parse(f.getRaw()).getBody() */; - if ( LOG.isDebugEnabled() ) { - LOG.debug("Field: " + f.getName() + ": [" + b + "]"); - } - return b; + // WHy did I use AbstractField in the first place? + final String b = f.getBody() /* AbstractField.parse(f.getRaw()).getBody() */; + if (LOG.isDebugEnabled()) { + LOG.debug("Field: " + f.getName() + ": [" + b + "]"); + } + return b; // } // catch ( MimeException ex ) { // LOG.error("Field value parsing error (" + f + ")", ex); @@ -169,37 +163,36 @@ private String parseFieldBody(Field f) // } } - private HttpResponseBody makeResponsePart(Result result, HeaderSet headers, MimeTokenStream parser) - throws HttpClientException - { - Header h = headers.getFirstHeader("Content-Type"); - if ( h == null ) { + private HttpResponseBody makeResponsePart(final Result result, final HeaderSet headers, final MimeTokenStream parser) + throws HttpClientException { + final Header h = headers.getFirstHeader("Content-Type"); + if (h == null) { throw new HttpClientException("impossible to find the content type"); } - ContentType type = new ContentType(h); - switch ( BodyFactory.parseType(type) ) { - case XML: { - // TODO: 'content_type' is the header Content-Type without any - // param (i.e. "text/xml".) Should we keep this, or put the - // whole header (i.e. "text/xml; charset=utf-8")? (and for - // other types as well...) - Reader in = parser.getReader(); - return new XmlResponseBody(result, in, type, headers, false); - } - case HTML: { - Reader in = parser.getReader(); - return new XmlResponseBody(result, in, type, headers, true); - } - case TEXT: { - Reader in = parser.getReader(); - return new TextResponseBody(result, in, type, headers); - } - case BINARY: { - InputStream in = parser.getInputStream(); - return new BinaryResponseBody(result, in, type, headers); + final ContentType type = ContentType.parse(h, null, null); + try { + switch (BodyFactory.parseType(type)) { + case XML: { + final Reader in = parser.getReader(); + return new XmlResponseBody(result, in, type, headers, false); + } + case HTML: { + final Reader in = parser.getReader(); + return new XmlResponseBody(result, in, type, headers, true); + } + case TEXT: { + final Reader in = parser.getReader(); + return new TextResponseBody(result, in, type, headers); + } + case BINARY: { + final InputStream in = parser.getInputStream(); + return new BinaryResponseBody(result, in, type, headers); + } + default: + throw new HttpClientException("INTERNAL ERROR: cannot happen"); } - default: - throw new HttpClientException("INTERNAL ERROR: cannot happen"); + } catch (final UnsupportedEncodingException ex) { + throw new HttpClientException("Unable to parse response part", ex); } } diff --git a/http-client-java/src/org/expath/httpclient/impl/RequestParser.java b/http-client-java/src/main/java/org/expath/httpclient/impl/RequestParser.java similarity index 88% rename from http-client-java/src/org/expath/httpclient/impl/RequestParser.java rename to http-client-java/src/main/java/org/expath/httpclient/impl/RequestParser.java index ccca30a..6423baa 100644 --- a/http-client-java/src/org/expath/httpclient/impl/RequestParser.java +++ b/http-client-java/src/main/java/org/expath/httpclient/impl/RequestParser.java @@ -62,11 +62,6 @@ public HttpCredentials getCredentials() return myCredentials; } - public boolean getSendAuth() - { - return mySendAuth; - } - public HttpRequest parse(Sequence bodies, String href) throws HttpClientException { @@ -89,9 +84,11 @@ public HttpRequest parse(Sequence bodies, String href) // override-media-type? = string // follow-redirect? = boolean // timeout? = integer + // gzip? = boolean + // chunked? = boolean for ( Attribute a : myRequest.attributes() ) { String local = a.getLocalName(); - if ( !"".equals(a.getNamespaceUri()) ) { + if ( !(a.getNamespaceUri() == null || a.getNamespaceUri().isEmpty()) ) { // ignore namespace qualified attributes } else if ( "method".equals(local) ) { @@ -116,7 +113,10 @@ else if ( "auth-method".equals(local) ) { auth_method = a.getValue(); } else if ( "send-authorization".equals(local) ) { - mySendAuth = toBoolean(a); + req.setPreemptiveAuthentication(toBoolean(a)); + } + else if ( "default-charset".equals(local) ) { + req.setDefaultCharset(a.getValue()); } else if ( "override-media-type".equals(local) ) { req.setOverrideType(a.getValue()); @@ -127,6 +127,12 @@ else if ( "follow-redirect".equals(local) ) { else if ( "timeout".equals(local) ) { req.setTimeout(toInteger(a)); } + else if ( "gzip".equals(local) ) { + req.setGzip(toBoolean(a)); + } + else if ( "chunked".equals(local) ) { + req.setChunked(toBoolean(a)); + } else { throw new HttpClientException("Unknown attribute http:request/@" + local); } @@ -140,6 +146,9 @@ else if ( "timeout".equals(local) ) { if ( username != null || password != null || auth_method != null ) { setAuthentication(username, password, auth_method); } + if(req.getHttpVersion() != null && req.getHttpVersion().equals(HttpConstants.HTTP_1_0) && req.isChunked()) { + throw new HttpClientException("Chunked transfer encoding can only be used with HTTP 1.1"); + } // walk the elements // TODO: Check element structure validity (header*, (multipart|body)?) @@ -148,7 +157,7 @@ else if ( "timeout".equals(local) ) { for ( Element child : myRequest.children() ) { String local = child.getLocalName(); String ns = child.getNamespaceUri(); - if ( "".equals(ns) ) { + if ( ns == null || ns.isEmpty() ) { // elements in no namespace are an error throw new HttpClientException("Element in no namespace: " + local); } @@ -200,7 +209,7 @@ private void addHeader(HeaderSet headers, Element e) String value = null; for ( Attribute a : e.attributes() ) { String local = a.getLocalName(); - if ( !"".equals(a.getNamespaceUri()) ) { + if ( !(a.getNamespaceUri() == null || a.getNamespaceUri().isEmpty()) ) { // ignore namespace qualified attributes } else if ( "name".equals(local) ) { @@ -217,6 +226,15 @@ else if ( "value".equals(local) ) { if ( name == null || value == null ) { throw new HttpClientException("@name and @value are required on http:header"); } + + if(name.equalsIgnoreCase("Content-Length")) { + throw new HttpClientException("Content-Length should not be explicitly provided, either it will automatically be added or Transfer-Encoding will be used."); + } + + if(name.equalsIgnoreCase("Transfer-Encoding")) { + throw new HttpClientException("Transfer-Encoding should not be explicitly provided, it will automatically be added if required."); + } + // actually add the header headers.add(name, value); } @@ -265,8 +283,6 @@ private int toInteger(Attribute a) private String myOtherNs; /** User credentials in case of authentication (from @username, @password and @auth-method). */ private HttpCredentials myCredentials = null; - /** The value of @send-authorization. */ - private boolean mySendAuth = false; } diff --git a/http-client-java/src/org/expath/httpclient/impl/SinglePartRequestBody.java b/http-client-java/src/main/java/org/expath/httpclient/impl/SinglePartRequestBody.java similarity index 100% rename from http-client-java/src/org/expath/httpclient/impl/SinglePartRequestBody.java rename to http-client-java/src/main/java/org/expath/httpclient/impl/SinglePartRequestBody.java diff --git a/http-client-java/src/org/expath/httpclient/impl/TextResponseBody.java b/http-client-java/src/main/java/org/expath/httpclient/impl/TextResponseBody.java similarity index 60% rename from http-client-java/src/org/expath/httpclient/impl/TextResponseBody.java rename to http-client-java/src/main/java/org/expath/httpclient/impl/TextResponseBody.java index 7c33c4c..cc5c418 100644 --- a/http-client-java/src/org/expath/httpclient/impl/TextResponseBody.java +++ b/http-client-java/src/main/java/org/expath/httpclient/impl/TextResponseBody.java @@ -9,12 +9,10 @@ package org.expath.httpclient.impl; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.UnsupportedEncodingException; +import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + import org.expath.httpclient.ContentType; import org.expath.httpclient.HeaderSet; import org.expath.httpclient.HttpClientException; @@ -28,58 +26,44 @@ * * @author Florent Georges */ -public class TextResponseBody - implements HttpResponseBody -{ - public TextResponseBody(Result result, InputStream in, ContentType type, HeaderSet headers) - throws HttpClientException - { - // FIXME: ... - String charset = "utf-8"; - try { - Reader reader = new InputStreamReader(in, charset); - init(result, reader, type, headers); - } - catch ( UnsupportedEncodingException ex ) { - String msg = "not supported charset reading HTTP response: " + charset; - throw new HttpClientException(msg, ex); +public class TextResponseBody implements HttpResponseBody { + + public static final Charset DEFAULT_HTTP_TEXT_CHARSET = StandardCharsets.ISO_8859_1; + + public TextResponseBody(final Result result, final InputStream in, final ContentType type, final HeaderSet headers) + throws HttpClientException { + myContentType = type; + myHeaders = headers; + + final Charset contentCharset; + if (type.getCharset() != null) { + contentCharset = Charset.forName(type.getCharset()); + } else { + contentCharset = DEFAULT_HTTP_TEXT_CHARSET; } - } - public TextResponseBody(Result result, Reader in, ContentType type, HeaderSet headers) - throws HttpClientException - { - init(result, in, type, headers); + final Reader reader = new InputStreamReader(in, contentCharset); + result.add(reader, contentCharset); } - private void init(Result result, Reader in, ContentType type, HeaderSet headers) - throws HttpClientException - { + public TextResponseBody(final Result result, final Reader in, final ContentType type, final HeaderSet headers) + throws HttpClientException { myContentType = type; myHeaders = headers; - // BufferedReader handles the ends of line (all \n, \r, and \r\n are - // transformed to \n) - try { - StringBuilder builder = new StringBuilder(); - BufferedReader buf_in = new BufferedReader(in); - String buf = null; - while ( (buf = buf_in.readLine()) != null ) { - builder.append(buf); - builder.append('\n'); - } - String value = builder.toString(); - result.add(value); - } - catch ( IOException ex ) { - throw new HttpClientException("error reading HTTP response", ex); + + final Charset contentCharset; + if (type.getCharset() != null) { + contentCharset = Charset.forName(type.getCharset()); + } else { + contentCharset = DEFAULT_HTTP_TEXT_CHARSET; } + + result.add(in, contentCharset); } @Override - public void outputBody(TreeBuilder b) - throws HttpClientException - { - if ( myHeaders != null ) { + public void outputBody(final TreeBuilder b) throws HttpClientException { + if (myHeaders != null) { b.outputHeaders(myHeaders); } try { @@ -88,14 +72,13 @@ public void outputBody(TreeBuilder b) // TODO: Support other attributes as well? b.startContent(); b.endElem(); - } - catch ( ToolsException ex ) { + } catch (ToolsException ex) { throw new HttpClientException("Error building the body", ex); } } - private ContentType myContentType; - private HeaderSet myHeaders; + private final ContentType myContentType; + private final HeaderSet myHeaders; } diff --git a/http-client-java/src/org/expath/httpclient/impl/XmlResponseBody.java b/http-client-java/src/main/java/org/expath/httpclient/impl/XmlResponseBody.java similarity index 72% rename from http-client-java/src/org/expath/httpclient/impl/XmlResponseBody.java rename to http-client-java/src/main/java/org/expath/httpclient/impl/XmlResponseBody.java index 6473876..400fe29 100644 --- a/http-client-java/src/org/expath/httpclient/impl/XmlResponseBody.java +++ b/http-client-java/src/main/java/org/expath/httpclient/impl/XmlResponseBody.java @@ -12,10 +12,12 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; -import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import javax.xml.transform.Source; import javax.xml.transform.sax.SAXSource; import javax.xml.transform.stream.StreamSource; + import org.ccil.cowan.tagsoup.Parser; import org.expath.httpclient.ContentType; import org.expath.httpclient.HeaderSet; @@ -32,61 +34,56 @@ * * @author Florent Georges */ -public class XmlResponseBody - implements HttpResponseBody -{ - public XmlResponseBody(Result result, InputStream in, ContentType type, HeaderSet headers, boolean html) - throws HttpClientException - { - // TODO: ... - String charset = "utf-8"; - try { - Reader reader = new InputStreamReader(in, charset); - init(result, reader, type, headers, html); - } - catch ( UnsupportedEncodingException ex ) { - String msg = "not supported charset reading HTTP response: " + charset; - throw new HttpClientException(msg, ex); +public class XmlResponseBody implements HttpResponseBody { + + public static final Charset DEFAULT_HTTP_APPLICATION_XML_CHARSET = StandardCharsets.UTF_8; + + public XmlResponseBody(final Result result, final InputStream in, final ContentType type, final HeaderSet headers, final boolean html) + throws HttpClientException { + + final Charset contentCharset; + if (type.getCharset() != null) { + contentCharset = Charset.forName(type.getCharset()); + } else { + contentCharset = DEFAULT_HTTP_APPLICATION_XML_CHARSET; } + + final Reader reader = new InputStreamReader(in, contentCharset); + init(result, reader, type, headers, html); } - public XmlResponseBody(Result result, Reader in, ContentType type, HeaderSet headers, boolean html) - throws HttpClientException - { + public XmlResponseBody(final Result result, final Reader in, final ContentType type, final HeaderSet headers, final boolean html) + throws HttpClientException { init(result, in, type, headers, html); } - private void init(Result result, Reader in, ContentType type, HeaderSet headers, boolean html) - throws HttpClientException - { + private void init(final Result result, final Reader in, final ContentType type, final HeaderSet headers, final boolean html) + throws HttpClientException { myContentType = type; myHeaders = headers; String sys_id = "TODO-find-a-useful-systemId"; try { Source src; - if ( html ) { - Parser parser = new Parser(); + if (html) { + final Parser parser = new Parser(); parser.setFeature(Parser.namespacesFeature, true); parser.setFeature(Parser.namespacePrefixesFeature, true); - InputSource input = new InputSource(in); + final InputSource input = new InputSource(in); src = new SAXSource(parser, input); src.setSystemId(sys_id); - } - else { + } else { src = new StreamSource(in, sys_id); } result.add(src); - } - catch ( SAXException ex ) { + } catch (SAXException ex) { throw new HttpClientException("error parsing result HTML", ex); } } @Override - public void outputBody(TreeBuilder b) - throws HttpClientException - { - if ( myHeaders != null ) { + public void outputBody(final TreeBuilder b) + throws HttpClientException { + if (myHeaders != null) { b.outputHeaders(myHeaders); } try { @@ -95,8 +92,7 @@ public void outputBody(TreeBuilder b) // TODO: Support other attributes as well? b.startContent(); b.endElem(); - } - catch ( ToolsException ex ) { + } catch (ToolsException ex) { throw new HttpClientException("Error building the body", ex); } } diff --git a/http-client-java/src/org/expath/httpclient/model/Result.java b/http-client-java/src/main/java/org/expath/httpclient/model/Result.java similarity index 93% rename from http-client-java/src/org/expath/httpclient/model/Result.java rename to http-client-java/src/main/java/org/expath/httpclient/model/Result.java index c16dc9f..642bddb 100644 --- a/http-client-java/src/org/expath/httpclient/model/Result.java +++ b/http-client-java/src/main/java/org/expath/httpclient/model/Result.java @@ -9,7 +9,11 @@ package org.expath.httpclient.model; +import java.io.Reader; +import java.io.InputStream; +import java.nio.charset.Charset; import javax.xml.transform.Source; + import org.expath.httpclient.HttpClientException; import org.expath.httpclient.HttpResponse; @@ -63,22 +67,22 @@ public Result makeNewResult() throws HttpClientException; /** - * Add an {@code xs:string} to the result sequence. + * Add a string value to the result sequence. * * @param string The string to add to the result sequence. * @throws HttpClientException If any error occurs. */ - public void add(String string) + public void add(Reader string, Charset encoding) throws HttpClientException; /** - * Add an {@code xs:base64Binary} to the result sequence. + * Add raw binary to the result sequence. * - * @param bytes The bytes representing the base64 binary item to add to the + * @param bytes The bytes representing the binary item to add to the * result sequence. * @throws HttpClientException If any error occurs. */ - public void add(byte[] bytes) + public void add(InputStream bytes) throws HttpClientException; /** @@ -124,5 +128,5 @@ public void add(HttpResponse response) /* */ /* The Initial Developer of the Original Code is Florent Georges. */ /* */ -/* Contributor(s): none. */ +/* Contributor(s): Adam Retter */ /* ------------------------------------------------------------------------ */ diff --git a/http-client-java/src/org/expath/httpclient/model/TreeBuilder.java b/http-client-java/src/main/java/org/expath/httpclient/model/TreeBuilder.java similarity index 100% rename from http-client-java/src/org/expath/httpclient/model/TreeBuilder.java rename to http-client-java/src/main/java/org/expath/httpclient/model/TreeBuilder.java diff --git a/http-client-java/src/org/expath/httpclient/ContentType.java b/http-client-java/src/org/expath/httpclient/ContentType.java deleted file mode 100644 index df9e0de..0000000 --- a/http-client-java/src/org/expath/httpclient/ContentType.java +++ /dev/null @@ -1,127 +0,0 @@ -/****************************************************************************/ -/* File: ContentType.java */ -/* Author: F. Georges - fgeorges.org */ -/* Date: 2009-02-22 */ -/* Tags: */ -/* Copyright (c) 2009 Florent Georges (see end of file.) */ -/* ------------------------------------------------------------------------ */ - - -package org.expath.httpclient; - -import org.apache.http.Header; -import org.apache.http.HeaderElement; -import org.apache.http.NameValuePair; - -/** - * Represent a Content-Type header. - * - * Provide the ability to get the boundary param in case of a multipart - * content type on the one hand, and the ability to get only the MIME type - * string without any param on the other hand. - * - * @author Florent Georges - */ -public class ContentType -{ - public ContentType(String type, String boundary) - { - myHeader = null; - myType = type; - myBoundary = boundary; - } - - public ContentType(Header h) - throws HttpClientException - { - if ( h == null ) { - throw new HttpClientException("Header is null"); - } - if ( ! "Content-Type".equalsIgnoreCase(h.getName()) ) { - throw new HttpClientException("Header is not content type"); - } - myHeader = h; - myType = HeaderSet.getHeaderWithoutParam(myHeader); - HeaderElement[] elems = h.getElements(); - if ( elems != null ) { - for ( HeaderElement e : elems ) { - for ( NameValuePair p : e.getParameters() ) { - if ( "boundary".equals(p.getName()) ) { - myBoundary = p.getValue(); - } - } - } - } - } - - @Override - public String toString() - { - if ( myHeader == null ) { - return "Content-Type: " + getValue(); - } - else { - return myHeader.toString(); - } - } - - public String getType() - { - return myType; - } - - public String getBoundary() - { - return myBoundary; - } - - public String getValue() - { - // TODO: Why did I add the boundary before...? -// if ( myHeader == null ) { -// StringBuilder b = new StringBuilder(); -// b.append(myType); -// if ( myBoundary != null ) { -// b.append("; boundary=\""); -// // TODO: Is that correct escaping sequence? -// b.append(myBoundary.replace("\"", "\\\"")); -// b.append("\""); -// } -// return b.toString(); -// } - if ( myType != null ) { - return myType; - } - if ( myHeader != null ) { - return myHeader.getValue(); - } - else { - return null; - } - } - - private Header myHeader; - private String myType; - private String myBoundary; -} - - -/* ------------------------------------------------------------------------ */ -/* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS COMMENT. */ -/* */ -/* The contents of this file are subject to the Mozilla Public License */ -/* Version 1.0 (the "License"); you may not use this file except in */ -/* compliance with the License. You may obtain a copy of the License at */ -/* http://www.mozilla.org/MPL/. */ -/* */ -/* Software distributed under the License is distributed on an "AS IS" */ -/* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See */ -/* the License for the specific language governing rights and limitations */ -/* under the License. */ -/* */ -/* The Original Code is: all this file. */ -/* */ -/* The Initial Developer of the Original Code is Florent Georges. */ -/* */ -/* Contributor(s): none. */ -/* ------------------------------------------------------------------------ */ diff --git a/http-client-java/test/tmp/tests/ApacheHttpClientTest.java b/http-client-java/src/test/java/tmp/tests/ApacheHttpClientTest.java similarity index 61% rename from http-client-java/test/tmp/tests/ApacheHttpClientTest.java rename to http-client-java/src/test/java/tmp/tests/ApacheHttpClientTest.java index afe0a3c..6d0ef39 100644 --- a/http-client-java/test/tmp/tests/ApacheHttpClientTest.java +++ b/http-client-java/src/test/java/tmp/tests/ApacheHttpClientTest.java @@ -7,7 +7,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; -import java.net.ProxySelector; import java.net.URI; import java.net.URISyntaxException; import java.security.KeyManagementException; @@ -21,22 +20,21 @@ import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; -import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.params.ClientPNames; -import org.apache.http.conn.routing.HttpRoutePlanner; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.cookie.Cookie; import org.apache.http.entity.ContentProducer; import org.apache.http.entity.EntityTemplate; -import org.apache.http.impl.client.AbstractHttpClient; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.conn.ProxySelectorRoutePlanner; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.SystemDefaultRoutePlanner; +import org.apache.http.ssl.SSLContextBuilder; import org.junit.Ignore; import org.junit.Test; +import javax.net.ssl.SSLContext; + /** * * @author Florent Georges - fgeorges.org @@ -45,14 +43,16 @@ public class ApacheHttpClientTest { @Test public void testGetMethod() - throws ClientProtocolException, IOException + throws IOException { HttpGet get = new HttpGet("http://www.fgeorges.org/"); - HttpResponse response = getClient().execute(get); - System.err.println("Status: " + response.getStatusLine().getStatusCode()); - System.err.print("Content: "); - response.getEntity().writeTo(System.err); - System.err.println(); + try(final CloseableHttpClient client = getClient()) { + HttpResponse response = client.execute(get); + System.err.println("Status: " + response.getStatusLine().getStatusCode()); + System.err.print("Content: "); + response.getEntity().writeTo(System.err); + System.err.println(); + } } /** @@ -60,70 +60,80 @@ public void testGetMethod() */ @Test public void testGoogleAuth() - throws ClientProtocolException, IOException + throws IOException { HttpPost post = new HttpPost("https://www.google.com/accounts/ClientLogin"); ContentProducer producer = new StringProducer(AUTH_CONTENT); EntityTemplate entity = new EntityTemplate(producer); entity.setContentType(FORM_TYPE); post.setEntity(entity); - HttpResponse resp = getClient().execute(post); - System.err.println("Status: " + resp.getStatusLine().getStatusCode()); - System.err.print("Content: "); - resp.getEntity().writeTo(System.err); - System.err.println(); + try(final CloseableHttpClient client = getClient()) { + HttpResponse resp = client.execute(post); + System.err.println("Status: " + resp.getStatusLine().getStatusCode()); + System.err.print("Content: "); + resp.getEntity().writeTo(System.err); + System.err.println(); + } } @Test public void testGoogleRedirect() - throws ClientProtocolException, IOException + throws IOException { HttpPost post = new HttpPost("https://www.google.com/accounts/ClientLogin"); ContentProducer producer = new StringProducer(AUTH_CONTENT); EntityTemplate entity = new EntityTemplate(producer); entity.setContentType(FORM_TYPE); post.setEntity(entity); - AbstractHttpClient client = getClient(); - HttpResponse resp = client.execute(post); - System.err.println("POST status: " + resp.getStatusLine().getStatusCode()); String token = null; - for ( String s : getStringContent(resp).split("\n") ) { - if ( s.startsWith("Auth=") ) { - token = s.substring(5); + try(final CloseableHttpClient client = getClient()) { + HttpResponse resp = client.execute(post); + System.err.println("POST status: " + resp.getStatusLine().getStatusCode()); + for (String s : getStringContent(resp).split("\n")) { + if (s.startsWith("Auth=")) { + token = s.substring(5); + } } + System.err.println("Token: " + token); } - System.err.println("Token: " + token); + // GetMethod get = new GetMethod("https://www.google.com/calendar/feeds/default/allcalendars/full"); HttpGet get = new HttpGet("http://www.google.com/calendar/feeds/xmlprague.cz_k0rlr8da52ivmgp6eujip041s8%40group.calendar.google.com/private/full"); get.setHeader("GData-Version", "2"); get.setHeader("Authorization", "GoogleLogin auth=" + token); // get.setFollowRedirects(false); - resp = client.execute(get); - System.err.println("GET status: " + resp.getStatusLine().getStatusCode()); - for ( Cookie c : client.getCookieStore().getCookies() ) { - System.err.println("Cookie: " + c.getName() + ", " + c.getValue()); - } - for ( String s : client.getCookieSpecs().getSpecNames() ) { - System.err.println("Cookie spec: " + s); - } - if ( resp.getStatusLine().getStatusCode() == 302 ) { - client = getClient(); - resp = client.execute(get); + HttpClientContext context = HttpClientContext.create(); + try(final CloseableHttpClient client = getClient()) { + HttpResponse resp = client.execute(get, context); System.err.println("GET status: " + resp.getStatusLine().getStatusCode()); + + for (Cookie c : context.getCookieStore().getCookies()) { + System.err.println("Cookie: " + c.getName() + ", " + c.getValue()); + } + System.err.println("Cookie spec: " + context.getCookieSpec()); + + if (resp.getStatusLine().getStatusCode() == 302) { + try(final CloseableHttpClient client2 = getClient()) { + final HttpResponse resp2 = client2.execute(get); + System.err.println("GET status: " + resp2.getStatusLine().getStatusCode()); + } + } } + HttpGet get2 = new HttpGet("http://www.google.com/calendar/feeds/xmlprague.cz_k0rlr8da52ivmgp6eujip041s8%40group.calendar.google.com/private/full"); get2.setHeader("GData-Version", "2"); get2.setHeader("Authorization", "GoogleLogin auth=" + token); // get2.setFollowRedirects(false); - client = getClient(); - resp = client.execute(get2); - System.err.println("GET 2 status: " + resp.getStatusLine().getStatusCode()); + try(final CloseableHttpClient client = getClient()) { + HttpResponse resp = client.execute(get2); + System.err.println("GET 2 status: " + resp.getStatusLine().getStatusCode()); + } } @Ignore("Broken authentication?!?") @Test public void testGoogleAddAgenda() - throws ClientProtocolException, IOException + throws IOException { System.err.println(); System.err.println("***** [testGoogleAddAgenda]"); @@ -137,8 +147,8 @@ public void testGoogleAddAgenda() System.err.println("***** [/testGoogleAddAgenda]"); } - public HttpResponse testGoogleAddAgenda_1(String token, String uri) - throws ClientProtocolException, IOException + private HttpResponse testGoogleAddAgenda_1(String token, String uri) + throws IOException { HttpPost post = new HttpPost(uri); post.setHeader("GData-Version", "2"); @@ -146,19 +156,21 @@ public HttpResponse testGoogleAddAgenda_1(String token, String uri) EntityTemplate entity = new EntityTemplate(new StringProducer(AGENDA_ENTRY)); entity.setContentType(ATOM_TYPE); post.setEntity(entity); - HttpResponse resp = getClient().execute(post); - System.err.println("POST status: " + resp.getStatusLine().getStatusCode()); - System.err.println("POST message: " + resp.getStatusLine().getReasonPhrase()); - System.err.print("POST response content: "); - resp.getEntity().writeTo(System.err); - System.err.println(); - return resp; + try(final CloseableHttpClient client = getClient()) { + HttpResponse resp = client.execute(post); + System.err.println("POST status: " + resp.getStatusLine().getStatusCode()); + System.err.println("POST message: " + resp.getStatusLine().getReasonPhrase()); + System.err.print("POST response content: "); + resp.getEntity().writeTo(System.err); + System.err.println(); + return resp; + } } @Ignore("Broken authentication?!?") @Test public void testGoogleAddAgendaStd() - throws ClientProtocolException, IOException, URISyntaxException + throws IOException, URISyntaxException { System.err.println(); System.err.println("***** [testGoogleAddAgendaStd]"); @@ -174,7 +186,7 @@ public void testGoogleAddAgendaStd() } private HttpURLConnection testGoogleAddAgendaStd_1(String token, URI uri) - throws ClientProtocolException, IOException, URISyntaxException + throws IOException, URISyntaxException { HttpURLConnection conn = (HttpURLConnection) uri.toURL().openConnection(); conn.setRequestMethod("POST"); @@ -200,44 +212,48 @@ private String authenticate() EntityTemplate entity = new EntityTemplate(new StringProducer(AUTH_CONTENT)); entity.setContentType(FORM_TYPE); auth.setEntity(entity); - HttpResponse resp = getClient().execute(auth); - System.err.println("AUTH status: " + resp.getStatusLine().getStatusCode()); - String token = null; - for ( String s : getStringContent(resp).split("\n") ) { - System.err.println("AUTH content line: " + s); - if ( s.startsWith("Auth=") ) { - token = s.substring(5); + try(final CloseableHttpClient client = getClient()) { + HttpResponse resp = client.execute(auth); + System.err.println("AUTH status: " + resp.getStatusLine().getStatusCode()); + String token = null; + for (String s : getStringContent(resp).split("\n")) { + System.err.println("AUTH content line: " + s); + if (s.startsWith("Auth=")) { + token = s.substring(5); + } } + if (token == null) { + throw new RuntimeException("Token is null!"); + } + return token; } - if ( token == null ) { - throw new RuntimeException("Token is null!"); - } - return token; } @Test public void testResponseBody() - throws ClientProtocolException, IOException, URISyntaxException + throws IOException, URISyntaxException { System.err.println(); System.err.println("***** [testResponseBody]"); HttpGet get = new HttpGet("http://www.fgeorges.org/tmp/xproc-fixed-alternative.mpr-"); - HttpResponse resp = getClient().execute(get); - System.err.println("Status: " + resp.getStatusLine().getStatusCode()); - HttpEntity entity = resp.getEntity(); - System.err.println("Entity class: " + entity.getClass()); - System.err.println("Entity type: " + entity.getContentType()); - System.err.println("Entity encoding: " + entity.getContentEncoding()); - System.err.println("Entity is chunck: " + entity.isChunked()); - System.err.println("Entity is repeat: " + entity.isRepeatable()); - System.err.println("Entity is stream: " + entity.isStreaming()); - System.err.println("***** [/testResponseBody]"); + try(final CloseableHttpClient client = getClient()) { + HttpResponse resp = client.execute(get); + System.err.println("Status: " + resp.getStatusLine().getStatusCode()); + HttpEntity entity = resp.getEntity(); + System.err.println("Entity class: " + entity.getClass()); + System.err.println("Entity type: " + entity.getContentType()); + System.err.println("Entity encoding: " + entity.getContentEncoding()); + System.err.println("Entity is chunck: " + entity.isChunked()); + System.err.println("Entity is repeat: " + entity.isRepeatable()); + System.err.println("Entity is stream: " + entity.isStreaming()); + System.err.println("***** [/testResponseBody]"); + } } @Ignore("Need a certificate file, see 'Unable to find the certificate file' below") @Test public void testTrustSelfSignedKeys() - throws ClientProtocolException, IOException, URISyntaxException, + throws IOException, URISyntaxException, KeyStoreException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException { @@ -255,24 +271,28 @@ public void testTrustSelfSignedKeys() InputStream in = new FileInputStream(in_f); Certificate certif = factory.generateCertificate(in); trustStore.setCertificateEntry("fgeorges.org", certif); - SSLSocketFactory socketFactory = new SSLSocketFactory(trustStore); - Scheme sch = new Scheme("https", socketFactory, 443); - AbstractHttpClient client = getClient(); - client.getConnectionManager().getSchemeRegistry().register(sch); - // - System.err.println(); - System.err.println("***** [testTrustSelfSignedKeys]"); - HttpGet get = new HttpGet("https://www.fgeorges.org/"); - //HttpGet get = new HttpGet("https://mail.google.com/"); - HttpResponse resp = client.execute(get); - System.err.println("Status: " + resp.getStatusLine().getStatusCode()); - System.err.println("***** [/testTrustSelfSignedKeys]"); + final SSLContext sslContext = SSLContextBuilder.create() + .loadTrustMaterial(trustStore, null) + .build(); + + try (CloseableHttpClient client = getClient(sslContext)) { + + // + + System.err.println(); + System.err.println("***** [testTrustSelfSignedKeys]"); + HttpGet get = new HttpGet("https://www.fgeorges.org/"); + //HttpGet get = new HttpGet("https://mail.google.com/"); + HttpResponse resp = client.execute(get); + System.err.println("Status: " + resp.getStatusLine().getStatusCode()); + System.err.println("***** [/testTrustSelfSignedKeys]"); + } } @Test public void testXProcPost() - throws ClientProtocolException, IOException, URISyntaxException + throws IOException, URISyntaxException { System.err.println(); System.err.println("***** [testXProcPost]"); @@ -284,7 +304,7 @@ public void testXProcPost() @Test public void testFGeorgesPost() - throws ClientProtocolException, IOException, URISyntaxException + throws IOException, URISyntaxException { System.err.println(); System.err.println("***** [testFGeorgesPost]"); @@ -295,31 +315,32 @@ public void testFGeorgesPost() } private void doPost(String uri, String content, String type) - throws ClientProtocolException, IOException, URISyntaxException + throws IOException, URISyntaxException { HttpPost post = new HttpPost(uri); EntityTemplate entity = new EntityTemplate(new StringProducer(content)); entity.setContentType(type); post.setEntity(entity); - AbstractHttpClient client = getClient(); - System.err.println("DEBUG: CLIENT: " + client.getClass()); - HttpResponse resp = client.execute(post); - System.err.println("Status: " + resp.getStatusLine().getStatusCode()); - HttpEntity body = resp.getEntity(); - System.err.println("Entity class: " + body.getClass()); - System.err.println("Entity type: " + body.getContentType()); - System.err.println("Entity encoding: " + body.getContentEncoding()); - System.err.println("Entity is chunck: " + body.isChunked()); - System.err.println("Entity is repeat: " + body.isRepeatable()); - System.err.println("Entity is stream: " + body.isStreaming()); - System.err.println("Entity body: "); - body.writeTo(System.err); - System.err.println(); + try(CloseableHttpClient client = getClient()) { + System.err.println("DEBUG: CLIENT: " + client.getClass()); + HttpResponse resp = client.execute(post); + System.err.println("Status: " + resp.getStatusLine().getStatusCode()); + HttpEntity body = resp.getEntity(); + System.err.println("Entity class: " + body.getClass()); + System.err.println("Entity type: " + body.getContentType()); + System.err.println("Entity encoding: " + body.getContentEncoding()); + System.err.println("Entity is chunck: " + body.isChunked()); + System.err.println("Entity is repeat: " + body.isRepeatable()); + System.err.println("Entity is stream: " + body.isStreaming()); + System.err.println("Entity body: "); + body.writeTo(System.err); + System.err.println(); + } } @Test public void testPost() - throws ClientProtocolException, IOException, URISyntaxException + throws IOException, URISyntaxException { System.err.println(); System.err.println("***** [testPost]"); @@ -328,10 +349,12 @@ public void testPost() EntityTemplate entity = new EntityTemplate(new StringProducer("")); entity.setContentType(XML_TYPE); post.setEntity(entity); - HttpResponse resp = getClient().execute(post); - System.err.println("Status: " + resp.getStatusLine().getStatusCode()); - System.err.println(); - System.err.println("***** [/testPost]"); + try(CloseableHttpClient client = getClient()) { + HttpResponse resp = client.execute(post); + System.err.println("Status: " + resp.getStatusLine().getStatusCode()); + System.err.println(); + System.err.println("***** [/testPost]"); + } } private String getStringContent(HttpResponse resp) @@ -364,23 +387,30 @@ public void writeTo(OutputStream out) throws IOException { private byte[] myContent; } - private static AbstractHttpClient getClient() + private static CloseableHttpClient getClient() + { + // FIXME: TODO: How to manage and reuse connections? In test cases, but + // also in production code... + return getClient(null); +// return CLIENT; + } + + private static CloseableHttpClient getClient(final SSLContext sslContext) { // FIXME: TODO: How to manage and reuse connections? In test cases, but // also in production code... - return makeNewClient(); + return makeNewClient(sslContext); // return CLIENT; } - private static AbstractHttpClient makeNewClient() + private static CloseableHttpClient makeNewClient(final SSLContext sslContext) { - AbstractHttpClient client = new DefaultHttpClient(); - HttpRoutePlanner routePlanner = new ProxySelectorRoutePlanner( - client.getConnectionManager().getSchemeRegistry(), - ProxySelector.getDefault()); - client.setRoutePlanner(routePlanner); - client.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false); - return client; + final HttpClientBuilder clientBuilder = HttpClientBuilder.create(); + if(sslContext != null) { + clientBuilder.setSSLContext(sslContext); + } + clientBuilder.setRoutePlanner(new SystemDefaultRoutePlanner(null)); + return clientBuilder.build(); } static { @@ -397,7 +427,7 @@ private static AbstractHttpClient makeNewClient() private static final String FORM_TYPE = "application/x-www-form-urlencoded"; private static final String ATOM_TYPE = "application/atom+xml"; private static final String XML_TYPE = "application/xml"; - private static final AbstractHttpClient CLIENT = makeNewClient(); + private static final CloseableHttpClient CLIENT = makeNewClient(null); // private static final AbstractHttpClient CLIENT = new DefaultHttpClient(); // static { // HttpRoutePlanner routePlanner = new ProxySelectorRoutePlanner( diff --git a/http-client-parent/pom.xml b/http-client-parent/pom.xml new file mode 100644 index 0000000..45a33a4 --- /dev/null +++ b/http-client-parent/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + + org.expath + expath-parent + 1.0-SNAPSHOT + + + + org.expath.http.client + http-client-parent + pom + + EXPath HTTP Client POM + The base POM for all EXPath HTTP Client modules. + + + scm:git:git://github.com/adamretter/expath-http-client-java.git + scm:git:git://github.com/adamretter/expath-http-client-java.git + http://github.com/adamretter/expath-http-client-java + + + + 1.0-SNAPSHOT + + + + + + + maven-assembly-plugin + 3.1.1 + + + + + + + + + org.expath.tools + tools-java + ${expath.tools.version} + + + org.apache.httpcomponents + httpcore + 4.4.11 + + + org.apache.httpcomponents + httpclient + 4.5.8 + + + + + diff --git a/http-client-saxon/pom.xml b/http-client-saxon/pom.xml new file mode 100644 index 0000000..3c139be --- /dev/null +++ b/http-client-saxon/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + + org.expath.http.client + http-client-parent + 1.0-SNAPSHOT + ../http-client-parent/pom.xml + + + http-client-saxon + jar + + EXPath HTTP Client Saxon Implementation + Implementation of HTTP Client module for Saxon + + + scm:git:git://github.com/adamretter/expath-http-client-java.git + scm:git:git://github.com/adamretter/expath-http-client-java.git + http://github.com/adamretter/expath-http-client-java + + + + + org.expath.tools + tools-java + + + org.expath.tools + tools-saxon + ${expath.tools.version} + + + ${project.groupId} + http-client-java + ${project.version} + + + org.expath.packaging + pkg-saxon + 1.0-SNAPSHOT + + + net.sf.saxon + Saxon-HE + + + org.apache.httpcomponents + httpcore + + + + junit + junit + test + + + + \ No newline at end of file diff --git a/http-client-saxon/src/org/expath/httpclient/saxon/SaxonResult.java b/http-client-saxon/src/main/java/org/expath/httpclient/saxon/SaxonResult.java similarity index 78% rename from http-client-saxon/src/org/expath/httpclient/saxon/SaxonResult.java rename to http-client-saxon/src/main/java/org/expath/httpclient/saxon/SaxonResult.java index 14f71a9..9a5e676 100644 --- a/http-client-saxon/src/org/expath/httpclient/saxon/SaxonResult.java +++ b/http-client-saxon/src/main/java/org/expath/httpclient/saxon/SaxonResult.java @@ -9,6 +9,8 @@ package org.expath.httpclient.saxon; +import java.io.*; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import javax.xml.transform.Source; @@ -50,19 +52,44 @@ public Result makeNewResult() } @Override - public void add(String string) + public void add(Reader reader, Charset charset) throws HttpClientException { - Item item = new StringValue(string); - myItems.add(item); + try(final BufferedReader buf_in = new BufferedReader(reader)) { + final StringBuilder builder = new StringBuilder(); + + String buf = null; + while ( (buf = buf_in.readLine()) != null ) { + builder.append(buf); + builder.append('\n'); + } + final String value = builder.toString(); + + Item item = new StringValue(value); + myItems.add(item); + } + catch ( final IOException ex ) { + throw new HttpClientException("error reading HTTP response", ex); + } } @Override - public void add(byte[] bytes) + public void add(InputStream inputStream) throws HttpClientException { - Item item = new Base64BinaryValue(bytes); - myItems.add(item); + try(final ByteArrayOutputStream out = new ByteArrayOutputStream()) { + final byte[] buf = new byte[4096]; + int read = -1; + while ( (read = inputStream.read(buf)) > 0 ) { + out.write(buf, 0, read); + } + final byte[] bytes = out.toByteArray(); + + Item item = new Base64BinaryValue(bytes); + myItems.add(item); + } catch(final IOException e) { + throw new HttpClientException(e.getMessage(), e); + } } @Override diff --git a/http-client-saxon/src/org/expath/httpclient/saxon/SaxonTreeBuilder.java b/http-client-saxon/src/main/java/org/expath/httpclient/saxon/SaxonTreeBuilder.java similarity index 100% rename from http-client-saxon/src/org/expath/httpclient/saxon/SaxonTreeBuilder.java rename to http-client-saxon/src/main/java/org/expath/httpclient/saxon/SaxonTreeBuilder.java diff --git a/http-client-saxon/src/org/expath/httpclient/saxon/SendRequestCall.java b/http-client-saxon/src/main/java/org/expath/httpclient/saxon/SendRequestCall.java similarity index 100% rename from http-client-saxon/src/org/expath/httpclient/saxon/SendRequestCall.java rename to http-client-saxon/src/main/java/org/expath/httpclient/saxon/SendRequestCall.java diff --git a/http-client-saxon/src/org/expath/httpclient/saxon/SendRequestFunction.java b/http-client-saxon/src/main/java/org/expath/httpclient/saxon/SendRequestFunction.java similarity index 100% rename from http-client-saxon/src/org/expath/httpclient/saxon/SendRequestFunction.java rename to http-client-saxon/src/main/java/org/expath/httpclient/saxon/SendRequestFunction.java diff --git a/http-client-saxon/test/org/fgeorges/google/GContactTest.java b/http-client-saxon/src/test/java/org/fgeorges/google/GContactTest.java similarity index 100% rename from http-client-saxon/test/org/fgeorges/google/GContactTest.java rename to http-client-saxon/src/test/java/org/fgeorges/google/GContactTest.java diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..7a0c9d7 --- /dev/null +++ b/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + + org.expath.http.client + http-client-parent + 1.0-SNAPSHOT + http-client-parent/pom.xml + + + http-client + pom + + EXPath HTTP Client + + EXPath HTTP Client module. + + + http-client-parent + http-client-java + http-client-saxon + + + \ No newline at end of file