diff options
Diffstat (limited to 'core/src/main/java/net/oauth/client')
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()); + } + } +} |