1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.volley.toolbox; 18 19 import com.android.volley.AuthFailureError; 20 import com.android.volley.Request; 21 import com.android.volley.Request.Method; 22 23 import org.apache.http.Header; 24 import org.apache.http.HttpEntity; 25 import org.apache.http.HttpResponse; 26 import org.apache.http.ProtocolVersion; 27 import org.apache.http.StatusLine; 28 import org.apache.http.entity.BasicHttpEntity; 29 import org.apache.http.message.BasicHeader; 30 import org.apache.http.message.BasicHttpResponse; 31 import org.apache.http.message.BasicStatusLine; 32 33 import java.io.DataOutputStream; 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.net.HttpURLConnection; 37 import java.net.URL; 38 import java.util.HashMap; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.Map.Entry; 42 43 import javax.net.ssl.HttpsURLConnection; 44 import javax.net.ssl.SSLSocketFactory; 45 46 /** 47 * An {@link HttpStack} based on {@link HttpURLConnection}. 48 */ 49 public class HurlStack implements HttpStack { 50 51 private static final String HEADER_CONTENT_TYPE = "Content-Type"; 52 53 /** 54 * An interface for transforming URLs before use. 55 */ 56 public interface UrlRewriter { 57 /** 58 * Returns a URL to use instead of the provided one, or null to indicate 59 * this URL should not be used at all. 60 */ 61 public String rewriteUrl(String originalUrl); 62 } 63 64 private final UrlRewriter mUrlRewriter; 65 private final SSLSocketFactory mSslSocketFactory; 66 67 public HurlStack() { 68 this(null); 69 } 70 71 /** 72 * @param urlRewriter Rewriter to use for request URLs 73 */ 74 public HurlStack(UrlRewriter urlRewriter) { 75 this(urlRewriter, null); 76 } 77 78 /** 79 * @param urlRewriter Rewriter to use for request URLs 80 * @param sslSocketFactory SSL factory to use for HTTPS connections 81 */ 82 public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) { 83 mUrlRewriter = urlRewriter; 84 mSslSocketFactory = sslSocketFactory; 85 } 86 87 @Override 88 public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) 89 throws IOException, AuthFailureError { 90 String url = request.getUrl(); 91 HashMap<String, String> map = new HashMap<String, String>(); 92 map.putAll(request.getHeaders()); 93 map.putAll(additionalHeaders); 94 if (mUrlRewriter != null) { 95 String rewritten = mUrlRewriter.rewriteUrl(url); 96 if (rewritten == null) { 97 throw new IOException("URL blocked by rewriter: " + url); 98 } 99 url = rewritten; 100 } 101 URL parsedUrl = new URL(url); 102 HttpURLConnection connection = openConnection(parsedUrl, request); 103 for (String headerName : map.keySet()) { 104 connection.addRequestProperty(headerName, map.get(headerName)); 105 } 106 setConnectionParametersForRequest(connection, request); 107 // Initialize HttpResponse with data from the HttpURLConnection. 108 ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); 109 int responseCode = connection.getResponseCode(); 110 if (responseCode == -1) { 111 // -1 is returned by getResponseCode() if the response code could not be retrieved. 112 // Signal to the caller that something was wrong with the connection. 113 throw new IOException("Could not retrieve response code from HttpUrlConnection."); 114 } 115 StatusLine responseStatus = new BasicStatusLine(protocolVersion, 116 connection.getResponseCode(), connection.getResponseMessage()); 117 BasicHttpResponse response = new BasicHttpResponse(responseStatus); 118 response.setEntity(entityFromConnection(connection)); 119 for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) { 120 if (header.getKey() != null) { 121 Header h = new BasicHeader(header.getKey(), header.getValue().get(0)); 122 response.addHeader(h); 123 } 124 } 125 return response; 126 } 127 128 /** 129 * Initializes an {@link HttpEntity} from the given {@link HttpURLConnection}. 130 * @param connection 131 * @return an HttpEntity populated with data from <code>connection</code>. 132 */ 133 private static HttpEntity entityFromConnection(HttpURLConnection connection) { 134 BasicHttpEntity entity = new BasicHttpEntity(); 135 InputStream inputStream; 136 try { 137 inputStream = connection.getInputStream(); 138 } catch (IOException ioe) { 139 inputStream = connection.getErrorStream(); 140 } 141 entity.setContent(inputStream); 142 entity.setContentLength(connection.getContentLength()); 143 entity.setContentEncoding(connection.getContentEncoding()); 144 entity.setContentType(connection.getContentType()); 145 return entity; 146 } 147 148 /** 149 * Create an {@link HttpURLConnection} for the specified {@code url}. 150 */ 151 protected HttpURLConnection createConnection(URL url) throws IOException { 152 return (HttpURLConnection) url.openConnection(); 153 } 154 155 /** 156 * Opens an {@link HttpURLConnection} with parameters. 157 * @param url 158 * @return an open connection 159 * @throws IOException 160 */ 161 private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException { 162 HttpURLConnection connection = createConnection(url); 163 164 int timeoutMs = request.getTimeoutMs(); 165 connection.setConnectTimeout(timeoutMs); 166 connection.setReadTimeout(timeoutMs); 167 connection.setUseCaches(false); 168 connection.setDoInput(true); 169 170 // use caller-provided custom SslSocketFactory, if any, for HTTPS 171 if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) { 172 ((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory); 173 } 174 175 return connection; 176 } 177 178 @SuppressWarnings("deprecation") 179 /* package */ static void setConnectionParametersForRequest(HttpURLConnection connection, 180 Request<?> request) throws IOException, AuthFailureError { 181 switch (request.getMethod()) { 182 case Method.DEPRECATED_GET_OR_POST: 183 // This is the deprecated way that needs to be handled for backwards compatibility. 184 // If the request's post body is null, then the assumption is that the request is 185 // GET. Otherwise, it is assumed that the request is a POST. 186 byte[] postBody = request.getPostBody(); 187 if (postBody != null) { 188 // Prepare output. There is no need to set Content-Length explicitly, 189 // since this is handled by HttpURLConnection using the size of the prepared 190 // output stream. 191 connection.setDoOutput(true); 192 connection.setRequestMethod("POST"); 193 connection.addRequestProperty(HEADER_CONTENT_TYPE, 194 request.getPostBodyContentType()); 195 DataOutputStream out = new DataOutputStream(connection.getOutputStream()); 196 out.write(postBody); 197 out.close(); 198 } 199 break; 200 case Method.GET: 201 // Not necessary to set the request method because connection defaults to GET but 202 // being explicit here. 203 connection.setRequestMethod("GET"); 204 break; 205 case Method.DELETE: 206 connection.setRequestMethod("DELETE"); 207 break; 208 case Method.POST: 209 connection.setRequestMethod("POST"); 210 addBodyIfExists(connection, request); 211 break; 212 case Method.PUT: 213 connection.setRequestMethod("PUT"); 214 addBodyIfExists(connection, request); 215 break; 216 default: 217 throw new IllegalStateException("Unknown method type."); 218 } 219 } 220 221 private static void addBodyIfExists(HttpURLConnection connection, Request<?> request) 222 throws IOException, AuthFailureError { 223 byte[] body = request.getBody(); 224 if (body != null) { 225 connection.setDoOutput(true); 226 connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType()); 227 DataOutputStream out = new DataOutputStream(connection.getOutputStream()); 228 out.write(body); 229 out.close(); 230 } 231 } 232 } 233