1/*
2 * Copyright (C) 2010 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.quicksearchbox.util;
18
19import android.os.Build;
20import android.util.Log;
21
22import java.io.BufferedReader;
23import java.io.IOException;
24import java.io.InputStreamReader;
25import java.io.OutputStreamWriter;
26import java.net.HttpURLConnection;
27import java.net.URL;
28import java.util.HashMap;
29import java.util.Map;
30
31/**
32 * Simple HTTP client API.
33 */
34public class JavaNetHttpHelper implements HttpHelper {
35    private static final String TAG = "QSB.JavaNetHttpHelper";
36    private static final boolean DBG = false;
37
38    private static final int BUFFER_SIZE = 1024 * 4;
39    private static final String USER_AGENT_HEADER = "User-Agent";
40    private static final String DEFAULT_CHARSET = "UTF-8";
41
42    private int mConnectTimeout;
43    private int mReadTimeout;
44    private final String mUserAgent;
45    private final HttpHelper.UrlRewriter mRewriter;
46
47    /**
48     * Creates a new HTTP helper.
49     *
50     * @param rewriter URI rewriter
51     * @param userAgent User agent string, e.g. "MyApp/1.0".
52     */
53    public JavaNetHttpHelper(UrlRewriter rewriter, String userAgent) {
54        mUserAgent = userAgent + " (" + Build.DEVICE + " " + Build.ID + ")";
55        mRewriter = rewriter;
56    }
57
58    /**
59     * Executes a GET request and returns the response content.
60     *
61     * @param request Request.
62     * @return The response content. This is the empty string if the response
63     *         contained no content.
64     * @throws IOException If an IO error occurs.
65     * @throws HttpException If the response has a status code other than 200.
66     */
67    public String get(GetRequest request) throws IOException, HttpException {
68        return get(request.getUrl(), request.getHeaders());
69    }
70
71    /**
72     * Executes a GET request and returns the response content.
73     *
74     * @param url Request URI.
75     * @param requestHeaders Request headers.
76     * @return The response content. This is the empty string if the response
77     *         contained no content.
78     * @throws IOException If an IO error occurs.
79     * @throws HttpException If the response has a status code other than 200.
80     */
81    public String get(String url, Map<String,String> requestHeaders)
82            throws IOException, HttpException {
83        HttpURLConnection c = null;
84        try {
85            c = createConnection(url, requestHeaders);
86            c.setRequestMethod("GET");
87            c.connect();
88            return getResponseFrom(c);
89        } finally {
90            if (c != null) {
91                c.disconnect();
92            }
93        }
94    }
95
96    @Override
97    public String post(PostRequest request) throws IOException, HttpException {
98        return post(request.getUrl(), request.getHeaders(), request.getContent());
99    }
100
101    public String post(String url, Map<String,String> requestHeaders, String content)
102            throws IOException, HttpException {
103        HttpURLConnection c = null;
104        try {
105            if (requestHeaders == null) {
106                requestHeaders = new HashMap<String, String>();
107            }
108            requestHeaders.put("Content-Length",
109                    Integer.toString(content == null ? 0 : content.length()));
110            c = createConnection(url, requestHeaders);
111            c.setDoOutput(content != null);
112            c.setRequestMethod("POST");
113            c.connect();
114            if (content != null) {
115                OutputStreamWriter writer = new OutputStreamWriter(c.getOutputStream());
116                writer.write(content);
117                writer.close();
118            }
119            return getResponseFrom(c);
120        } finally {
121            if (c != null) {
122                c.disconnect();
123            }
124        }
125    }
126
127    private HttpURLConnection createConnection(String url, Map<String, String> headers)
128            throws IOException, HttpException {
129        URL u = new URL(mRewriter.rewrite(url));
130        if (DBG) Log.d(TAG, "URL=" + url + " rewritten='" + u + "'");
131        HttpURLConnection c = (HttpURLConnection) u.openConnection();
132        if (headers != null) {
133            for (Map.Entry<String,String> e : headers.entrySet()) {
134                String name = e.getKey();
135                String value = e.getValue();
136                if (DBG) Log.d(TAG, "  " + name + ": " + value);
137                c.addRequestProperty(name, value);
138            }
139        }
140        c.addRequestProperty(USER_AGENT_HEADER, mUserAgent);
141        if (mConnectTimeout != 0) {
142            c.setConnectTimeout(mConnectTimeout);
143        }
144        if (mReadTimeout != 0) {
145            c.setReadTimeout(mReadTimeout);
146        }
147        return c;
148    }
149
150    private String getResponseFrom(HttpURLConnection c) throws IOException, HttpException {
151        if (c.getResponseCode() != HttpURLConnection.HTTP_OK) {
152            throw new HttpException(c.getResponseCode(), c.getResponseMessage());
153        }
154        if (DBG) {
155            Log.d(TAG, "Content-Type: " + c.getContentType() + " (assuming " +
156                    DEFAULT_CHARSET + ")");
157        }
158        BufferedReader reader = new BufferedReader(
159                new InputStreamReader(c.getInputStream(), DEFAULT_CHARSET));
160        StringBuilder string = new StringBuilder();
161        char[] chars = new char[BUFFER_SIZE];
162        int bytes;
163        while ((bytes = reader.read(chars)) != -1) {
164            string.append(chars, 0, bytes);
165        }
166        return string.toString();
167    }
168
169    public void setConnectTimeout(int timeoutMillis) {
170        mConnectTimeout = timeoutMillis;
171    }
172
173    public void setReadTimeout(int timeoutMillis) {
174        mReadTimeout = timeoutMillis;
175    }
176
177    /**
178     * A Url rewriter that does nothing, i.e., returns the
179     * url that is passed to it.
180     */
181    public static class PassThroughRewriter implements UrlRewriter {
182        @Override
183        public String rewrite(String url) {
184            return url;
185        }
186    }
187}
188