3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

AndroidのWebView#shouldInterceptRequestでリクエストをinterceptするときに忘れがちなこと

Posted at

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"
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")]
shouldInterceptを上記実装でオーバーライドした場合
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呼び出しに使うようなものではないですね。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?