Skip to content

Instantly share code, notes, and snippets.

@ErikHellman
Created April 8, 2019 07:23
Show Gist options
  • Save ErikHellman/b158b41cb4bfb2b53345c686bd6950df to your computer and use it in GitHub Desktop.
Save ErikHellman/b158b41cb4bfb2b53345c686bd6950df to your computer and use it in GitHub Desktop.
A simple "web server" for handling intercepted WebView requests on Android and deliver local content.
@file:Suppress("unused")
package se.hellsoft.webviewserver
import android.content.res.AssetManager
import android.net.Uri
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
/**
* This class will act as a web server for intercepted request from a `WebView`. Simply implement your own
* `WebViewClientCompat` and assign it as the `webViewClient` of your `WebView` and call `handleWebViewRequest()` from
* `WebViewClient.shouldInterceptRequest()`.
*
* Just add your own `RequestHandler` for every response you want to return. Two ready-made implementations
* of `RequestHandler` are provided: `FileRequestHandler` and `AssetRequestHandler`. See respective class for details.
*
* If no matching `RequestHandler` is found, a default 404 response will be returned.
*/
class WebViewServer {
private val requestHandlers = mutableListOf<RequestHandler>()
fun addRequestHandler(requestHandler: RequestHandler) {
requestHandlers += requestHandler
}
fun handleWebViewRequest(webResourceRequest: WebResourceRequest): WebResourceResponse {
val request = Request.fromWebResourceRequest(webResourceRequest)
for (requestHandler in requestHandlers) {
if (requestHandler.shouldHandleRequest(request)) {
return requestHandler.handleRequest(request).toWebResourceResponse()
}
}
return NOT_FOUND_RESPONSE.toWebResourceResponse()
}
companion object {
val NOT_FOUND_RESPONSE = Response(
404,
"Not found",
emptyMap(),
"text/plain",
"UTF-8",
ByteArrayInputStream(byteArrayOf())
)
}
}
data class Request(val url: String, val method: String, val headers: Map<String, String>) {
companion object {
fun fromWebResourceRequest(webResourceRequest: WebResourceRequest): Request {
return Request(
webResourceRequest.url.toString(),
webResourceRequest.method,
webResourceRequest.requestHeaders
)
}
}
}
data class Response(
val statusCode: Int,
val reasonPhrase: String,
val responseHeaders: Map<String, String>,
val mimeType: String,
val encoding: String,
val data: InputStream
) {
fun toWebResourceResponse(): WebResourceResponse {
return WebResourceResponse(mimeType, encoding, statusCode, reasonPhrase, responseHeaders, data)
}
}
interface RequestHandler {
fun shouldHandleRequest(request: Request): Boolean
fun handleRequest(request: Request): Response
}
/**
* Returns a response containing the content of the specified `File`. Note that the content of the file
* is cached in memory, so beware of loading too large files using this class. For very large files,
* please provide your own `RequestHandler` that streams the content to the `WebView` instead.
*/
open class FileRequestHandler(
file: File,
private val requestPath: String,
private val mimeType: String,
private val encoding: String
) : RequestHandler {
private val bytes: ByteArray
init {
bytes = FileInputStream(file).use {
return@use it.readBytes()
}
}
override fun shouldHandleRequest(request: Request): Boolean {
val uri = Uri.parse(requestPath)
return uri.path == requestPath
}
override fun handleRequest(request: Request): Response {
return Response(200, "OK", emptyMap(), mimeType, encoding, ByteArrayInputStream(bytes))
}
}
/**
* Returns a response containing the content of the specified Android asset. Note that the content of the file
* is cached in memory, so beware of loading too large files using this class. For very large files,
* please provide your own `RequestHandler` that streams the content to the `WebView` instead.
*/
open class AssetRequestHandler(
assetManager: AssetManager,
assetPath: String,
private val requestPath: String,
private val mimeType: String,
private val encoding: String
) : RequestHandler {
private val bytes: ByteArray
init {
val inputStream = assetManager.open(assetPath)
bytes = inputStream.use {
return@use it.readBytes()
}
}
override fun shouldHandleRequest(request: Request): Boolean {
val uri = Uri.parse(requestPath)
return uri.path == requestPath
}
override fun handleRequest(request: Request): Response {
return Response(200, "OK", emptyMap(), mimeType, encoding, ByteArrayInputStream(bytes))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment