AndroidのWebViewでHTTPレスポンスのハンドリングやタイムアウトを独自に実装したいときに、shouldInterceptRequestをオーバーライドして、独自にHTTP通信を実装することがありますよね。
サンプルなので大雑把な実装ですが、例えば以下のような感じに実装したとしましょう。
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()
}
でも、これを使うときは要注意。
例えば、以下のような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はHTML解析機能を持っていますから、上記のHTML読み込み時に、doPost関数が呼び出され、
タグに定義されたhttp://192.168.100.2:3000/hoge
に対して、自動的にリクエストを送信します。
このとき、shouldInterceptRequestでリソースの読み込みをWebViewに任せていれば(shouldInterceptRequestをオーバーライドしていなければ)、
<input>タグのhidden属性の値を自動的にPOSTしてくれますが、開発者が独自にOkHTTPなどでリクエストを実装する際は、HTMLの内容を解析して自前で付与しないといけません。(デフォルトではhidden属性の値は付与されない)
以下のようなサーバを立てて、ログを見てみましょう。
{-# 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"
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")]
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:
[]
HTMLのhidden属性に定義されていた内容がPOSTされていませんね。これだとサーバから意図したレスポンスが返却されなくなってしまうでしょう。
その他、User-Agentなどのヘッダー情報も代わってしまいますから、shouldInterceptRequestをオーバライドするときは、この点も注意。
hidden属性の値をPOSTするためには、HTMLの解析が必要になってきます。
ここら辺は既に情報が色々出回っています。
HTMLの内容の取得方法は以下とか参考になりました。
https://stackoverflow.com/questions/8200945/how-to-get-html-content-from-a-webview
OkHttpで、application/x-www-form-urlencodedのPOSTパラメータを設定する方法は以下とかが
参考になりました。
https://stackoverflow.com/questions/35756264/how-do-i-post-data-using-okhttp-library-with-content-type-x-www-form-urlencoded
以前の記事でも同じようなこと言いましたが、WebViewの目的はHTMLのレンダリングだから、WebAPI呼び出しに使うようなものではないですね。