blob: 06a45d182d9ca9c3d436c0970430a46ac3fede93 [file] [log] [blame]
/*
* 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, String>?): 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<String, String>?,
content: String?
): String? {
var mRequestHeaders: MutableMap<String, String>? = 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<String, String>?): 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
}
}