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
17package com.android.volley.toolbox;
18
19import com.android.volley.AuthFailureError;
20import com.android.volley.Request;
21import com.android.volley.Request.Method;
22
23import org.apache.http.HttpEntity;
24import org.apache.http.HttpResponse;
25import org.apache.http.NameValuePair;
26import org.apache.http.client.HttpClient;
27import org.apache.http.client.methods.HttpDelete;
28import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
29import org.apache.http.client.methods.HttpGet;
30import org.apache.http.client.methods.HttpHead;
31import org.apache.http.client.methods.HttpOptions;
32import org.apache.http.client.methods.HttpPost;
33import org.apache.http.client.methods.HttpPut;
34import org.apache.http.client.methods.HttpTrace;
35import org.apache.http.client.methods.HttpUriRequest;
36import org.apache.http.entity.ByteArrayEntity;
37import org.apache.http.message.BasicNameValuePair;
38import org.apache.http.params.HttpConnectionParams;
39import org.apache.http.params.HttpParams;
40
41import java.io.IOException;
42import java.net.URI;
43import java.util.ArrayList;
44import java.util.List;
45import java.util.Map;
46
47/**
48 * An HttpStack that performs request over an {@link HttpClient}.
49 */
50public class HttpClientStack implements HttpStack {
51    protected final HttpClient mClient;
52
53    private final static String HEADER_CONTENT_TYPE = "Content-Type";
54
55    public HttpClientStack(HttpClient client) {
56        mClient = client;
57    }
58
59    private static void addHeaders(HttpUriRequest httpRequest, Map<String, String> headers) {
60        for (String key : headers.keySet()) {
61            httpRequest.setHeader(key, headers.get(key));
62        }
63    }
64
65    @SuppressWarnings("unused")
66    private static List<NameValuePair> getPostParameterPairs(Map<String, String> postParams) {
67        List<NameValuePair> result = new ArrayList<NameValuePair>(postParams.size());
68        for (String key : postParams.keySet()) {
69            result.add(new BasicNameValuePair(key, postParams.get(key)));
70        }
71        return result;
72    }
73
74    @Override
75    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
76            throws IOException, AuthFailureError {
77        HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
78        addHeaders(httpRequest, additionalHeaders);
79        addHeaders(httpRequest, request.getHeaders());
80        onPrepareRequest(httpRequest);
81        HttpParams httpParams = httpRequest.getParams();
82        int timeoutMs = request.getTimeoutMs();
83        // TODO: Reevaluate this connection timeout based on more wide-scale
84        // data collection and possibly different for wifi vs. 3G.
85        HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
86        HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
87        return mClient.execute(httpRequest);
88    }
89
90    /**
91     * Creates the appropriate subclass of HttpUriRequest for passed in request.
92     */
93    @SuppressWarnings("deprecation")
94    /* protected */ static HttpUriRequest createHttpRequest(Request<?> request,
95            Map<String, String> additionalHeaders) throws AuthFailureError {
96        switch (request.getMethod()) {
97            case Method.DEPRECATED_GET_OR_POST: {
98                // This is the deprecated way that needs to be handled for backwards compatibility.
99                // If the request's post body is null, then the assumption is that the request is
100                // GET.  Otherwise, it is assumed that the request is a POST.
101                byte[] postBody = request.getPostBody();
102                if (postBody != null) {
103                    HttpPost postRequest = new HttpPost(request.getUrl());
104                    postRequest.addHeader(HEADER_CONTENT_TYPE, request.getPostBodyContentType());
105                    HttpEntity entity;
106                    entity = new ByteArrayEntity(postBody);
107                    postRequest.setEntity(entity);
108                    return postRequest;
109                } else {
110                    return new HttpGet(request.getUrl());
111                }
112            }
113            case Method.GET:
114                return new HttpGet(request.getUrl());
115            case Method.DELETE:
116                return new HttpDelete(request.getUrl());
117            case Method.POST: {
118                HttpPost postRequest = new HttpPost(request.getUrl());
119                postRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
120                setEntityIfNonEmptyBody(postRequest, request);
121                return postRequest;
122            }
123            case Method.PUT: {
124                HttpPut putRequest = new HttpPut(request.getUrl());
125                putRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
126                setEntityIfNonEmptyBody(putRequest, request);
127                return putRequest;
128            }
129            case Method.HEAD:
130                return new HttpHead(request.getUrl());
131            case Method.OPTIONS:
132                return new HttpOptions(request.getUrl());
133            case Method.TRACE:
134                return new HttpTrace(request.getUrl());
135            case Method.PATCH: {
136                HttpPatch patchRequest = new HttpPatch(request.getUrl());
137                patchRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
138                setEntityIfNonEmptyBody(patchRequest, request);
139                return patchRequest;
140            }
141            default:
142                throw new IllegalStateException("Unknown request method.");
143        }
144    }
145
146    private static void setEntityIfNonEmptyBody(HttpEntityEnclosingRequestBase httpRequest,
147            Request<?> request) throws AuthFailureError {
148        byte[] body = request.getBody();
149        if (body != null) {
150            HttpEntity entity = new ByteArrayEntity(body);
151            httpRequest.setEntity(entity);
152        }
153    }
154
155    /**
156     * Called before the request is executed using the underlying HttpClient.
157     *
158     * <p>Overwrite in subclasses to augment the request.</p>
159     */
160    protected void onPrepareRequest(HttpUriRequest request) throws IOException {
161        // Nothing.
162    }
163
164    /**
165     * The HttpPatch class does not exist in the Android framework, so this has been defined here.
166     */
167    public static final class HttpPatch extends HttpEntityEnclosingRequestBase {
168
169        public final static String METHOD_NAME = "PATCH";
170
171        public HttpPatch() {
172            super();
173        }
174
175        public HttpPatch(final URI uri) {
176            super();
177            setURI(uri);
178        }
179
180        /**
181         * @throws IllegalArgumentException if the uri is invalid.
182         */
183        public HttpPatch(final String uri) {
184            super();
185            setURI(URI.create(uri));
186        }
187
188        @Override
189        public String getMethod() {
190            return METHOD_NAME;
191        }
192
193    }
194}
195