Home | History | Annotate | Download | only in toolbox
      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 
     22 import org.apache.http.Header;
     23 import org.apache.http.HttpEntity;
     24 import org.apache.http.HttpResponse;
     25 import org.apache.http.ProtocolVersion;
     26 import org.apache.http.StatusLine;
     27 import org.apache.http.entity.BasicHttpEntity;
     28 import org.apache.http.message.BasicHeader;
     29 import org.apache.http.message.BasicHttpResponse;
     30 import org.apache.http.message.BasicStatusLine;
     31 
     32 import java.io.DataOutputStream;
     33 import java.io.IOException;
     34 import java.io.InputStream;
     35 import java.net.HttpURLConnection;
     36 import java.net.URL;
     37 import java.util.HashMap;
     38 import java.util.List;
     39 import java.util.Map;
     40 import java.util.Map.Entry;
     41 
     42 /**
     43  * An {@link HttpStack} based on {@link HttpURLConnection}.
     44  */
     45 public class HurlStack implements HttpStack {
     46 
     47     /**
     48      * An interface for transforming URLs before use.
     49      */
     50     public interface UrlRewriter {
     51         /**
     52          * Returns a URL to use instead of the provided one, or null to indicate
     53          * this URL should not be used at all.
     54          */
     55         public String rewriteUrl(String originalUrl);
     56     }
     57 
     58     private final UrlRewriter mUrlRewriter;
     59 
     60     public HurlStack() {
     61         this(null);
     62     }
     63 
     64     /**
     65      * @param urlRewriter Rewriter to use for request URLs
     66      */
     67     public HurlStack(UrlRewriter urlRewriter) {
     68         mUrlRewriter = urlRewriter;
     69     }
     70 
     71     @Override
     72     public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
     73             throws IOException, AuthFailureError {
     74         String url = request.getUrl();
     75         HashMap<String, String> map = new HashMap<String, String>();
     76         map.putAll(request.getHeaders());
     77         map.putAll(additionalHeaders);
     78         if (mUrlRewriter != null) {
     79             String rewritten = mUrlRewriter.rewriteUrl(url);
     80             if (rewritten == null) {
     81                 throw new IOException("URL blocked by rewriter: " + url);
     82             }
     83             url = rewritten;
     84         }
     85         URL parsedUrl = new URL(url);
     86         HttpURLConnection connection = openConnection(parsedUrl, request);
     87         for (String headerName : map.keySet()) {
     88             connection.addRequestProperty(headerName, map.get(headerName));
     89         }
     90         handlePost(connection, request);
     91         // Initialize HttpResponse with data from the HttpURLConnection.
     92         ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
     93         int responseCode = connection.getResponseCode();
     94         if (responseCode == -1) {
     95             // -1 is returned by getResponseCode() if the response code could not be retrieved.
     96             // Signal to the caller that something was wrong with the connection.
     97             throw new IOException("Could not retrieve response code from HttpUrlConnection.");
     98         }
     99         StatusLine responseStatus = new BasicStatusLine(protocolVersion,
    100                 connection.getResponseCode(), connection.getResponseMessage());
    101         BasicHttpResponse response = new BasicHttpResponse(responseStatus);
    102         response.setEntity(entityFromConnection(connection));
    103         for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
    104             if (header.getKey() != null) {
    105                 Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
    106                 response.addHeader(h);
    107             }
    108         }
    109         return response;
    110     }
    111 
    112     /**
    113      * Initializes an {@link HttpEntity} from the given {@link HttpURLConnection}.
    114      * @param connection
    115      * @return an HttpEntity populated with data from <code>connection</code>.
    116      */
    117     private static HttpEntity entityFromConnection(HttpURLConnection connection) {
    118         BasicHttpEntity entity = new BasicHttpEntity();
    119         InputStream inputStream;
    120         try {
    121             inputStream = connection.getInputStream();
    122         } catch (IOException ioe) {
    123             inputStream = connection.getErrorStream();
    124         }
    125         entity.setContent(inputStream);
    126         entity.setContentLength(connection.getContentLength());
    127         entity.setContentEncoding(connection.getContentEncoding());
    128         entity.setContentType(connection.getContentType());
    129         return entity;
    130     }
    131 
    132     /**
    133      * Opens an {@link HttpURLConnection} with parameters.
    134      * @param url
    135      * @return an open connection
    136      * @throws IOException
    137      */
    138     private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
    139         HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    140 
    141         int timeoutMs = request.getTimeoutMs();
    142         connection.setConnectTimeout(timeoutMs);
    143         connection.setReadTimeout(timeoutMs);
    144         connection.setUseCaches(false);
    145         connection.setDoInput(true);
    146         return connection;
    147     }
    148 
    149     private void handlePost(HttpURLConnection connection, Request<?> request)
    150             throws IOException, AuthFailureError {
    151         byte[] postBody = request.getPostBody();
    152         if (postBody == null) return;
    153         // Prepare output. There is no need to set Content-Length explicitly,
    154         // since this is handled by HttpURLConnection using the size of the prepared output stream.
    155         connection.setDoOutput(true);
    156         connection.setRequestMethod("POST");
    157         connection.addRequestProperty("Content-Type", request.getPostBodyContentType());
    158         DataOutputStream out = new DataOutputStream(connection.getOutputStream());
    159         out.write(postBody);
    160         out.close();
    161     }
    162 }
    163