134

I need to add custom headers to EVERY request coming from the WebView. I know loadURL has the parameter for extraHeaders, but those are only applied to the initial request. All subsequent requests do not contain the headers. I have looked at all overrides in WebViewClient, but nothing allows for adding headers to resource requests - onLoadResource(WebView view, String url). Any help would be wonderful.

Thanks, Ray

8
  • 3
    @MediumOne: This isn't a bug as much it is a feature that you consider to be missing. I am not aware of anything in the HTTP specification that says subsequent HTTP requests must mirror arbitrary headers from previous HTTP requests. Commented Jan 31, 2013 at 18:59
  • 1
    @CommonsWare: The word "subsequent" is misleading here. When I type "facebook.com" on any browser to load the facebook.com homepage, there are several supporting "resource requests" to load the CSS, js and img files. You can check this in Chrome using the F12 feature (Network tab). For these requests, the webview does not add headers. I tried adding custom headers to FireFox requests using the addons.mozilla.org/en-us/firefox/addon/modify-headers plug-in. This plugin was able to add headers to all such suppporting "resource requests". I think WebView should do the same. Commented Feb 1, 2013 at 2:13
  • 2
    @MediumOne: "I think WebView should do the same" -- which is a feature that you consider to be missing. Note that you had to resort to a plugin to make Firefox do this. I am not saying that your proposed feature is a bad idea. I am saying that characterizing it as a bug is unlikely to help your cause to get this proposed feature added to Android. Commented Feb 1, 2013 at 14:39
  • 1
    @CommonsWare: Suppose that I am using a WebView to build a browser that can be configured to work with a custom HTTP proxy. This proxy uses custom authentication where are requests to it should have a custom header. Now, webview provides an API to set custom headers, but internally, it is not setting the header to all the resource requests it generates. There aren't any additional APIs to set headers for these requests as well. So, any feature which relies on adding custom headers to WebView requests fails. Commented Feb 2, 2013 at 16:11
  • 1
    @CommonsWare - I am revisiting this conversation after 4 years. I agree now - this should not be a bug. There's nothing in the HTTP specification that says subsequent requests should send the same headers. :) Commented Jun 4, 2017 at 18:16

13 Answers 13

97

Try

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

For adding headers to resources loading requests, make custom WebViewClient and override:

API 24+:
WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)
or
WebResourceResponse shouldInterceptRequest(WebView view, String url)
Sign up to request clarification or add additional context in comments.

8 Comments

Sorry, but this does not work. It only applies the headers too the initial requests. Headers are NOT added to the resource requests. Other ideas? Thanks.
Yes, override WebClient.shouldOverrideUrlLoading like this: public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url, extraHeaders); return true; }
@peceps - the callback 'shouldOverrideUrlLoading' is not called during resource loading. For example, when we try view.loadUrl("http://www.facebook.com", extraHeaders), there are multiple resource requests like 'http://static.fb.com/images/logo.png' etc that are sent from the webiew. For these requests, the extra headers are not added. And shouldOverrideUrlLoading is not called during such resource requests. The callback 'OnLoadResource' is called, but there is no way to set headers at this point.
@MediumOne, for resource loading, override WebViewClient.shouldInterceptRequest(android.webkit.WebView view, java.lang.String url) Check out API for more.
@yorkw: This method does capture all resource request urls. But there is no way to add headers to these requests. My goal is to add custom HTTP headers to all requests. If this can be achieved using the shouldInterceptRequest method, can you please explain how?
|
44

You will need to intercept each request using WebViewClient.shouldInterceptRequest

With each interception, you will need to take the url, make this request yourself, and return the content stream:

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);

If your minimum API target is level 21, you can use the new shouldInterceptRequest which gives you additional request information (such as headers) instead of just the URL.

9 Comments

Just in case someone encounters same situation I have while using this trick. (This is a good one anyway.) Here is a note for you. Since http content-type header, which can contain optional parameter such as charset, is not fully compatible to MIME type, the requirement of first parameter of WebResourceResponse constructor, so that we should extract the MIME type part from content-type by any mean you can think of, such as RegExp, to make it work for most cases.
This event is deprecated.. use public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request) instead find more here
@HirdeshVishwdewa - look at the last sentence.
You can skip doing your own load by returning the results of the superclass's shouldInterceptRequest method with your webview and modified request as parameters. This is particularly handy in scenarios where you're triggering based on the URL, aren't changing it on the reload and would run into an infinite loop. Much thanks for the new request example though. Java ways of handling things are highly counterintuitive to me.
HttpClient can't be used with compileSdk 23 and above,
|
39

Maybe my response quite late, but it covers API below and above 21 level.

To add headers we should intercept every request and create new one with required headers.

So we need to override shouldInterceptRequest method called in both cases: 1. for API until level 21; 2. for API level 21+

    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;
            }

        }
   });

If response type should be processed you could change

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

to

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

and add method

        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;
        }

2 Comments

Sorry to answer to this old post, but with this code my app tries to download the file (and it fails) instead of loading the page.
can this be used for post requests ?
24

As mentioned before, you can do this:

 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);

I tested this and on with a MVC Controller that I extended the Authorize Attribute to inspect the header and the header is there.

4 Comments

I will have to address this again as when it was written and posted it functioned with the Kit-Kat. I have not tried with Lolly Pop.
Not working for me on Jelly bean or Marshmallow... doesn't change anything in the headers
This doesn't do what OP is asking. He wants to add headers to all requests made by the webview. This adds custom header to first request only
I know this doesn't answer what OP was looking for but this was exactly what I wanted, i.e. adding an extra header to a WebViewIntent URL. Thanks, regardless!
17

This works for me:

  1. First you need to create method, which will be returns your headers you want to add to request:

    private Map<String, String> getCustomHeaders()
    {
        Map<String, String> headers = new HashMap<>();
        headers.put("YOURHEADER", "VALUE");
        return headers;
    }
    
  2. Second you need to create 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. Add WebViewClient to your WebView:

    webView.setWebViewClient(getWebViewClient());
    

Hope this helps.

3 Comments

Looks good, but does this add a header, or does this replace the headers?
@IvoRenkema loadUrl(String url, Map<String, String> additionalHttpHeaders) means add additions headers
Apparently this won't work for resources inside the page (for example, images), so this does not address the OP question.
5

Here is an implementation using 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
    }
}

Note that this does not work for POST requests because WebResourceRequest doesn't provide POST data. There is a Request Data - WebViewClient library which uses a JavaScript injection workaround for intercepting POST data.

Comments

3

You should be able to control all your headers by skipping loadUrl and writing your own loadPage using Java's HttpURLConnection. Then use the webview's loadData to display the response.

There is no access to the headers which Google provides. They are in a JNI call, deep in the WebView source.

1 Comment

Do you have any reference to what you say in your answer.. it will be helpful for others if you give implementation references with your answers.
2

This worked for me. Create WebViewClient like this below and set the webclient to your webview. I had to use webview.loadDataWithBaseURL as my urls (in my content) did not have the baseurl but only relative urls. You will get the url correctly only when there is a baseurl set using loadDataWithBaseURL.

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;
            }
        }
    };

}

1 Comment

for me it works: .post(reqbody) where RequestBody reqbody = RequestBody.create(null, "");
0

shouldInterceptRequest could not intercept request.body. I recommend you reset userAgent before werview.loadUrl.

like: webview.settings.userAgentString += " key1/value1 key2/value2"

Comments

0

You can try my solution for the issue, and while I acknowledge that it may not be universally applicable, it has proven effective in meeting my specific requirements, particularly when dealing with a significant number of JavaScript fetch calls that do not trigger WebViewClient callbacks.

Long story short:

To add custom headers into every WebView HTTP request, you can inject java script code that overrides window.fetch behavior in your own WebViewClient.

The following is a detailed implementation

1. Create your custom WebViewClient

class CustomHeaderWebViewClient(headerValue: String) : WebViewClient() { }

In this custom WebViewClient, you can pass the headerValue that you want to inject into HTTP headers.

2. Define JS-injection code

Create a property that stores your JavaScript injection code. This code will modify the window.fetch method to include the custom header:

private val authHeaderInjection = """
            (function() {
                const originalFetch = window.fetch;
                window.fetch = function(input, init) {
                    init = init || {};
                    init.headers = init.headers || {};
                    init.headers['My-Header'] = '$headerValue';
                    return originalFetch.apply(this, arguments);
                };
            })();
        """.trimIndent()

3. Override onPageStarted Method

You need to override the onPageStarted method to execute the JavaScript injection on each navigation:

override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
    view?.evaluateJavascript(authHeaderInjection) { Timber.d(it) }
    super.onPageStarted(view, url, favicon)
}

Note: Depending on the implementation of the WebView's content, you can choose different callbacks.

4. Finally, don't forget to set your custom WebViewClient for your WebView.

Note: Please note that you may still need to manually add other custom headers when using webview.loadUrl(url, yourHeaders) as per your requirements.

Final code:

import android.graphics.Bitmap
import android.webkit.WebView
import android.webkit.WebViewClient
import timber.log.Timber

class CustomHeaderWebViewClient(headerValue: String) : WebViewClient() {
    private val authHeaderInjection = """
            (function() {
                const originalFetch = window.fetch;
                window.fetch = function(input, init) {
                    init = init || {};
                    init.headers = init.headers || {};
                    init.headers['My-Header'] = '${headerValue}';
                    return originalFetch.apply(this, arguments);
                };
            })();
        """.trimIndent()

    override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
        view?.evaluateJavascript(authHeaderInjection) { Timber.d(it) }
        super.onPageStarted(view, url, favicon)
    }
}

Comments

-2

You can use this:

@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 Comments

This just keeps reloading the url, doesn't it?
@CarloMoretti no, it doesn't. But it does not address OP question anyway.
-3

I came accross the same problem and solved.

As said before you need to create your custom WebViewClient and override the shouldInterceptRequest method.

WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)

That method should issue a webView.loadUrl while returning an "empty" WebResourceResponse.

Something like this:

@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);
}

4 Comments

Calling view.loadUrl from this method seems to crash the app
@willcwf do you have an example of this crashing?
@Francesco my app also crashes
Too all is downvoting this, saying that it crash is not helping. Please be more specific, write some error information.
-19

Use this:

webView.getSettings().setUserAgentString("User-Agent");

2 Comments

this doesn't answer the question
this is not the same as authorization header

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.