/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.quicksearchbox.util import android.os.Build import android.util.Log import java.io.BufferedReader import java.io.IOException import java.io.InputStreamReader import java.io.OutputStreamWriter import java.net.HttpURLConnection import java.net.URL /** Simple HTTP client API. */ class JavaNetHttpHelper(rewriter: HttpHelper.UrlRewriter, userAgent: String) : HttpHelper { private var mConnectTimeout = 0 private var mReadTimeout = 0 private val mUserAgent: String private val mRewriter: HttpHelper.UrlRewriter /** * Executes a GET request and returns the response content. * * @param request Request. * @return The response content. This is the empty string if the response contained no content. * @throws IOException If an IO error occurs. * @throws HttpException If the response has a status code other than 200. */ @Throws(IOException::class, HttpHelper.HttpException::class) override operator fun get(request: HttpHelper.GetRequest?): String? { return get(request?.url, request?.headers) } /** * Executes a GET request and returns the response content. * * @param url Request URI. * @param requestHeaders Request headers. * @return The response content. This is the empty string if the response contained no content. * @throws IOException If an IO error occurs. * @throws HttpException If the response has a status code other than 200. */ @Throws(IOException::class, HttpHelper.HttpException::class) override operator fun get(url: String?, requestHeaders: MutableMap?): String? { var c: HttpURLConnection? = null return try { c = createConnection(url!!, requestHeaders) c.setRequestMethod("GET") c.connect() getResponseFrom(c) } finally { if (c != null) { c.disconnect() } } } @Override @Throws(IOException::class, HttpHelper.HttpException::class) override fun post(request: HttpHelper.PostRequest?): String? { return post(request?.url, request?.headers, request?.content) } @Throws(IOException::class, HttpHelper.HttpException::class) override fun post( url: String?, requestHeaders: MutableMap?, content: String? ): String? { var mRequestHeaders: MutableMap? = requestHeaders var c: HttpURLConnection? = null return try { if (mRequestHeaders == null) { mRequestHeaders = mutableMapOf() } mRequestHeaders.put("Content-Length", Integer.toString(content?.length ?: 0)) c = createConnection(url!!, mRequestHeaders) c.setDoOutput(content != null) c.setRequestMethod("POST") c.connect() if (content != null) { val writer = OutputStreamWriter(c.getOutputStream()) writer.write(content) writer.close() } getResponseFrom(c) } finally { if (c != null) { c.disconnect() } } } @Throws(IOException::class, HttpHelper.HttpException::class) private fun createConnection(url: String, headers: Map?): HttpURLConnection { val u = URL(mRewriter.rewrite(url)) if (DBG) Log.d(TAG, "URL=$url rewritten='$u'") val c: HttpURLConnection = u.openConnection() as HttpURLConnection if (headers != null) { for (e in headers.entries) { val name: String = e.key val value: String = e.value if (DBG) Log.d(TAG, " $name: $value") c.addRequestProperty(name, value) } } c.addRequestProperty(USER_AGENT_HEADER, mUserAgent) if (mConnectTimeout != 0) { c.setConnectTimeout(mConnectTimeout) } if (mReadTimeout != 0) { c.setReadTimeout(mReadTimeout) } return c } @Throws(IOException::class, HttpHelper.HttpException::class) private fun getResponseFrom(c: HttpURLConnection): String { if (c.getResponseCode() != HttpURLConnection.HTTP_OK) { throw HttpHelper.HttpException(c.getResponseCode(), c.getResponseMessage()) } if (DBG) { Log.d( TAG, "Content-Type: " + c.getContentType().toString() + " (assuming " + DEFAULT_CHARSET + ")" ) } val reader = BufferedReader(InputStreamReader(c.getInputStream(), DEFAULT_CHARSET)) val string: StringBuilder = StringBuilder() val chars = CharArray(BUFFER_SIZE) var bytes: Int while (reader.read(chars).also { bytes = it } != -1) { string.append(chars, 0, bytes) } return string.toString() } override fun setConnectTimeout(timeoutMillis: Int) { mConnectTimeout = timeoutMillis } override fun setReadTimeout(timeoutMillis: Int) { mReadTimeout = timeoutMillis } /** A Url rewriter that does nothing, i.e., returns the url that is passed to it. */ class PassThroughRewriter : HttpHelper.UrlRewriter { @Override override fun rewrite(url: String): String { return url } } companion object { private const val TAG = "QSB.JavaNetHttpHelper" private const val DBG = false private const val BUFFER_SIZE = 1024 * 4 private const val USER_AGENT_HEADER = "User-Agent" private const val DEFAULT_CHARSET = "UTF-8" } /** * Creates a new HTTP helper. * * @param rewriter URI rewriter * @param userAgent User agent string, e.g. "MyApp/1.0". */ init { mUserAgent = userAgent + " (" + Build.DEVICE + " " + Build.ID + ")" mRewriter = rewriter } }