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