summaryrefslogtreecommitdiff
path: root/core/src/main/java/net/oauth/client
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main/java/net/oauth/client')
-rwxr-xr-xcore/src/main/java/net/oauth/client/ExcerptInputStream.java45
-rwxr-xr-xcore/src/main/java/net/oauth/client/OAuthClient.java348
-rwxr-xr-xcore/src/main/java/net/oauth/client/OAuthResponseMessage.java93
-rwxr-xr-xcore/src/main/java/net/oauth/client/URLConnectionClient.java113
-rwxr-xr-xcore/src/main/java/net/oauth/client/URLConnectionResponse.java136
-rwxr-xr-xcore/src/main/java/net/oauth/client/httpclient4/HttpClient4.java125
-rwxr-xr-xcore/src/main/java/net/oauth/client/httpclient4/HttpClientPool.java36
-rwxr-xr-xcore/src/main/java/net/oauth/client/httpclient4/HttpMethodResponse.java137
8 files changed, 1033 insertions, 0 deletions
diff --git a/core/src/main/java/net/oauth/client/ExcerptInputStream.java b/core/src/main/java/net/oauth/client/ExcerptInputStream.java
new file mode 100755
index 0000000..0dae3c3
--- /dev/null
+++ b/core/src/main/java/net/oauth/client/ExcerptInputStream.java
@@ -0,0 +1,45 @@
+package net.oauth.client;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A decorator that retains a copy of the first few bytes of data.
+ * @hide
+ */
+public class ExcerptInputStream extends BufferedInputStream
+{
+ /**
+ * A marker that's appended to the excerpt if it's less than the complete
+ * stream.
+ */
+ public static final byte[] ELLIPSIS = " ...".getBytes();
+
+ public ExcerptInputStream(InputStream in) throws IOException {
+ super(in);
+ mark(LIMIT);
+ int total = 0;
+ int read;
+ while ((read = read(excerpt, total, LIMIT - total)) != -1 && ((total += read) < LIMIT));
+ if (total == LIMIT) {
+ // Only add the ellipsis if there are at least LIMIT bytes
+ System.arraycopy(ELLIPSIS, 0, excerpt, total, ELLIPSIS.length);
+ } else {
+ byte[] tmp = new byte[total];
+ System.arraycopy(excerpt, 0, tmp, 0, total);
+ excerpt = tmp;
+ }
+ reset();
+ }
+
+ private static final int LIMIT = 1024;
+ private byte[] excerpt = new byte[LIMIT + ELLIPSIS.length];
+
+ /** The first few bytes of data, plus ELLIPSIS if there are more bytes. */
+ public byte[] getExcerpt()
+ {
+ return excerpt;
+ }
+
+}
diff --git a/core/src/main/java/net/oauth/client/OAuthClient.java b/core/src/main/java/net/oauth/client/OAuthClient.java
new file mode 100755
index 0000000..4be9e7f
--- /dev/null
+++ b/core/src/main/java/net/oauth/client/OAuthClient.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright 2007, 2008 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.oauth.client;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import net.oauth.OAuth;
+import net.oauth.OAuthAccessor;
+import net.oauth.OAuthConsumer;
+import net.oauth.OAuthException;
+import net.oauth.OAuthMessage;
+import net.oauth.OAuthProblemException;
+import net.oauth.http.HttpClient;
+import net.oauth.http.HttpMessage;
+import net.oauth.http.HttpMessageDecoder;
+import net.oauth.http.HttpResponseMessage;
+
+/**
+ * Methods for an OAuth consumer to request tokens from a service provider.
+ * <p>
+ * This class can also be used to request access to protected resources, in some
+ * cases. But not in all cases. For example, this class can't handle arbitrary
+ * HTTP headers.
+ * <p>
+ * Methods of this class return a response as an OAuthMessage, from which you
+ * can get a body or parameters but not both. Calling a getParameter method will
+ * read and close the body (like readBodyAsString), so you can't read it later.
+ * If you read or close the body first, then getParameter can't read it. The
+ * response headers should tell you whether the response contains encoded
+ * parameters, that is whether you should call getParameter or not.
+ * <p>
+ * Methods of this class don't follow redirects. When they receive a redirect
+ * response, they throw an OAuthProblemException, with properties
+ * HttpResponseMessage.STATUS_CODE = the redirect code
+ * HttpResponseMessage.LOCATION = the redirect URL. Such a redirect can't be
+ * handled at the HTTP level, if the second request must carry another OAuth
+ * signature (with different parameters). For example, Google's Service Provider
+ * routinely redirects requests for access to protected resources, and requires
+ * the redirected request to be signed.
+ *
+ * @author John Kristian
+ * @hide
+ */
+public class OAuthClient {
+
+ public OAuthClient(HttpClient http)
+ {
+ this.http = http;
+ }
+
+ private HttpClient http;
+
+ public void setHttpClient(HttpClient http) {
+ this.http = http;
+ }
+
+ public HttpClient getHttpClient() {
+ return http;
+ }
+
+ /**
+ * Get a fresh request token from the service provider.
+ *
+ * @param accessor
+ * should contain a consumer that contains a non-null consumerKey
+ * and consumerSecret. Also,
+ * accessor.consumer.serviceProvider.requestTokenURL should be
+ * the URL (determined by the service provider) for getting a
+ * request token.
+ * @throws OAuthProblemException
+ * the HTTP response status code was not 200 (OK)
+ */
+ public void getRequestToken(OAuthAccessor accessor) throws IOException,
+ OAuthException, URISyntaxException {
+ getRequestToken(accessor, null);
+ }
+
+ /**
+ * Get a fresh request token from the service provider.
+ *
+ * @param accessor
+ * should contain a consumer that contains a non-null consumerKey
+ * and consumerSecret. Also,
+ * accessor.consumer.serviceProvider.requestTokenURL should be
+ * the URL (determined by the service provider) for getting a
+ * request token.
+ * @param httpMethod
+ * typically OAuthMessage.POST or OAuthMessage.GET, or null to
+ * use the default method.
+ * @throws OAuthProblemException
+ * the HTTP response status code was not 200 (OK)
+ */
+ public void getRequestToken(OAuthAccessor accessor, String httpMethod)
+ throws IOException, OAuthException, URISyntaxException {
+ getRequestToken(accessor, httpMethod, null);
+ }
+
+ /** Get a fresh request token from the service provider.
+ *
+ * @param accessor
+ * should contain a consumer that contains a non-null consumerKey
+ * and consumerSecret. Also,
+ * accessor.consumer.serviceProvider.requestTokenURL should be
+ * the URL (determined by the service provider) for getting a
+ * request token.
+ * @param httpMethod
+ * typically OAuthMessage.POST or OAuthMessage.GET, or null to
+ * use the default method.
+ * @param parameters
+ * additional parameters for this request, or null to indicate
+ * that there are no additional parameters.
+ * @throws OAuthProblemException
+ * the HTTP response status code was not 200 (OK)
+ */
+ public void getRequestToken(OAuthAccessor accessor, String httpMethod,
+ Collection<? extends Map.Entry> parameters) throws IOException,
+ OAuthException, URISyntaxException {
+ accessor.accessToken = null;
+ accessor.tokenSecret = null;
+ {
+ // This code supports the 'Variable Accessor Secret' extension
+ // described in http://oauth.pbwiki.com/AccessorSecret
+ Object accessorSecret = accessor
+ .getProperty(OAuthConsumer.ACCESSOR_SECRET);
+ if (accessorSecret != null) {
+ List<Map.Entry> p = (parameters == null) ? new ArrayList<Map.Entry>(
+ 1)
+ : new ArrayList<Map.Entry>(parameters);
+ p.add(new OAuth.Parameter("oauth_accessor_secret",
+ accessorSecret.toString()));
+ parameters = p;
+ // But don't modify the caller's parameters.
+ }
+ }
+ OAuthMessage response = invoke(accessor, httpMethod,
+ accessor.consumer.serviceProvider.requestTokenURL, parameters);
+ accessor.requestToken = response.getParameter(OAuth.OAUTH_TOKEN);
+ accessor.tokenSecret = response.getParameter(OAuth.OAUTH_TOKEN_SECRET);
+ response.requireParameters(OAuth.OAUTH_TOKEN, OAuth.OAUTH_TOKEN_SECRET);
+ }
+
+ /**
+ * Get an access token from the service provider, in exchange for an
+ * authorized request token.
+ *
+ * @param accessor
+ * should contain a non-null requestToken and tokenSecret, and a
+ * consumer that contains a consumerKey and consumerSecret. Also,
+ * accessor.consumer.serviceProvider.accessTokenURL should be the
+ * URL (determined by the service provider) for getting an access
+ * token.
+ * @param httpMethod
+ * typically OAuthMessage.POST or OAuthMessage.GET, or null to
+ * use the default method.
+ * @param parameters
+ * additional parameters for this request, or null to indicate
+ * that there are no additional parameters.
+ * @throws OAuthProblemException
+ * the HTTP response status code was not 200 (OK)
+ */
+ public OAuthMessage getAccessToken(OAuthAccessor accessor, String httpMethod,
+ Collection<? extends Map.Entry> parameters) throws IOException, OAuthException, URISyntaxException {
+ if (accessor.requestToken != null) {
+ if (parameters == null) {
+ parameters = OAuth.newList(OAuth.OAUTH_TOKEN, accessor.requestToken);
+ } else if (!OAuth.newMap(parameters).containsKey(OAuth.OAUTH_TOKEN)) {
+ List<Map.Entry> p = new ArrayList<Map.Entry>(parameters);
+ p.add(new OAuth.Parameter(OAuth.OAUTH_TOKEN, accessor.requestToken));
+ parameters = p;
+ }
+ }
+ OAuthMessage response = invoke(accessor, httpMethod,
+ accessor.consumer.serviceProvider.accessTokenURL, parameters);
+ response.requireParameters(OAuth.OAUTH_TOKEN, OAuth.OAUTH_TOKEN_SECRET);
+ accessor.accessToken = response.getParameter(OAuth.OAUTH_TOKEN);
+ accessor.tokenSecret = response.getParameter(OAuth.OAUTH_TOKEN_SECRET);
+ return response;
+ }
+
+ /**
+ * Construct a request message, send it to the service provider and get the
+ * response.
+ *
+ * @param httpMethod
+ * the HTTP request method, or null to use the default method
+ * @return the response
+ * @throws URISyntaxException
+ * the given url isn't valid syntactically
+ * @throws OAuthProblemException
+ * the HTTP response status code was not 200 (OK)
+ */
+ public OAuthMessage invoke(OAuthAccessor accessor, String httpMethod,
+ String url, Collection<? extends Map.Entry> parameters)
+ throws IOException, OAuthException, URISyntaxException {
+ String ps = (String) accessor.consumer.getProperty(PARAMETER_STYLE);
+ ParameterStyle style = (ps == null) ? ParameterStyle.BODY : Enum
+ .valueOf(ParameterStyle.class, ps);
+ OAuthMessage request = accessor.newRequestMessage(httpMethod, url,
+ parameters);
+ return invoke(request, style);
+ }
+
+ /**
+ * The name of the OAuthConsumer property whose value is the ParameterStyle
+ * to be used by invoke.
+ */
+ public static final String PARAMETER_STYLE = "parameterStyle";
+
+ /**
+ * The name of the OAuthConsumer property whose value is the Accept-Encoding
+ * header in HTTP requests.
+ * @deprecated use {@link OAuthConsumer#ACCEPT_ENCODING} instead
+ */
+ @Deprecated
+ public static final String ACCEPT_ENCODING = OAuthConsumer.ACCEPT_ENCODING;
+
+ /**
+ * Construct a request message, send it to the service provider and get the
+ * response.
+ *
+ * @return the response
+ * @throws URISyntaxException
+ * the given url isn't valid syntactically
+ * @throws OAuthProblemException
+ * the HTTP response status code was not 200 (OK)
+ */
+ public OAuthMessage invoke(OAuthAccessor accessor, String url,
+ Collection<? extends Map.Entry> parameters) throws IOException,
+ OAuthException, URISyntaxException {
+ return invoke(accessor, null, url, parameters);
+ }
+
+ /**
+ * Send a request message to the service provider and get the response.
+ *
+ * @return the response
+ * @throws IOException
+ * failed to communicate with the service provider
+ * @throws OAuthProblemException
+ * the HTTP response status code was not 200 (OK)
+ */
+ public OAuthMessage invoke(OAuthMessage request, ParameterStyle style)
+ throws IOException, OAuthException {
+ final boolean isPost = POST.equalsIgnoreCase(request.method);
+ InputStream body = request.getBodyAsStream();
+ if (style == ParameterStyle.BODY && !(isPost && body == null)) {
+ style = ParameterStyle.QUERY_STRING;
+ }
+ String url = request.URL;
+ final List<Map.Entry<String, String>> headers =
+ new ArrayList<Map.Entry<String, String>>(request.getHeaders());
+ switch (style) {
+ case QUERY_STRING:
+ url = OAuth.addParameters(url, request.getParameters());
+ break;
+ case BODY: {
+ byte[] form = OAuth.formEncode(request.getParameters()).getBytes(
+ request.getBodyEncoding());
+ headers.add(new OAuth.Parameter(HttpMessage.CONTENT_TYPE,
+ OAuth.FORM_ENCODED));
+ headers.add(new OAuth.Parameter(CONTENT_LENGTH, form.length + ""));
+ body = new ByteArrayInputStream(form);
+ break;
+ }
+ case AUTHORIZATION_HEADER:
+ headers.add(new OAuth.Parameter("Authorization", request.getAuthorizationHeader(null)));
+ // Find the non-OAuth parameters:
+ List<Map.Entry<String, String>> others = request.getParameters();
+ if (others != null && !others.isEmpty()) {
+ others = new ArrayList<Map.Entry<String, String>>(others);
+ for (Iterator<Map.Entry<String, String>> p = others.iterator(); p
+ .hasNext();) {
+ if (p.next().getKey().startsWith("oauth_")) {
+ p.remove();
+ }
+ }
+ // Place the non-OAuth parameters elsewhere in the request:
+ if (isPost && body == null) {
+ byte[] form = OAuth.formEncode(others).getBytes(
+ request.getBodyEncoding());
+ headers.add(new OAuth.Parameter(HttpMessage.CONTENT_TYPE,
+ OAuth.FORM_ENCODED));
+ headers.add(new OAuth.Parameter(CONTENT_LENGTH, form.length
+ + ""));
+ body = new ByteArrayInputStream(form);
+ } else {
+ url = OAuth.addParameters(url, others);
+ }
+ }
+ break;
+ }
+ final HttpMessage httpRequest = new HttpMessage(request.method, new URL(url), body);
+ httpRequest.headers.addAll(headers);
+ HttpResponseMessage httpResponse = http.execute(httpRequest);
+ httpResponse = HttpMessageDecoder.decode(httpResponse);
+ OAuthMessage response = new OAuthResponseMessage(httpResponse);
+ if (httpResponse.getStatusCode() != HttpResponseMessage.STATUS_OK) {
+ OAuthProblemException problem = new OAuthProblemException();
+ try {
+ response.getParameters(); // decode the response body
+ } catch (IOException ignored) {
+ }
+ problem.getParameters().putAll(response.getDump());
+ try {
+ InputStream b = response.getBodyAsStream();
+ if (b != null) {
+ b.close(); // release resources
+ }
+ } catch (IOException ignored) {
+ }
+ throw problem;
+ }
+ return response;
+ }
+
+ /** Where to place parameters in an HTTP message. */
+ public enum ParameterStyle {
+ AUTHORIZATION_HEADER, BODY, QUERY_STRING;
+ };
+
+ protected static final String PUT = OAuthMessage.PUT;
+ protected static final String POST = OAuthMessage.POST;
+ protected static final String DELETE = OAuthMessage.DELETE;
+ protected static final String CONTENT_LENGTH = HttpMessage.CONTENT_LENGTH;
+
+}
diff --git a/core/src/main/java/net/oauth/client/OAuthResponseMessage.java b/core/src/main/java/net/oauth/client/OAuthResponseMessage.java
new file mode 100755
index 0000000..88a1417
--- /dev/null
+++ b/core/src/main/java/net/oauth/client/OAuthResponseMessage.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2008 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.oauth.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import net.oauth.OAuth;
+import net.oauth.OAuthMessage;
+import net.oauth.OAuthProblemException;
+import net.oauth.http.HttpMessage;
+import net.oauth.http.HttpResponseMessage;
+
+/**
+ * An HTTP response, encapsulated as an OAuthMessage.
+ *
+ * @author John Kristian
+ * @hide
+ */
+final class OAuthResponseMessage extends OAuthMessage
+{
+ OAuthResponseMessage(HttpResponseMessage http) throws IOException
+ {
+ super(http.method, http.url.toExternalForm(), null);
+ this.http = http;
+ getHeaders().addAll(http.headers);
+ for (Map.Entry<String, String> header : http.headers) {
+ if ("WWW-Authenticate".equalsIgnoreCase(header.getKey())) {
+ for (OAuth.Parameter parameter : decodeAuthorization(header.getValue())) {
+ if (!"realm".equalsIgnoreCase(parameter.getKey())) {
+ addParameter(parameter);
+ }
+ }
+ }
+ }
+ }
+
+ private final HttpMessage http;
+
+ @Override
+ public InputStream getBodyAsStream() throws IOException
+ {
+ return http.getBody();
+ }
+
+ @Override
+ public String getBodyEncoding()
+ {
+ return http.getContentCharset();
+ }
+
+ @Override
+ protected void completeParameters() throws IOException
+ {
+ super.completeParameters();
+ String body = readBodyAsString();
+ if (body != null) {
+ addParameters(OAuth.decodeForm(body.trim()));
+ }
+ }
+
+ @Override
+ protected void dump(Map<String, Object> into) throws IOException
+ {
+ super.dump(into);
+ http.dump(into);
+ }
+
+ @Override
+ public void requireParameters(String... names) throws OAuthProblemException, IOException {
+ try {
+ super.requireParameters(names);
+ } catch (OAuthProblemException problem) {
+ problem.getParameters().putAll(getDump());
+ throw problem;
+ }
+ }
+
+}
diff --git a/core/src/main/java/net/oauth/client/URLConnectionClient.java b/core/src/main/java/net/oauth/client/URLConnectionClient.java
new file mode 100755
index 0000000..18bdf47
--- /dev/null
+++ b/core/src/main/java/net/oauth/client/URLConnectionClient.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2008 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.oauth.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import net.oauth.http.HttpClient;
+import net.oauth.http.HttpMessage;
+import net.oauth.http.HttpResponseMessage;
+
+/**
+ * An HttpClient based on HttpURLConnection.
+ * <p>
+ * HttpClient3 or HttpClient4 perform better than this class, as a rule; since
+ * they do things like connection pooling. They also support reading the body
+ * of an HTTP response whose status code isn't 200 (OK), which can enable your
+ * application to handle problems better.
+ *
+ * @author John Kristian
+ * @hide
+ */
+public class URLConnectionClient implements HttpClient {
+
+ /** Send a message to the service provider and get the response. */
+ public HttpResponseMessage execute(HttpMessage request) throws IOException {
+ final String httpMethod = request.method;
+ final Collection<Map.Entry<String, String>> addHeaders = request.headers;
+ final URL url = request.url;
+ final URLConnection connection = url.openConnection();
+ connection.setDoInput(true);
+ if (connection instanceof HttpURLConnection) {
+ HttpURLConnection http = (HttpURLConnection) connection;
+ http.setRequestMethod(httpMethod);
+ http.setInstanceFollowRedirects(false);
+ }
+ StringBuilder headers = new StringBuilder(httpMethod);
+ {
+ headers.append(" ").append(url.getPath());
+ String query = url.getQuery();
+ if (query != null && query.length() > 0) {
+ headers.append("?").append(query);
+ }
+ headers.append(EOL);
+ for (Map.Entry<String, List<String>> header : connection
+ .getRequestProperties().entrySet()) {
+ String key = header.getKey();
+ for (String value : header.getValue()) {
+ headers.append(key).append(": ").append(value).append(EOL);
+ }
+ }
+ }
+ String contentLength = null;
+ for (Map.Entry<String, String> header : addHeaders) {
+ String key = header.getKey();
+ if (HttpMessage.CONTENT_LENGTH.equalsIgnoreCase(key)
+ && connection instanceof HttpURLConnection) {
+ contentLength = header.getValue();
+ } else {
+ connection.setRequestProperty(key, header.getValue());
+ }
+ headers.append(key).append(": ").append(header.getValue()).append(EOL);
+ }
+ byte[] excerpt = null;
+ final InputStream body = request.getBody();
+ if (body != null) {
+ try {
+ if (contentLength != null) {
+ ((HttpURLConnection) connection)
+ .setFixedLengthStreamingMode(Integer.parseInt(contentLength));
+ }
+ connection.setDoOutput(true);
+ OutputStream output = connection.getOutputStream();
+ try {
+ final ExcerptInputStream ex = new ExcerptInputStream(body);
+ byte[] b = new byte[1024];
+ for (int n; 0 < (n = ex.read(b));) {
+ output.write(b, 0, n);
+ }
+ excerpt = ex.getExcerpt();
+ } finally {
+ output.close();
+ }
+ } finally {
+ body.close();
+ }
+ }
+ return new URLConnectionResponse(request, headers.toString(), excerpt, connection);
+ }
+
+ private static final String EOL = HttpResponseMessage.EOL;
+
+}
diff --git a/core/src/main/java/net/oauth/client/URLConnectionResponse.java b/core/src/main/java/net/oauth/client/URLConnectionResponse.java
new file mode 100755
index 0000000..4061ca8
--- /dev/null
+++ b/core/src/main/java/net/oauth/client/URLConnectionResponse.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2008 Netflix, Inc.
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.oauth.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import net.oauth.OAuth;
+import net.oauth.http.HttpMessage;
+import net.oauth.http.HttpResponseMessage;
+
+/**
+ * The response part of a URLConnection, encapsulated as an OAuthMessage.
+ *
+ * @author John Kristian
+ * @hide
+ */
+public class URLConnectionResponse extends HttpResponseMessage {
+
+ /**
+ * Construct an OAuthMessage from the HTTP response, including parameters
+ * from OAuth WWW-Authenticate headers and the body. The header parameters
+ * come first, followed by the ones from the response body.
+ */
+ public URLConnectionResponse(HttpMessage request, String requestHeaders,
+ byte[] requestExcerpt, URLConnection connection) throws IOException {
+ super(request.method, request.url);
+ this.requestHeaders = requestHeaders;
+ this.requestExcerpt = requestExcerpt;
+ this.requestEncoding = request.getContentCharset();
+ this.connection = connection;
+ this.headers.addAll(getHeaders());
+ }
+
+ private final String requestHeaders;
+ private final byte[] requestExcerpt;
+ private final String requestEncoding;
+ private final URLConnection connection;
+
+ @Override
+ public int getStatusCode() throws IOException {
+ if (connection instanceof HttpURLConnection) {
+ return ((HttpURLConnection) connection).getResponseCode();
+ }
+ return STATUS_OK;
+ }
+
+ @Override
+ public InputStream openBody() {
+ try {
+ return connection.getInputStream();
+ } catch (IOException ohWell) {
+ }
+ return null;
+ }
+
+ private List<Map.Entry<String, String>> getHeaders() {
+ List<Map.Entry<String, String>> headers = new ArrayList<Map.Entry<String, String>>();
+ boolean foundContentType = false;
+ String value;
+ for (int i = 0; (value = connection.getHeaderField(i)) != null; ++i) {
+ String name = connection.getHeaderFieldKey(i);
+ if (name != null) {
+ headers.add(new OAuth.Parameter(name, value));
+ if (CONTENT_TYPE.equalsIgnoreCase(name)) {
+ foundContentType = true;
+ }
+ }
+ }
+ if (!foundContentType) {
+ headers.add(new OAuth.Parameter(CONTENT_TYPE, connection
+ .getContentType()));
+ }
+ return headers;
+ }
+ /** Return a complete description of the HTTP exchange. */
+ @Override
+ public void dump(Map<String, Object> into) throws IOException {
+ super.dump(into);
+ {
+ StringBuilder request = new StringBuilder(requestHeaders);
+ request.append(EOL);
+ if (requestExcerpt != null) {
+ request.append(new String(requestExcerpt, requestEncoding));
+ }
+ into.put(REQUEST, request.toString());
+ }
+ {
+ HttpURLConnection http = (connection instanceof HttpURLConnection) ? (HttpURLConnection) connection
+ : null;
+ StringBuilder response = new StringBuilder();
+ String value;
+ for (int i = 0; (value = connection.getHeaderField(i)) != null; ++i) {
+ String name = connection.getHeaderFieldKey(i);
+ if (i == 0 && name != null && http != null) {
+ String firstLine = "HTTP " + getStatusCode();
+ String message = http.getResponseMessage();
+ if (message != null) {
+ firstLine += (" " + message);
+ }
+ response.append(firstLine).append(EOL);
+ }
+ if (name != null) {
+ response.append(name).append(": ");
+ name = name.toLowerCase();
+ }
+ response.append(value).append(EOL);
+ }
+ response.append(EOL);
+ if (body != null) {
+ response.append(new String(((ExcerptInputStream) body)
+ .getExcerpt(), getContentCharset()));
+ }
+ into.put(HttpMessage.RESPONSE, response.toString());
+ }
+ }
+
+}
diff --git a/core/src/main/java/net/oauth/client/httpclient4/HttpClient4.java b/core/src/main/java/net/oauth/client/httpclient4/HttpClient4.java
new file mode 100755
index 0000000..3376cc8
--- /dev/null
+++ b/core/src/main/java/net/oauth/client/httpclient4/HttpClient4.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2008 Sean Sullivan
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.oauth.client.httpclient4;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Map;
+import net.oauth.client.ExcerptInputStream;
+import net.oauth.http.HttpMessage;
+import net.oauth.http.HttpResponseMessage;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+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.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.params.ClientPNames;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.params.HttpParams;
+
+/**
+ * Utility methods for an OAuth client based on the <a
+ * href="http://hc.apache.org">Apache HttpClient</a>.
+ *
+ * @author Sean Sullivan
+ * @hide
+ */
+public class HttpClient4 implements net.oauth.http.HttpClient {
+
+ public HttpClient4() {
+ this(SHARED_CLIENT);
+ }
+
+ public HttpClient4(HttpClientPool clientPool) {
+ this.clientPool = clientPool;
+ }
+
+ private final HttpClientPool clientPool;
+
+ public HttpResponseMessage execute(HttpMessage request) throws IOException {
+ final String method = request.method;
+ final String url = request.url.toExternalForm();
+ final InputStream body = request.getBody();
+ final boolean isDelete = DELETE.equalsIgnoreCase(method);
+ final boolean isPost = POST.equalsIgnoreCase(method);
+ final boolean isPut = PUT.equalsIgnoreCase(method);
+ byte[] excerpt = null;
+ HttpRequestBase httpRequest;
+ if (isPost || isPut) {
+ HttpEntityEnclosingRequestBase entityEnclosingMethod =
+ isPost ? new HttpPost(url) : new HttpPut(url);
+ if (body != null) {
+ ExcerptInputStream e = new ExcerptInputStream(body);
+ excerpt = e.getExcerpt();
+ String length = request.removeHeaders(HttpMessage.CONTENT_LENGTH);
+ entityEnclosingMethod.setEntity(new InputStreamEntity(e,
+ (length == null) ? -1 : Long.parseLong(length)));
+ }
+ httpRequest = entityEnclosingMethod;
+ } else if (isDelete) {
+ httpRequest = new HttpDelete(url);
+ } else {
+ httpRequest = new HttpGet(url);
+ }
+ for (Map.Entry<String, String> header : request.headers) {
+ httpRequest.addHeader(header.getKey(), header.getValue());
+ }
+ HttpClient client = clientPool.getHttpClient(new URL(httpRequest.getURI().toString()));
+ client.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false);
+ HttpResponse httpResponse = client.execute(httpRequest);
+ return new HttpMethodResponse(httpRequest, httpResponse, excerpt, request.getContentCharset());
+ }
+
+ private static final HttpClientPool SHARED_CLIENT = new SingleClient();
+
+ /**
+ * A pool that simply shares a single HttpClient. An HttpClient owns a pool
+ * of TCP connections. So, callers that share an HttpClient will share
+ * connections. Sharing improves performance (by avoiding the overhead of
+ * creating connections) and uses fewer resources in the client and its
+ * servers.
+ */
+ private static class SingleClient implements HttpClientPool
+ {
+ SingleClient()
+ {
+ HttpClient client = new DefaultHttpClient();
+ ClientConnectionManager mgr = client.getConnectionManager();
+ if (!(mgr instanceof ThreadSafeClientConnManager)) {
+ HttpParams params = client.getParams();
+ client = new DefaultHttpClient(new ThreadSafeClientConnManager(params,
+ mgr.getSchemeRegistry()), params);
+ }
+ this.client = client;
+ }
+
+ private final HttpClient client;
+
+ public HttpClient getHttpClient(URL server)
+ {
+ return client;
+ }
+ }
+
+}
diff --git a/core/src/main/java/net/oauth/client/httpclient4/HttpClientPool.java b/core/src/main/java/net/oauth/client/httpclient4/HttpClientPool.java
new file mode 100755
index 0000000..90f9ade
--- /dev/null
+++ b/core/src/main/java/net/oauth/client/httpclient4/HttpClientPool.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2008 Sean Sullivan
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.oauth.client.httpclient4;
+
+import java.net.URL;
+
+/**
+ *
+ * A source of Apache HttpClient 4 objects.
+ *
+ * This class relies on <a href="http://hc.apache.org">Apache HttpClient</a>
+ * version 4.
+ *
+ * @author Sean Sullivan
+ * @hide
+ */
+public interface HttpClientPool {
+
+ /** Get the appropriate HttpClient for sending a request to the given URL. */
+ public org.apache.http.client.HttpClient getHttpClient(URL server);
+
+}
diff --git a/core/src/main/java/net/oauth/client/httpclient4/HttpMethodResponse.java b/core/src/main/java/net/oauth/client/httpclient4/HttpMethodResponse.java
new file mode 100755
index 0000000..1f7b747
--- /dev/null
+++ b/core/src/main/java/net/oauth/client/httpclient4/HttpMethodResponse.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2008 Sean Sullivan
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.oauth.client.httpclient4;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import net.oauth.OAuth;
+import net.oauth.client.ExcerptInputStream;
+import net.oauth.http.HttpMessage;
+import net.oauth.http.HttpResponseMessage;
+import org.apache.http.Header;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpRequestBase;
+
+/**
+ * An HttpResponse, encapsulated as an OAuthMessage.
+ *
+ * This class relies on <a href="http://hc.apache.org">Apache HttpClient</a>
+ * version 4.
+ *
+ * @author Sean Sullivan
+ * @hide
+ */
+public class HttpMethodResponse extends HttpResponseMessage
+{
+
+ /**
+ * Construct an OAuthMessage from the HTTP response, including parameters
+ * from OAuth WWW-Authenticate headers and the body. The header parameters
+ * come first, followed by the ones from the response body.
+ */
+ public HttpMethodResponse(HttpRequestBase request, HttpResponse response, byte[] requestBody,
+ String requestEncoding) throws IOException
+ {
+ super(request.getMethod(), new URL(request.getURI().toString()));
+ this.httpRequest = request;
+ this.httpResponse = response;
+ this.requestBody = requestBody;
+ this.requestEncoding = requestEncoding;
+ this.headers.addAll(getHeaders());
+ }
+
+ private final HttpRequestBase httpRequest;
+ private final HttpResponse httpResponse;
+ private final byte[] requestBody;
+ private final String requestEncoding;
+
+ @Override
+ public int getStatusCode()
+ {
+ return httpResponse.getStatusLine().getStatusCode();
+ }
+
+ @Override
+ public InputStream openBody() throws IOException
+ {
+ return httpResponse.getEntity().getContent();
+ }
+
+ private List<Map.Entry<String, String>> getHeaders()
+ {
+ List<Map.Entry<String, String>> headers = new ArrayList<Map.Entry<String, String>>();
+ Header[] allHeaders = httpResponse.getAllHeaders();
+ if (allHeaders != null) {
+ for (Header header : allHeaders) {
+ headers.add(new OAuth.Parameter(header.getName(), header.getValue()));
+ }
+ }
+ return headers;
+ }
+
+ /** Return a complete description of the HTTP exchange. */
+ @Override
+ public void dump(Map<String, Object> into) throws IOException
+ {
+ super.dump(into);
+ {
+ StringBuilder request = new StringBuilder(httpRequest.getMethod());
+ request.append(" ").append(httpRequest.getURI().getPath());
+ String query = httpRequest.getURI().getQuery();
+ if (query != null && query.length() > 0) {
+ request.append("?").append(query);
+ }
+ request.append(EOL);
+ for (Header header : httpRequest.getAllHeaders()) {
+ request.append(header.getName()).append(": ").append(header.getValue()).append(EOL);
+ }
+ if (httpRequest instanceof HttpEntityEnclosingRequest) {
+ HttpEntityEnclosingRequest r = (HttpEntityEnclosingRequest) httpRequest;
+ long contentLength = r.getEntity().getContentLength();
+ if (contentLength >= 0) {
+ request.append("Content-Length: ").append(contentLength).append(EOL);
+ }
+ }
+ request.append(EOL);
+ if (requestBody != null) {
+ request.append(new String(requestBody, requestEncoding));
+ }
+ into.put(REQUEST, request.toString());
+ }
+ {
+ StringBuilder response = new StringBuilder();
+ String value = httpResponse.getStatusLine().toString();
+ response.append(value).append(EOL);
+ for (Header header : httpResponse.getAllHeaders()) {
+ String name = header.getName();
+ value = header.getValue();
+ response.append(name).append(": ").append(value).append(EOL);
+ }
+ response.append(EOL);
+ if (body != null) {
+ response.append(new String(((ExcerptInputStream) body).getExcerpt(),
+ getContentCharset()));
+ }
+ into.put(HttpMessage.RESPONSE, response.toString());
+ }
+ }
+}