[Java] Things to forget when intercepting a request with Android’s WebView#shouldInterceptRequest

2 minute read

When you want to implement HTTP response handling and timeout in Android WebView, you may override shouldInterceptRequest and implement HTTP communication independently.

Since it is a sample, it is a rough implementation, but let’s say you implement it as follows.

override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
    val latch = CountDownLatch(1)
    var res: InputStream? = null

    val call = createOkHttpClient().newCall(Request.Builder().url(request?.url.toString()).method("POST", RequestBody.create(null, "hoge")).build())
    call.enqueue(object: Callback {
        override fun onFailure(call: Call, e: IOException) {
            latch.countDown()
        }

        override fun onResponse(call: Call, response: Response) {
            res = response.body()?.byteStream()
            latch.countDown()
        }
    })

    latch.await()
    return WebResourceResponse("text/html", "UTF-8",res)
}

private val cookieStore = HashMap<String, MutableList<Cookie>>()

fun createOkHttpClient(): OkHttpClient {
    return OkHttpClient.Builder()
        .cookieJar(object: CookieJar {
            override fun saveFromResponse(url: HttpUrl, cookies: MutableList<Cookie>) {
                cookieStore[url.host()] = cookies
        }

            override fun loadForRequest(url: HttpUrl): MutableList<Cookie> {
                val cookies = cookieStore[url.host()]
                return cookies ?: ArrayList()
            }
        })
        .build()
}

But be careful when using this. For example, let’s assume you want to get the following HTML.

<html>
<body>
<script type="text/javascript">
  function doPost() {
    document.TestForm.submit();
  }
</script>

<h1>Test</h1>

<form id="TestForm" name="TestForm" action="http://192.168.100.2:3000/hoge" method="post">
  <input type="hidden" name="hoge" value="hogeVal"/>
  <input type="hidden" name="fuga" value="fugaVal"/>
  <input type="submit" value="submit">
</form>
<script type="text/javascript">
  doPost();
</script>
</body>
</html>

WebView has an HTML parsing function, so when the above HTML is read, the doPost function is called and the http://192.168.100.2:3000/hoge defined in the <form> tag is And send the request automatically.

At this time, if you let WebView to load the resource with shouldInterceptRequest (unless you should override shouldInterceptRequest), The value of hidden attribute of <input> tag will be automatically POSTed, but when the developer implements the request by OkHTTP etc., he must analyze the HTML content and add it himself. not. (The value of hidden attribute is not added by default)

Let’s set up the following server and look at the log.

{-# LANGUAGE OverloadedStrings #-}
module Main where

import Web.Scotty
import Control.Monad.IO.Class (liftIO)

main :: IO ()
main = scotty 3000 $ do
  post "/test.html" $ file "./static/test.html" >> setHeader "Content-Type" "text/html"
  post "/hoge" $ do
    (liftIO .putStrLn $ ("Access to /hoge. Headers: "::String)) >> headers >>= liftIO .print
    (liftIO .putStrLn $ ("Params: "::String)) >> params >>= liftIO .print
    text "hoge success"

When not overriding

shouldInterceptRequest


Access to /hoge. Headers:
[("Host","192.168.100.151:3000"),("Connection","keep-alive"),("Content-Length","25"),("Cache-Control","max-age =0"),("Origin","http://192.168.100.151:3000"),("Upgrade-Insecure-Requests","1"),("Content-Type","application/x-www -form-urlencoded"),("User-Agent","Mozilla/5.0 (Linux; Android 9; Android SDK built for x86 Build/PSR1.180720.012; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome /66.0.3359.158 Mobile Safari/537.36"),("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 "),("Referer","http://192.168.100.151:3000/test.html"),("Accept-Encoding","gzip, deflate"),("Accept-Language","ja-JP ,en-US;q=0.9"),("X-Requested-With","com.badlogic.masaki.webviewjsinterfacesample")]
Params:
[("hoge","hogeVal"),("fuga","fugaVal")]

When

shouldIntercept is overridden in the above implementation


Access to /hoge. Headers:
[("Content-Length","4"),("Host","192.168.100.151:3000"),("Connection","Keep-Alive"),("Accept-Encoding","gzip") ,("User-Agent","okhttp/3.10.0")]
Params:
[]

The content defined in the hidden attribute of HTML is not POSTed. If this happens, the intended response will not be returned from the server. In addition, header information such as User-Agent will be replaced, so be careful when overriding shouldInterceptRequest.

In order to POST the value of hidden attribute, HTML parsing becomes necessary. A lot of information is already available around here.

How to get the contents of HTML was as follows. https://stackoverflow.com/questions/8200945/how-to-get-html-content-from-a-webview

With OkHttp, the method of setting the POST parameter of application/x-www-form-urlencoded is as follows. It was helpful. https://stackoverflow.com/questions/35756264/how-do-i-post-data-using-okhttp-library-with-content-type-x-www-form-urlencoded

Previous article said the same thing, but the purpose of WebView is to render HTML, so it’s not something to use for WebAPI calls. ..