1 /* 2 * Copyright (C) 2020 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.statementservice.network.retriever 18 19 import android.net.Network 20 import android.net.TrafficStats 21 import android.util.Log 22 import com.android.statementservice.utils.Result 23 import kotlinx.coroutines.Dispatchers 24 import kotlinx.coroutines.async 25 import kotlinx.coroutines.withContext 26 import java.net.HttpURLConnection 27 import java.net.URL 28 import java.nio.charset.Charset 29 import javax.net.ssl.HttpsURLConnection 30 31 class UrlFetcher { 32 33 companion object { 34 private val TAG = UrlFetcher::class.java.simpleName 35 } 36 37 suspend fun fetch( 38 url: URL, 39 connectionTimeoutMillis: Int, 40 fileSizeLimit: Long, 41 network: Network? = null 42 ) = withContext(Dispatchers.IO) { 43 TrafficStats.setThreadStatsTag(Thread.currentThread().id.toInt()) 44 @Suppress("BlockingMethodInNonBlockingContext") 45 val connection = 46 ((network?.openConnection(url) ?: url.openConnection()) as HttpsURLConnection) 47 try { 48 connection.apply { 49 connectTimeout = connectionTimeoutMillis 50 readTimeout = connectionTimeoutMillis 51 useCaches = true 52 instanceFollowRedirects = false 53 addRequestProperty("Cache-Control", "max-stale=60") 54 } 55 val responseCode = connection.responseCode 56 when { 57 responseCode != HttpURLConnection.HTTP_OK -> { 58 Log.w(TAG, "The responses code is not 200 but $responseCode") 59 Result.Success(Response(responseCode)) 60 } 61 connection.contentLength > fileSizeLimit -> { 62 Log.w(TAG, "The content size of the url is larger than $fileSizeLimit") 63 Result.Success(Response(responseCode)) 64 } 65 else -> { 66 val content = async { 67 connection.inputStream 68 .bufferedReader(Charset.forName("UTF-8")) 69 .readText() 70 } 71 72 Result.Success(Response(responseCode, content.await())) 73 } 74 } 75 } catch (ignored: Throwable) { 76 Result.Failure(ignored) 77 } finally { 78 connection.disconnect() 79 } 80 } 81 82 data class Response( 83 val responseCode: Int, 84 val content: String? = null 85 ) 86 } 87