11ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath/*
21ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath * Copyright (C) 2010 The Android Open Source Project
31ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath *
41ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath * Licensed under the Apache License, Version 2.0 (the "License");
51ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath * you may not use this file except in compliance with the License.
61ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath * You may obtain a copy of the License at
71ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath *
81ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath *      http://www.apache.org/licenses/LICENSE-2.0
91ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath *
101ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath * Unless required by applicable law or agreed to in writing, software
111ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath * distributed under the License is distributed on an "AS IS" BASIS,
121ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath * See the License for the specific language governing permissions and
141ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath * limitations under the License.
151ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath */
161ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath
171ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamathpackage com.android.quicksearchbox.util;
181ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath
191ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamathimport android.os.Build;
201ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamathimport android.util.Log;
211ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath
221ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamathimport java.io.BufferedReader;
231ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamathimport java.io.IOException;
241ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamathimport java.io.InputStreamReader;
251ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamathimport java.io.OutputStreamWriter;
261ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamathimport java.net.HttpURLConnection;
271ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamathimport java.net.URL;
281ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamathimport java.util.HashMap;
291ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamathimport java.util.Map;
301ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath
311ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath/**
321ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath * Simple HTTP client API.
331ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath */
341ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamathpublic class JavaNetHttpHelper implements HttpHelper {
351ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    private static final String TAG = "QSB.JavaNetHttpHelper";
361ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    private static final boolean DBG = false;
371ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath
381ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    private static final int BUFFER_SIZE = 1024 * 4;
391ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    private static final String USER_AGENT_HEADER = "User-Agent";
401ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    private static final String DEFAULT_CHARSET = "UTF-8";
411ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath
421ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    private int mConnectTimeout;
431ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    private int mReadTimeout;
441ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    private final String mUserAgent;
451ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    private final HttpHelper.UrlRewriter mRewriter;
461ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath
471ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    /**
481ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     * Creates a new HTTP helper.
491ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     *
501ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     * @param rewriter URI rewriter
511ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     * @param userAgent User agent string, e.g. "MyApp/1.0".
521ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     */
531ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    public JavaNetHttpHelper(UrlRewriter rewriter, String userAgent) {
541ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        mUserAgent = userAgent + " (" + Build.DEVICE + " " + Build.ID + ")";
551ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        mRewriter = rewriter;
561ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    }
571ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath
581ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    /**
591ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     * Executes a GET request and returns the response content.
601ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     *
611ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     * @param request Request.
621ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     * @return The response content. This is the empty string if the response
631ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     *         contained no content.
641ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     * @throws IOException If an IO error occurs.
651ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     * @throws HttpException If the response has a status code other than 200.
661ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     */
671ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    public String get(GetRequest request) throws IOException, HttpException {
681ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        return get(request.getUrl(), request.getHeaders());
691ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    }
701ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath
711ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    /**
721ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     * Executes a GET request and returns the response content.
731ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     *
741ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     * @param url Request URI.
751ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     * @param requestHeaders Request headers.
761ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     * @return The response content. This is the empty string if the response
771ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     *         contained no content.
781ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     * @throws IOException If an IO error occurs.
791ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     * @throws HttpException If the response has a status code other than 200.
801ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath     */
811ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    public String get(String url, Map<String,String> requestHeaders)
821ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            throws IOException, HttpException {
831ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        HttpURLConnection c = null;
841ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        try {
851ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            c = createConnection(url, requestHeaders);
861ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            c.setRequestMethod("GET");
871ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            c.connect();
881ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            return getResponseFrom(c);
891ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        } finally {
901ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            if (c != null) {
911ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath                c.disconnect();
921ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            }
931ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        }
941ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    }
951ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath
961ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    @Override
971ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    public String post(PostRequest request) throws IOException, HttpException {
981ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        return post(request.getUrl(), request.getHeaders(), request.getContent());
991ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    }
1001ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath
1011ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    public String post(String url, Map<String,String> requestHeaders, String content)
1021ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            throws IOException, HttpException {
1031ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        HttpURLConnection c = null;
1041ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        try {
1051ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            if (requestHeaders == null) {
1061ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath                requestHeaders = new HashMap<String, String>();
1071ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            }
1081ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            requestHeaders.put("Content-Length",
1091ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath                    Integer.toString(content == null ? 0 : content.length()));
1101ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            c = createConnection(url, requestHeaders);
1111ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            c.setDoOutput(content != null);
1121ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            c.setRequestMethod("POST");
1131ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            c.connect();
1141ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            if (content != null) {
1151ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath                OutputStreamWriter writer = new OutputStreamWriter(c.getOutputStream());
1161ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath                writer.write(content);
1171ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath                writer.close();
1181ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            }
1191ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            return getResponseFrom(c);
1201ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        } finally {
1211ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            if (c != null) {
1221ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath                c.disconnect();
1231ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            }
1241ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        }
1251ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    }
1261ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath
1271ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    private HttpURLConnection createConnection(String url, Map<String, String> headers)
1281ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            throws IOException, HttpException {
1291ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        URL u = new URL(mRewriter.rewrite(url));
1301ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        if (DBG) Log.d(TAG, "URL=" + url + " rewritten='" + u + "'");
1311ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        HttpURLConnection c = (HttpURLConnection) u.openConnection();
1321ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        if (headers != null) {
1331ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            for (Map.Entry<String,String> e : headers.entrySet()) {
1341ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath                String name = e.getKey();
1351ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath                String value = e.getValue();
1361ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath                if (DBG) Log.d(TAG, "  " + name + ": " + value);
1371ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath                c.addRequestProperty(name, value);
1381ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            }
1391ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        }
1401ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        c.addRequestProperty(USER_AGENT_HEADER, mUserAgent);
1411ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        if (mConnectTimeout != 0) {
1421ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            c.setConnectTimeout(mConnectTimeout);
1431ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        }
1441ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        if (mReadTimeout != 0) {
1451ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            c.setReadTimeout(mReadTimeout);
1461ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        }
1471ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        return c;
1481ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    }
1491ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath
1501ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    private String getResponseFrom(HttpURLConnection c) throws IOException, HttpException {
1511ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        if (c.getResponseCode() != HttpURLConnection.HTTP_OK) {
1521ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            throw new HttpException(c.getResponseCode(), c.getResponseMessage());
1531ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        }
1541ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        if (DBG) {
1551ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            Log.d(TAG, "Content-Type: " + c.getContentType() + " (assuming " +
1561ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath                    DEFAULT_CHARSET + ")");
1571ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        }
1581ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        BufferedReader reader = new BufferedReader(
1591ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath                new InputStreamReader(c.getInputStream(), DEFAULT_CHARSET));
1601ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        StringBuilder string = new StringBuilder();
1611ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        char[] chars = new char[BUFFER_SIZE];
1621ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        int bytes;
1631ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        while ((bytes = reader.read(chars)) != -1) {
1641ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath            string.append(chars, 0, bytes);
1651ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        }
1661ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        return string.toString();
1671ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    }
1681ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath
1691ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    public void setConnectTimeout(int timeoutMillis) {
1701ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        mConnectTimeout = timeoutMillis;
1711ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    }
1721ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath
1731ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    public void setReadTimeout(int timeoutMillis) {
1741ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath        mReadTimeout = timeoutMillis;
1751ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath    }
1761ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath
177ef6dbc776667219ac89bd5f80c181f65260457acNarayan Kamath    /**
178ef6dbc776667219ac89bd5f80c181f65260457acNarayan Kamath     * A Url rewriter that does nothing, i.e., returns the
179ef6dbc776667219ac89bd5f80c181f65260457acNarayan Kamath     * url that is passed to it.
180ef6dbc776667219ac89bd5f80c181f65260457acNarayan Kamath     */
181ef6dbc776667219ac89bd5f80c181f65260457acNarayan Kamath    public static class PassThroughRewriter implements UrlRewriter {
182ef6dbc776667219ac89bd5f80c181f65260457acNarayan Kamath        @Override
183ef6dbc776667219ac89bd5f80c181f65260457acNarayan Kamath        public String rewrite(String url) {
184ef6dbc776667219ac89bd5f80c181f65260457acNarayan Kamath            return url;
185ef6dbc776667219ac89bd5f80c181f65260457acNarayan Kamath        }
186ef6dbc776667219ac89bd5f80c181f65260457acNarayan Kamath    }
1871ea50e57e0bb5bf1184e3c1e4fbda27c3d8e44a6Narayan Kamath}
188