在WebView中拦截POST请求

40

我正在开发一个Android应用程序,使用白名单过滤请求并使用自定义SSLSocketFactory。为此,我开发了一个自定义的WebViewClient并覆盖了shouldInterceptRequest方法。我可以过滤和使用我的SocketFactory处理GET请求,但我无法拦截POST请求。

那么,在WebView中是否有一种方式可以拦截POST请求?

以下是shouldInterceptRequest方法的代码:

public final WebResourceResponse shouldInterceptRequest(WebView view, String urlStr) {
    URI uri = URI.create(urlStr);
    String scheme = uri.getScheme();
    // If scheme not http(s), let the default webview manage it
    if(!"http".equals(scheme) && !"https".equals(scheme)) {
        return null;
    }
    URL url = uri.toURL();

    if(doCancelRequest(url)) {
        // Empty response
        Log.d(TAG, "URL filtered: " + url);
        return new WebResourceResponse("text/plain", "UTF-8", new EmptyInputStream());

    } else {
        Log.d(TAG, "URL: " + url);

        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestProperty("User-Agent", mSettings.getUserAgentString());

        // Configure connections
        configureConnection(conn);

        String mimeType = conn.getContentType();
        String encoding = conn.getContentEncoding();

        if(mimeType != null && mimeType.contains(CONTENT_TYPE_SPLIT)) {
            String[] split = mimeType.split(CONTENT_TYPE_SPLIT);
            mimeType = split[0];

            Matcher matcher = CONTENT_TYPE_PATTERN.matcher(split[1]);
            if(matcher.find()) {
                encoding = matcher.group(1);
            }
        }

        InputStream is = conn.getInputStream();
        return new WebResourceResponse(mimeType, encoding, is);
    }
}

1
你是否遇到了错误,还是它根本就不起作用?你应该发一些相关的代码,例如你重写的 shouldInterceptRequest 方法。 - Esoteric Screen Name
没有错误。我在shouldInterceptRequest方法的开头记录了URL,但我只看到GET请求。其他请求似乎是由WebView底层处理的。 - Fab_34
7个回答

9

我几天前也遇到了同样的问题。

因此,我建立了一个解决方案的库:

https://github.com/KonstantinSchubert/request_data_webviewclient

它是一个WebViewClient,具有包含XMLHttpRequest请求的POST/PUT/...负载的自定义WebResourceRequest。

但它仅适用于这些请求类型,而不适用于表单和其他请求来源。

该方法的基本原理是通过将脚本注入HTML中来拦截XMLHttpRequest调用。它记录post/put/...内容并将其发送到android.webkit.JavascriptInterface。在那里,请求被保存直到Android调用shouldInterceptRequest方法...


2
非常聪明的解决方法,谢谢。将其转换为适用于Xamarin.Android,并在遇到此问题时挽救了局面。 - Emmanuel Vazquez
2
很棒的想法,我冒昧地创建了您的库的更新和扩展版本:https://github.com/acsbendi/Android-Request-Inspector-WebView。我添加了对缺失部分的支持,例如表单和获取请求,以及记录确切的标头。 - user3738870
@EmmanuelVazquez:你能分享一下Xamarin.Android版本的代码吗?这会救我的一天。:) - Sudhanshu

3
我创建了一个旨在捕获所有来自Android WebViews发送的HTTP请求数据的
使用此库,您可以轻松实现发送POST或任何其他请求。下面是适用于该库的调整后的代码:
    @Nullable
    @Override
    public final WebResourceResponse shouldInterceptRequest(WebView view, WebViewRequest request) {
        URI uri = URI.create(request.getUrl());
        String scheme = uri.getScheme();
        // If scheme not http(s), let the default webview manage it
        if(!"http".equals(scheme) && !"https".equals(scheme)) {
            return null;
        }
        URL url = uri.toURL();

        if(doCancelRequest(url)) {
            // Empty response
            Log.d(TAG, "URL filtered: " + url);
            return new WebResourceResponse("text/plain", "UTF-8", new EmptyInputStream());

        } else {
            Log.d(TAG, "URL: " + url);

            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // Set request method
            conn.setRequestMethod(request.getMethod());
            // Set request headers
            for (Map.Entry<String, String> header : request.getHeaders().entrySet()) {
                conn.setRequestProperty(header.getKey(), header.getValue());
            }

            // Set request body, if it's present
            if (!request.getBody().isEmpty()) {
                OutputStream os = conn.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
                osw.write(request.getBody());
                osw.flush();
                osw.close();
                os.close();  //don't forget to close the OutputStream
            }
            conn.connect();

            String mimeType = conn.getContentType();
            String encoding = conn.getContentEncoding();

            if(mimeType != null && mimeType.contains(CONTENT_TYPE_SPLIT)) {
                String[] split = mimeType.split(CONTENT_TYPE_SPLIT);
                mimeType = split[0];

                Matcher matcher = CONTENT_TYPE_PATTERN.matcher(split[1]);
                if(matcher.find()) {
                    encoding = matcher.group(1);
                }
            }

            InputStream is = conn.getInputStream();
            return new WebResourceResponse(mimeType, encoding, is);
        }
    }

这个方法是从哪里来的?request.getBody() - Tony
1
它来自我的库,你可以在这里找到它:https://github.com/acsbendi/Android-Request-Inspector-WebView - user3738870
2
不错的代码库!继续努力改进它。 - Tony

0

我发现最简单的方法是使用JQuery Ajax事件处理程序和Javascript接口。

您需要设置Javascript接口,但一旦设置完成,只需将其与您感兴趣的处理程序的签名匹配即可。

要获取有效载荷,只需找到有效载荷变量名称并将其放入接口中即可。

以下是Javascript代码:

 $( document ).ajaxSend(function( event, request, settings ) {
      var eventJSON= JSON.stringify(event);
      var requestJSON = JSON.stringify(request);
      var settingsJSON = JSON.stringify(settings);
      var payloadJSON = JSON.stringify(payload);

      myJSInterface.passData(eventJSON,requestJSON,settingsJSON,payloadJSON);       
 });

这是 Kotlin 类

 class yourJSInterface() {
      lateinit var event: String
      lateinit var request: String
      lateinit var settings: String
      lateinit var payload: String
 
      @JavascriptInterface
      fun passData(eventJSON:String, responseJSON:String, settingsJSON:String,payloadJSON:String){
           event = eventJSON
           response= responseJSON
           settings= settingsJSON
           payload= payloadJSON
      }
 }

在WebViewClient的onPageStarted覆盖中进行注册。
 webView.addJavascriptInterface(yourJSInterface,"myJSInterface")

最后,在OnPageFinished重写中将JS注入到WebView中

 webView.evaluateJavascript("javascript:" + getString(R.string.js_from_above),null)

我在onPageStarted中注册了接口,因为否则,在onPageFinished中的javascript文件将无法识别您的接口。


0

我在上面的线程中有一个答案 http://code.google.com/p/android/issues/detail?id=9122

请查看评论#31

我发现我的解决方案有一些注意事项:

  1. 将依赖于xmlhttprequest原型放入其中,不同的Webkit实现方式不同。
  2. 在URL中发送POST请求的安全问题。但我想你可以通过某种加密机制来解决这个问题。
  3. 如果您要发布大量数据,则某些浏览器的URL长度可能会出现问题

除此之外,我发现this github repo似乎是用另一种hacky的方式解决了这个问题。我研究了代码,但没有时间实施和测试它。但值得一试。


-1

有一个更简单的解决方案:在ajax调用中通过GET发送参数,并在shouldInterceptRequest中将它们转换为POST。


-1

在提交前,您可以获取输入值
https://github.com/henrychuangtw/WebView-Javascript-Inject

步骤1:创建一个被JavaScript调用的类

class MyJavaScriptInterface
{
    @JavascriptInterface
    public void processHTML(String html)
    {
        //called by javascript
    }
}


步骤2:为JavaScript注册接口

webview1.getSettings().setJavaScriptEnabled(true);
webview1.addJavascriptInterface(new MyJavaScriptInterface(), "MYOBJECT");


步骤三:向页面注入 JavaScript

webview1.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);

        StringBuilder sb = new StringBuilder();
        sb.append("document.getElementsByTagName('form')[0].onsubmit = function () {");
        sb.append("var objPWD, objAccount;var str = '';");
        sb.append("var inputs = document.getElementsByTagName('input');");
        sb.append("for (var i = 0; i < inputs.length; i++) {");
        sb.append("if (inputs[i].type.toLowerCase() === 'password') {objPWD = inputs[i];}");
        sb.append("else if (inputs[i].name.toLowerCase() === 'email') {objAccount = inputs[i];}");
        sb.append("}");
        sb.append("if (objAccount != null) {str += objAccount.value;}");
        sb.append("if (objPWD != null) { str += ' , ' + objPWD.value;}");
        sb.append("window.MYOBJECT.processHTML(str);");
        sb.append("return true;");
        sb.append("};");

        view.loadUrl("javascript:" + sb.toString());
    }

});

-4

8
是的,我在Android Bugtracker中找到了问题。但在我的情况下,我无法将POST更改为GET请求,因为我不知道要连接到哪个服务器。 例如,我无法连接到GMail,因为在身份验证期间存在POST请求。 - Fab_34
1
看看这个库:https://github.com/KonstantinSchubert/request_data_webviewclient 我在遇到类似问题时编写了它。 - Konstantin Schubert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接