将自定义标头添加到WebView资源请求-Android


95

我需要向来自WebView的每个请求添加自定义标头。我知道loadURL具有的参数extraHeaders,但这些参数仅应用于初始请求。所有后续请求均不包含标头。我已经看过所有的替代WebViewClient,但是没有什么允许向资源请求添加标头- onLoadResource(WebView view, String url)。任何帮助都会很棒。

谢谢,雷


2
@MediumOne:这不是一个错误,因为它是您认为缺少的功能。我不知道HTTP规范中的任何内容,表明后续的HTTP请求必须镜像先前的HTTP请求中的任意标头。
CommonsWare 2013年

1
@CommonsWare:“后续”一词在这里有误导性。当我在任何浏览器上键入“ facebook.com ”以加载facebook.com主页时,都有几种支持的“资源请求”来加载CSS,js和img文件。您可以使用F12功能(“网络”标签)在Chrome中进行检查。对于这些请求,webview不添加标题。我尝试使用addons.mozilla.org/en-us/firefox/addon/modify-headers插件向FireFox请求添加自定义标头。该插件能够向所有此类支持的“资源请求”添加标头。我认为WebView应该做同样的事情。
2013年

1
@MediumOne:“我认为WebView应该做同样的事情”-这是您认为缺少的功能。请注意,您必须借助插件才能使Firefox执行此操作。我并不是说您建议的功能不是一个好主意。我是说,将其描述为错误不太可能帮助您将这个提议的功能添加到Android。
CommonsWare 2013年

1
@CommonsWare:假设我正在使用WebView构建可配置为与自定义HTTP代理一起使用的浏览器。此代理使用自定义身份验证,对它的请求应具有自定义标头。现在,webview提供了一个用于设置自定义标头的API,但是在内部,它并未将标头设置为其生成的所有资源请求。也没有任何其他API可以为这些请求设置标头。因此,任何依赖于向WebView请求添加自定义标头的功能都会失败。
2013年

1
@CommonsWare-4年后,我将重新讨论此对话。我现在同意-这不应该是一个错误。HTTP规范中没有任何内容表明后续请求应发送相同的标头。:)
MediumOne

Answers:


80

尝试

loadUrl(String url, Map<String, String> extraHeaders)

要将标头添加到资源加载请求中,请定制WebViewClient并重写:

API 24+:
WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)
or
WebResourceResponse shouldInterceptRequest(WebView view, String url)

9
抱歉,但这不起作用。它也仅将标头应用于初始请求。标头未添加到资源请求中。还有其他想法吗?谢谢。

19
是的,像这样重写WebClient.shouldOverrideUrlLoading:public boolean shouldOverrideUrlLoading(WebView view,String url){view.loadUrl(url,extraHeaders); 返回true;}
2011年

5
@peceps-资源加载期间未调用回调“ shouldOverrideUrlLoading”。例如,当我们尝试时view.loadUrl("http://www.facebook.com", extraHeaders),有'http://static.fb.com/images/logo.png'从webiew发送的多个资源请求(例如etc)。对于这些请求,不添加额外的标题。并且在此类资源请求期间不调用shouldOverrideUrlLoading。调用了“ OnLoadResource”回调,但是目前无法设置标头。
MediumOne

2
@MediumOne,用于资源加载,请覆盖WebViewClient.shouldInterceptRequest(android.webkit.WebView view, java.lang.String url)Check API以获得更多信息。
yorkw

3
@yorkw:此方法确实捕获所有资源请求的URL。但是无法将标头添加到这些请求。我的目标是向所有请求添加自定义HTTP标头。如果可以使用该shouldInterceptRequest方法实现,请解释一下如何?
2013年

36

您将需要使用WebViewClient.shouldInterceptRequest拦截每个请求

每次拦截时,您都需要获取url,自己提出此请求,然后返回内容流:

WebViewClient wvc = new WebViewClient() {
    @Override
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {

        try {
            DefaultHttpClient client = new DefaultHttpClient();
            HttpGet httpGet = new HttpGet(url);
            httpGet.setHeader("MY-CUSTOM-HEADER", "header value");
            httpGet.setHeader(HttpHeaders.USER_AGENT, "custom user-agent");
            HttpResponse httpReponse = client.execute(httpGet);

            Header contentType = httpReponse.getEntity().getContentType();
            Header encoding = httpReponse.getEntity().getContentEncoding();
            InputStream responseInputStream = httpReponse.getEntity().getContent();

            String contentTypeValue = null;
            String encodingValue = null;
            if (contentType != null) {
                contentTypeValue = contentType.getValue();
            }
            if (encoding != null) {
                encodingValue = encoding.getValue();
            }
            return new WebResourceResponse(contentTypeValue, encodingValue, responseInputStream);
        } catch (ClientProtocolException e) {
            //return null to tell WebView we failed to fetch it WebView should try again.
            return null;
        } catch (IOException e) {
             //return null to tell WebView we failed to fetch it WebView should try again.
            return null;
        }
    }
}

Webview wv = new WebView(this);
wv.setWebViewClient(wvc);

如果您的最低API目标是21级,则可以使用新的shouldInterceptRequest,它为您提供其他请求信息(例如标头),而不仅仅是URL。


2
以防万一有人在使用此技巧时遇到相同的情况。(无论如何,这是一个好习惯。)这是给您的注释。由于可以包含可选参数(例如charset)的http content-type标头与MIME类型不完全兼容,因此需要WebResourceResponse构造函数的第一个参数,因此,无论如何,我们都应从content-type中提取MIME类型部分可以想到,例如RegExp,使其在大多数情况下都能正常工作。
James Chen

2
不推荐使用此事件。. public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)而是在此处
Hirdesh Vishwdewa

3
@HirdeshVishwdewa-看最后一句话。
Martin Konecny

2
您可以通过以Web视图和修改后的请求作为参数返回超类的shouldInterceptRequest方法的结果来跳过自己的加载。在您根据URL触发,在重新加载时未更改它并会陷入无限循环的情况下,这特别方便。不过,非常感谢您提出了新的请求示例。Java处理事情的方式对我而言是非常违反直觉的。
Erik Reppen '16

4
HttpClient的不能与compileSdk 23及以上使用,
陶Kozmér

30

也许我的回复很晚了,但它涵盖了API 之上之下的内容 21水平。

要添加标题,我们应该拦截每个请求创建一个新请求使用所需的标题。

因此,我们都需要重写在两种情况下调用的shouldInterceptRequest方法:1.对于API,直到级别21;2.适用于21级以上的API

    webView.setWebViewClient(new WebViewClient() {

        // Handle API until level 21
        @SuppressWarnings("deprecation")
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {

            return getNewResponse(url);
        }

        // Handle API 21+
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {

            String url = request.getUrl().toString();

            return getNewResponse(url);
        }

        private WebResourceResponse getNewResponse(String url) {

            try {
                OkHttpClient httpClient = new OkHttpClient();

                Request request = new Request.Builder()
                        .url(url.trim())
                        .addHeader("Authorization", "YOU_AUTH_KEY") // Example header
                        .addHeader("api-key", "YOUR_API_KEY") // Example header
                        .build();

                Response response = httpClient.newCall(request).execute();

                return new WebResourceResponse(
                        null,
                        response.header("content-encoding", "utf-8"),
                        response.body().byteStream()
                );

            } catch (Exception e) {
                return null;
            }

        }
   });

如果应处理响应类型,则可以更改

        return new WebResourceResponse(
                null, // <- Change here
                response.header("content-encoding", "utf-8"),
                response.body().byteStream()
        );

        return new WebResourceResponse(
                getMimeType(url), // <- Change here
                response.header("content-encoding", "utf-8"),
                response.body().byteStream()
        );

并添加方法

        private String getMimeType(String url) {
            String type = null;
            String extension = MimeTypeMap.getFileExtensionFromUrl(url);

            if (extension != null) {

                switch (extension) {
                    case "js":
                        return "text/javascript";
                    case "woff":
                        return "application/font-woff";
                    case "woff2":
                        return "application/font-woff2";
                    case "ttf":
                        return "application/x-font-ttf";
                    case "eot":
                        return "application/vnd.ms-fontobject";
                    case "svg":
                        return "image/svg+xml";
                }

                type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
            }

            return type;
        }

1
很抱歉回答这个旧帖子,但是使用此代码,我的应用尝试下载文件(但失败)而不是加载页面。
Giacomo M

非常感谢你!
AlexS

21

如前所述,您可以执行以下操作:

 WebView  host = (WebView)this.findViewById(R.id.webView);
 String url = "<yoururladdress>";

 Map <String, String> extraHeaders = new HashMap<String, String>();
 extraHeaders.put("Authorization","Bearer"); 
 host.loadUrl(url,extraHeaders);

我对此进行了测试,然后使用MVC控制器对其进行了扩展,该控制器扩展了Authorize Attribute以检查标头和标头是否存在。


我将不得不再次解决该问题,因为它是在编写和发布时与Kit-Kat一起工作的。我还没有尝试过Lolly Pop。
leeroya '02

不能在果冻豆或棉花糖上为我工作...不会更改页眉中的任何内容
Erik Verboom

6
这不符合OP的要求。他想将标题添加到Webview发出的所有请求中。这只会向第一个请求添加自定义标头
-NinjaCoder

这不是OP的要求
Akshay

我知道这不能满足OP的需求,但这恰恰是我想要的,即向WebViewIntent URL添加一个额外的标头。谢谢,不管!
约书亚·品特

9

这对我有用:

  1. 首先,您需要创建方法,该方法将返回要添加到请求的标头:

    private Map<String, String> getCustomHeaders()
    {
        Map<String, String> headers = new HashMap<>();
        headers.put("YOURHEADER", "VALUE");
        return headers;
    }
  2. 其次,您需要创建WebViewClient:

    private WebViewClient getWebViewClient()
    {
    
        return new WebViewClient()
        {
    
        @Override
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
        {
            view.loadUrl(request.getUrl().toString(), getCustomHeaders());
            return true;
        }
    
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url)
        {
            view.loadUrl(url, getCustomHeaders());
            return true;
        }
    };
    }
  3. 将WebViewClient添加到您的WebView:

    webView.setWebViewClient(getWebViewClient());

希望这可以帮助。


1
看起来不错,但这会添加标题,还是替换标题?
Ivo Renkema '17

@IvoRenkema loadUrl(String url, Map<String, String> additionalHttpHeaders) 意味着添加附加标题
AbhinayMe

4

您应该能够通过跳过loadUrl并使用Java的HttpURLConnection编写自己的loadPage来控制所有标头。然后使用网络视图的loadData显示响应。

无法访问Google提供的标题。它们在WebView源代码的JNI调用中。


1
您是否对答案中的所说内容有任何参考。如果您将实现参考与答案一起使用,则对其他人会有所帮助。
Hirdesh Vishwdewa,2015年

1

这是使用HttpUrlConnection的实现:

class CustomWebviewClient : WebViewClient() {
    private val charsetPattern = Pattern.compile(".*?charset=(.*?)(;.*)?$")

    override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
        try {
            val connection: HttpURLConnection = URL(request.url.toString()).openConnection() as HttpURLConnection
            connection.requestMethod = request.method
            for ((key, value) in request.requestHeaders) {
                connection.addRequestProperty(key, value)
            }

            connection.addRequestProperty("custom header key", "custom header value")

            var contentType: String? = connection.contentType
            var charset: String? = null
            if (contentType != null) {
                // some content types may include charset => strip; e. g. "application/json; charset=utf-8"
                val contentTypeTokenizer = StringTokenizer(contentType, ";")
                val tokenizedContentType = contentTypeTokenizer.nextToken()

                var capturedCharset: String? = connection.contentEncoding
                if (capturedCharset == null) {
                    val charsetMatcher = charsetPattern.matcher(contentType)
                    if (charsetMatcher.find() && charsetMatcher.groupCount() > 0) {
                        capturedCharset = charsetMatcher.group(1)
                    }
                }
                if (capturedCharset != null && !capturedCharset.isEmpty()) {
                    charset = capturedCharset
                }

                contentType = tokenizedContentType
            }

            val status = connection.responseCode
            var inputStream = if (status == HttpURLConnection.HTTP_OK) {
                connection.inputStream
            } else {
                // error stream can sometimes be null even if status is different from HTTP_OK
                // (e. g. in case of 404)
                connection.errorStream ?: connection.inputStream
            }
            val headers = connection.headerFields
            val contentEncodings = headers.get("Content-Encoding")
            if (contentEncodings != null) {
                for (header in contentEncodings) {
                    if (header.equals("gzip", true)) {
                        inputStream = GZIPInputStream(inputStream)
                        break
                    }
                }
            }
            return WebResourceResponse(contentType, charset, status, connection.responseMessage, convertConnectionResponseToSingleValueMap(connection.headerFields), inputStream)
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return super.shouldInterceptRequest(view, request)
    }

    private fun convertConnectionResponseToSingleValueMap(headerFields: Map<String, List<String>>): Map<String, String> {
        val headers = HashMap<String, String>()
        for ((key, value) in headerFields) {
            when {
                value.size == 1 -> headers[key] = value[0]
                value.isEmpty() -> headers[key] = ""
                else -> {
                    val builder = StringBuilder(value[0])
                    val separator = "; "
                    for (i in 1 until value.size) {
                        builder.append(separator)
                        builder.append(value[i])
                    }
                    headers[key] = builder.toString()
                }
            }
        }
        return headers
    }
}

请注意,这不适用于POST请求,因为WebResourceRequest不提供POST数据。有一个请求数据-WebViewClient库,它使用JavaScript注入替代方法来拦截POST数据。


0

这对我有用。像下面这样创建WebViewClient并将webclient设置为您的webview。我必须使用webview.loadDataWithBaseURL,因为我的网址(在我的内容中)没有基本网址,而只有相对网址。仅当使用loadDataWithBaseURL设置了baseurl时,您才能正确获取该url。

public WebViewClient getWebViewClientWithCustomHeader(){
    return new WebViewClient() {
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
            try {
                OkHttpClient httpClient = new OkHttpClient();
                com.squareup.okhttp.Request request = new com.squareup.okhttp.Request.Builder()
                        .url(url.trim())
                        .addHeader("<your-custom-header-name>", "<your-custom-header-value>")
                        .build();
                com.squareup.okhttp.Response response = httpClient.newCall(request).execute();

                return new WebResourceResponse(
                        response.header("content-type", response.body().contentType().type()), // You can set something other as default content-type
                        response.header("content-encoding", "utf-8"),  // Again, you can set another encoding as default
                        response.body().byteStream()
                );
            } catch (ClientProtocolException e) {
                //return null to tell WebView we failed to fetch it WebView should try again.
                return null;
            } catch (IOException e) {
                //return null to tell WebView we failed to fetch it WebView should try again.
                return null;
            }
        }
    };

}

对我来说,它的工作原理是:.post(reqbody)其中RequestBody reqbody = RequestBody.create(null,“”);
Karoly

-2

您可以使用此:

@Override

 public boolean shouldOverrideUrlLoading(WebView view, String url) {

                // Here put your code
                Map<String, String> map = new HashMap<String, String>();
                map.put("Content-Type","application/json");
                view.loadUrl(url, map);
                return false;

            }

2
这只是不断重新加载网址,不是吗?
Onheiron

-3

我遇到了同样的问题并解决了。

如前所述,您需要创建自定义WebViewClient并覆盖shouldInterceptRequest方法。

WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)

该方法应在返回“空” WebResourceResponse的同时发出webView.loadUrl。

像这样:

@Override
public boolean shouldInterceptRequest(WebView view, WebResourceRequest request) {

    // Check for "recursive request" (are yor header set?)
    if (request.getRequestHeaders().containsKey("Your Header"))
        return null;

    // Add here your headers (could be good to import original request header here!!!)
    Map<String, String> customHeaders = new HashMap<String, String>();
    customHeaders.put("Your Header","Your Header Value");
    view.loadUrl(url, customHeaders);

    return new WebResourceResponse("", "", null);
}

通过这种方法调用view.loadUrl似乎使应用程序崩溃
willcwf '18

@willcwf你有一个崩溃的例子吗?
弗朗切斯科

@Francesco我的应用程序崩溃,也
贾科莫中号

所有人都对此表示不满,说它崩溃没有帮助。请更具体地写一些错误信息。
弗朗切斯科

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.