Blame | Last modification | View Log | RSS feed
/** Copyright (C) 2011 The Android Open Source Project** 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 com.android.volley.toolbox;import com.android.volley.AuthFailureError;import com.android.volley.Request;import com.android.volley.Request.Method;import org.apache.http.Header;import org.apache.http.HttpEntity;import org.apache.http.HttpResponse;import org.apache.http.HttpStatus;import org.apache.http.ProtocolVersion;import org.apache.http.StatusLine;import org.apache.http.entity.BasicHttpEntity;import org.apache.http.message.BasicHeader;import org.apache.http.message.BasicHttpResponse;import org.apache.http.message.BasicStatusLine;import java.io.DataOutputStream;import java.io.IOException;import java.io.InputStream;import java.net.HttpURLConnection;import java.net.URL;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Map.Entry;import javax.net.ssl.HttpsURLConnection;import javax.net.ssl.SSLSocketFactory;/*** An {@link HttpStack} based on {@link HttpURLConnection}.*/public class HurlStack implements HttpStack {private static final String HEADER_CONTENT_TYPE = "Content-Type";/*** An interface for transforming URLs before use.*/public interface UrlRewriter {/*** Returns a URL to use instead of the provided one, or null to indicate* this URL should not be used at all.*/String rewriteUrl(String originalUrl);}private final UrlRewriter mUrlRewriter;private final SSLSocketFactory mSslSocketFactory;public HurlStack() {this(null);}/*** @param urlRewriter Rewriter to use for request URLs*/public HurlStack(UrlRewriter urlRewriter) {this(urlRewriter, null);}/*** @param urlRewriter Rewriter to use for request URLs* @param sslSocketFactory SSL factory to use for HTTPS connections*/public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {mUrlRewriter = urlRewriter;mSslSocketFactory = sslSocketFactory;}@Overridepublic HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)throws IOException, AuthFailureError {String url = request.getUrl();HashMap<String, String> map = new HashMap<String, String>();map.putAll(request.getHeaders());map.putAll(additionalHeaders);if (mUrlRewriter != null) {String rewritten = mUrlRewriter.rewriteUrl(url);if (rewritten == null) {throw new IOException("URL blocked by rewriter: " + url);}url = rewritten;}URL parsedUrl = new URL(url);HttpURLConnection connection = openConnection(parsedUrl, request);for (String headerName : map.keySet()) {connection.addRequestProperty(headerName, map.get(headerName));}setConnectionParametersForRequest(connection, request);// Initialize HttpResponse with data from the HttpURLConnection.ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);int responseCode = connection.getResponseCode();if (responseCode == -1) {// -1 is returned by getResponseCode() if the response code could not be retrieved.// Signal to the caller that something was wrong with the connection.throw new IOException("Could not retrieve response code from HttpUrlConnection.");}StatusLine responseStatus = new BasicStatusLine(protocolVersion,connection.getResponseCode(), connection.getResponseMessage());BasicHttpResponse response = new BasicHttpResponse(responseStatus);if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {response.setEntity(entityFromConnection(connection));}for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {if (header.getKey() != null) {Header h = new BasicHeader(header.getKey(), header.getValue().get(0));response.addHeader(h);}}return response;}/*** Checks if a response message contains a body.* @see <a href="https://tools.ietf.org/html/rfc7230#section-3.3">RFC 7230 section 3.3</a>* @param requestMethod request method* @param responseCode response status code* @return whether the response has a body*/private static boolean hasResponseBody(int requestMethod, int responseCode) {return requestMethod != Request.Method.HEAD&& !(HttpStatus.SC_CONTINUE <= responseCode && responseCode < HttpStatus.SC_OK)&& responseCode != HttpStatus.SC_NO_CONTENT&& responseCode != HttpStatus.SC_NOT_MODIFIED;}/*** Initializes an {@link HttpEntity} from the given {@link HttpURLConnection}.* @param connection* @return an HttpEntity populated with data from <code>connection</code>.*/private static HttpEntity entityFromConnection(HttpURLConnection connection) {BasicHttpEntity entity = new BasicHttpEntity();InputStream inputStream;try {inputStream = connection.getInputStream();} catch (IOException ioe) {inputStream = connection.getErrorStream();}entity.setContent(inputStream);entity.setContentLength(connection.getContentLength());entity.setContentEncoding(connection.getContentEncoding());entity.setContentType(connection.getContentType());return entity;}/*** Create an {@link HttpURLConnection} for the specified {@code url}.*/protected HttpURLConnection createConnection(URL url) throws IOException {return (HttpURLConnection) url.openConnection();}/*** Opens an {@link HttpURLConnection} with parameters.* @param url* @return an open connection* @throws IOException*/private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {HttpURLConnection connection = createConnection(url);int timeoutMs = request.getTimeoutMs();connection.setConnectTimeout(timeoutMs);connection.setReadTimeout(timeoutMs);connection.setUseCaches(false);connection.setDoInput(true);// use caller-provided custom SslSocketFactory, if any, for HTTPSif ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory);}return connection;}@SuppressWarnings("deprecation")/* package */ static void setConnectionParametersForRequest(HttpURLConnection connection,Request<?> request) throws IOException, AuthFailureError {switch (request.getMethod()) {case Method.DEPRECATED_GET_OR_POST:// This is the deprecated way that needs to be handled for backwards compatibility.// If the request's post body is null, then the assumption is that the request is// GET. Otherwise, it is assumed that the request is a POST.byte[] postBody = request.getPostBody();if (postBody != null) {// Prepare output. There is no need to set Content-Length explicitly,// since this is handled by HttpURLConnection using the size of the prepared// output stream.connection.setDoOutput(true);connection.setRequestMethod("POST");connection.addRequestProperty(HEADER_CONTENT_TYPE,request.getPostBodyContentType());DataOutputStream out = new DataOutputStream(connection.getOutputStream());out.write(postBody);out.close();}break;case Method.GET:// Not necessary to set the request method because connection defaults to GET but// being explicit here.connection.setRequestMethod("GET");break;case Method.DELETE:connection.setRequestMethod("DELETE");break;case Method.POST:connection.setRequestMethod("POST");addBodyIfExists(connection, request);break;case Method.PUT:connection.setRequestMethod("PUT");addBodyIfExists(connection, request);break;case Method.HEAD:connection.setRequestMethod("HEAD");break;case Method.OPTIONS:connection.setRequestMethod("OPTIONS");break;case Method.TRACE:connection.setRequestMethod("TRACE");break;case Method.PATCH:connection.setRequestMethod("PATCH");addBodyIfExists(connection, request);break;default:throw new IllegalStateException("Unknown method type.");}}private static void addBodyIfExists(HttpURLConnection connection, Request<?> request)throws IOException, AuthFailureError {byte[] body = request.getBody();if (body != null) {connection.setDoOutput(true);connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType());DataOutputStream out = new DataOutputStream(connection.getOutputStream());out.write(body);out.close();}}}