[components] Issue https://github.com/mozilla-mobile/android-components/issues/1325: (lib-fetch-httpurlconnection): A concept-fetch implementation using HttpURLConnection.
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
# [Android Components](../../../README.md) > Libraries > Fetch-HttpURLConnection
|
||||
|
||||
A [concept-fetch](../../concept/fetch/README.md) implementation using [HttpURLConnection](https://developer.android.com/reference/java/net/HttpURLConnection.html).
|
||||
|
||||
This implementation of `concept-fetch` uses [HttpURLConnection](https://developer.android.com/reference/java/net/HttpURLConnection.html) from the standard library of the Android System. Therefore this component has no third-party dependencies and is smaller than other implementations. It's intended use is for apps that have strict APK size constraints.
|
||||
|
||||
## Usage
|
||||
|
||||
### Setting up the dependency
|
||||
|
||||
Use Gradle to download the library from [maven.mozilla.org](https://maven.mozilla.org/) ([Setup repository](../../../README.md#maven-repository)):
|
||||
|
||||
```Groovy
|
||||
implementation "org.mozilla.components:lib-fetch-httpurlconnection:{latest-version}"
|
||||
```
|
||||
|
||||
### Performing requests
|
||||
|
||||
See the [concept-fetch documentation](../../concept/fetch/README.md) for generic examples of using the API of components implementing `concept-fetch`.
|
||||
|
||||
## License
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/
|
||||
@@ -0,0 +1,38 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion Config.compileSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion Config.minSdkVersion
|
||||
targetSdkVersion Config.targetSdkVersion
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation Deps.kotlin_stdlib
|
||||
implementation Deps.kotlin_coroutines
|
||||
|
||||
implementation project(':concept-fetch')
|
||||
|
||||
testImplementation Deps.testing_junit
|
||||
testImplementation Deps.testing_robolectric
|
||||
testImplementation Deps.testing_mockito
|
||||
|
||||
testImplementation project(':tooling-fetch-tests')
|
||||
}
|
||||
|
||||
apply from: '../../../publish.gradle'
|
||||
ext.configurePublish(Config.componentsGroupId, archivesBaseName, gradle.componentDescriptions[archivesBaseName])
|
||||
21
mobile/android/android-components/components/lib/fetch-httpurlconnection/proguard-rules.pro
vendored
Normal file
21
mobile/android/android-components/components/lib/fetch-httpurlconnection/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,5 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="mozilla.components.lib.fetch.httpurlconnection" />
|
||||
@@ -0,0 +1,152 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.components.lib.fetch.httpurlconnection
|
||||
|
||||
import mozilla.components.concept.fetch.Client
|
||||
import mozilla.components.concept.fetch.Headers
|
||||
import mozilla.components.concept.fetch.MutableHeaders
|
||||
import mozilla.components.concept.fetch.Request
|
||||
import mozilla.components.concept.fetch.Response
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.util.zip.GZIPInputStream
|
||||
|
||||
/**
|
||||
* [HttpURLConnection] implementation of [Client].
|
||||
*/
|
||||
class HttpURLConnectionClient : Client() {
|
||||
@Throws(IOException::class)
|
||||
override fun fetch(request: Request): Response {
|
||||
val connection = (URL(request.url).openConnection() as HttpURLConnection)
|
||||
|
||||
connection.setupWith(request)
|
||||
connection.addHeadersFrom(request, defaultHeaders = defaultHeaders)
|
||||
connection.addBodyFrom(request)
|
||||
|
||||
return connection.toResponse()
|
||||
}
|
||||
}
|
||||
|
||||
private fun HttpURLConnection.addBodyFrom(request: Request) {
|
||||
if (request.body == null) {
|
||||
return
|
||||
}
|
||||
|
||||
request.body?.let { body ->
|
||||
doOutput = true
|
||||
|
||||
body.useStream { inStream ->
|
||||
outputStream.use { outStream ->
|
||||
inStream
|
||||
.buffered()
|
||||
.copyTo(outStream)
|
||||
outStream.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun HttpURLConnection.setupWith(request: Request) {
|
||||
requestMethod = request.method.name
|
||||
instanceFollowRedirects = request.redirect == Request.Redirect.FOLLOW
|
||||
|
||||
request.connectTimeout?.let { (timeout, unit) ->
|
||||
connectTimeout = unit.toMillis(timeout).toInt()
|
||||
}
|
||||
|
||||
request.readTimeout?.let { (timeout, unit) ->
|
||||
readTimeout = unit.toMillis(timeout).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
private fun HttpURLConnection.addHeadersFrom(request: Request, defaultHeaders: Headers) {
|
||||
defaultHeaders.filter { header ->
|
||||
request.headers?.contains(header.name) != true
|
||||
}.forEach { header ->
|
||||
setRequestProperty(header.name, header.value)
|
||||
}
|
||||
|
||||
request.headers?.forEach { header ->
|
||||
addRequestProperty(header.name, header.value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun HttpURLConnection.toResponse(): Response {
|
||||
return Response(
|
||||
url.toString(),
|
||||
responseCode,
|
||||
translateHeaders(this),
|
||||
createBody(this)
|
||||
)
|
||||
}
|
||||
|
||||
private fun translateHeaders(connection: HttpURLConnection): Headers {
|
||||
val headers = MutableHeaders()
|
||||
|
||||
var index = 0
|
||||
|
||||
while (connection.getHeaderField(index) != null) {
|
||||
val name = connection.getHeaderFieldKey(index)
|
||||
if (name == null) {
|
||||
index++
|
||||
continue
|
||||
}
|
||||
|
||||
val value = connection.getHeaderField(index)
|
||||
|
||||
headers.append(name, value)
|
||||
|
||||
index++
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
private fun createBody(connection: HttpURLConnection): Response.Body {
|
||||
val gzipped = connection.contentEncoding == "gzip"
|
||||
|
||||
withFileNotFoundExceptionIgnored {
|
||||
return HttpUrlConnectionBody(
|
||||
connection,
|
||||
connection.inputStream,
|
||||
gzipped
|
||||
)
|
||||
}
|
||||
|
||||
withFileNotFoundExceptionIgnored {
|
||||
return HttpUrlConnectionBody(
|
||||
connection,
|
||||
connection.errorStream,
|
||||
gzipped
|
||||
)
|
||||
}
|
||||
|
||||
return EmptyBody()
|
||||
}
|
||||
|
||||
private class EmptyBody : Response.Body("".byteInputStream())
|
||||
|
||||
private class HttpUrlConnectionBody(
|
||||
private val connection: HttpURLConnection,
|
||||
stream: InputStream,
|
||||
gzipped: Boolean
|
||||
) : Response.Body(if (gzipped) GZIPInputStream(stream) else stream) {
|
||||
override fun close() {
|
||||
super.close()
|
||||
|
||||
connection.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun withFileNotFoundExceptionIgnored(block: () -> Unit) {
|
||||
try {
|
||||
block()
|
||||
} catch (e: FileNotFoundException) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.components.lib.fetch.httpurlconnection
|
||||
|
||||
import mozilla.components.concept.fetch.Client
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class HttpUrlConnectionFetchTestCases : mozilla.components.tooling.fetch.tests.FetchTestCases() {
|
||||
override fun createNewClient(): Client = HttpURLConnectionClient()
|
||||
|
||||
// Inherits test methods from generic test suite base class
|
||||
|
||||
@Test
|
||||
fun `Client instance`() {
|
||||
// We need at least one test case defined here so that this is recognized as test class.
|
||||
assertTrue(createNewClient() is HttpURLConnectionClient)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user