Добавление настраиваемых заголовков в запросы ресурсов WebView - android


97

Мне нужно добавлять пользовательские заголовки к КАЖДОМУ запросу, исходящему от WebView. Я знаю, что loadURLесть параметр для extraHeaders, но они применяются только к первоначальному запросу. Все последующие запросы не содержат заголовков. Я просмотрел все переопределения WebViewClient, но ничего не позволяет добавлять заголовки в запросы ресурсов - onLoadResource(WebView view, String url). Любая помощь была бы замечательной.

Спасибо, Рэй


2
@MediumOne: это не ошибка, а функция, которую вы считаете отсутствующей. Мне ничего не известно в спецификации HTTP, где говорится, что последующие HTTP-запросы должны отражать произвольные заголовки из предыдущих HTTP-запросов.
CommonsWare

1
@CommonsWare: слово «последующий» здесь вводит в заблуждение. Когда я набираю « facebook.com » в любом браузере, чтобы загрузить домашнюю страницу facebook.com, есть несколько поддерживающих «запросов ресурсов» для загрузки файлов CSS, js и img. Вы можете проверить это в Chrome с помощью функции F12 (вкладка «Сеть»). Для этих запросов веб-просмотр не добавляет заголовки. Я пробовал добавлять собственные заголовки в запросы FireFox с помощью плагина addons.mozilla.org/en-us/firefox/addon/modify-headers . Этот плагин мог добавлять заголовки ко всем таким «запросам ресурсов». Я думаю, что WebView должен делать то же самое.
MediumOne 01

1
@MediumOne: «Я думаю, что WebView должен делать то же самое» - эта функция, по вашему мнению, отсутствует. Обратите внимание, что вам пришлось прибегнуть к плагину, чтобы Firefox сделал это. Я не говорю, что предложенная вами функция - плохая идея. Я говорю, что описание этого как ошибки вряд ли поможет вам добавить эту предлагаемую функцию в Android.
CommonsWare 01

1
@CommonsWare: предположим, что я использую WebView для создания браузера, который можно настроить для работы с настраиваемым прокси-сервером HTTP. Этот прокси-сервер использует настраиваемую аутентификацию, при этом запросы к нему должны иметь настраиваемый заголовок. Теперь webview предоставляет API для установки пользовательских заголовков, но внутри он не устанавливает заголовок для всех запросов ресурсов, которые он генерирует. Также нет никаких дополнительных API для установки заголовков для этих запросов. Таким образом, любая функция, которая полагается на добавление настраиваемых заголовков в запросы WebView, не работает.
MediumOne

1
@CommonsWare - я возвращаюсь к этому разговору через 4 года. Я согласен сейчас - это не должно быть ошибкой. В спецификации HTTP нет ничего, что говорило бы, что последующие запросы должны отправлять те же заголовки. :)
MediumOne 04

Ответы:


84

Пытаться

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

Для добавления заголовков к запросам загрузки ресурсов создайте собственный WebViewClient и переопределите:

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

9
Извините, но это не работает. Он применяет только заголовки к начальным запросам. Заголовки НЕ добавляются к запросам ресурсов. Другие идеи? Спасибо.
Ray

19
Да, переопределить WebClient.shouldOverrideUrlLoading следующим образом: публичное логическое значение shouldOverrideUrlLoading (представление WebView, строковый URL-адрес) {view.loadUrl (url, extraHeaders); вернуть истину; }
peceps

5
@peceps - обратный вызов shouldOverrideUrlLoading не вызывается во время загрузки ресурса. Например, когда мы пытаемся view.loadUrl("http://www.facebook.com", extraHeaders), 'http://static.fb.com/images/logo.png'из веб-просмотра отправляется несколько запросов к ресурсам, таких как и т. Д. Для этих запросов дополнительные заголовки не добавляются. И shouldOverrideUrlLoading не вызывается во время таких запросов ресурсов. Вызывается обратный вызов OnLoadResource, но на этом этапе нельзя установить заголовки.
MediumOne

2
@MediumOne, для загрузки ресурсов, переопределите APIWebViewClient.shouldInterceptRequest(android.webkit.WebView view, java.lang.String url) Check out для получения дополнительной информации.
Йорк, 01

3
@yorkw: этот метод захватывает все URL-адреса запросов ресурсов. Но к этим запросам нельзя добавлять заголовки. Моя цель - добавить настраиваемые заголовки HTTP ко всем запросам. Если этого можно достичь с помощью shouldInterceptRequestметода, не могли бы вы объяснить, как?
MediumOne 01

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
На всякий случай кто-то столкнется с той же ситуацией, что и я при использовании этого трюка. (В любом случае, это хороший вариант.) Вот вам заметка. Поскольку заголовок типа содержимого http, который может содержать необязательный параметр, такой как кодировка, не полностью совместим с типом MIME, требование первого параметра конструктора WebResourceResponse, поэтому мы должны извлекать часть типа MIME из типа содержимого любым способом можно придумать, например, RegExp, чтобы заставить его работать в большинстве случаев.
Джеймс Чен

2
Это событие устарело .. используйте public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request) вместо этого подробнее здесь
Hirdesh Vishwdewa

3
@HirdeshVishwdewa - посмотрите на последнее предложение.
Мартин Конечны

2
Вы можете пропустить выполнение собственной загрузки, вернув результаты метода shouldInterceptRequest суперкласса с вашим веб-просмотром и измененным запросом в качестве параметров. Это особенно удобно в сценариях, когда вы запускаете на основе URL-адреса, не меняете его при перезагрузке и запускаете бесконечный цикл. Однако большое спасибо за новый пример запроса. Для меня способы работы с вещами в Java крайне противоречивы.
Эрик Реппен

4
HttpClient нельзя использовать с compileSdk 23 и выше,
Тамаш Козмер.

30

Возможно, мой ответ довольно поздний, но он касается API ниже и выше 21 уровня.

Чтобы добавить заголовки, мы должны перехватить каждый запрос и создать новый с необходимыми заголовками.

Поэтому нам нужно переопределить метод shouldInterceptRequest, вызываемый в обоих случаях: 1. для API до уровня 21; 2. для уровня API 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;
            }

        }
   });

Если необходимо обработать тип ответа, вы можете изменить

        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

можно ли это использовать для почтовых запросов?
mrid

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, который расширил атрибут авторизации, чтобы проверить заголовок, и заголовок там.


Мне придется обратиться к этому еще раз, поскольку когда он был написан и опубликован, он работал с Kit-Kat. Я не пробовал с Lolly Pop.
leeroya 01

У меня не работает Jelly bean или Marshmallow ... ничего не меняет в заголовках
Эрик Вербум

6
Это не делает то, о чем просит OP. Он хочет добавить заголовки ко всем запросам, сделанным веб-просмотром. Это добавляет настраиваемый заголовок только к первому запросу
NinjaCoder

OP спрашивает не об этом
Акшай

Я знаю, что это не отвечает на то, что искал OP, но это было именно то, что я хотел, то есть добавление дополнительного заголовка к URL-адресу WebViewIntent. Спасибо, несмотря ни на что!
Джошуа Пинтер

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
Выглядит хорошо, но добавляет ли это заголовок или заменяет заголовки?
Иво Ренкема

@IvoRenkema loadUrl(String url, Map<String, String> additionalHttpHeaders) означает добавление заголовков дополнений
AbhinayMe

4

Вы должны иметь возможность контролировать все свои заголовки, пропустив loadUrl и написав свой собственный loadPage с использованием Java HttpURLConnection. Затем используйте loadData веб-просмотра для отображения ответа.

Нет доступа к заголовкам, которые предоставляет Google. Они находятся в вызове JNI глубоко в источнике WebView.


1
Есть ли у вас какие-либо ссылки на то, что вы говорите в своем ответе ... будет полезно для других, если вы дадите ссылки на реализацию в своих ответах.
Hirdesh Vishwdewa

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, как показано ниже, и установите веб-клиент для своего веб-просмотра. Мне пришлось использовать webview.loadDataWithBaseURL, поскольку мои URL-адреса (в моем контенте) не имели базового URL-адреса, а имели только относительные URL-адреса. Вы получите правильный URL-адрес только при наличии базового URL-адреса, установленного с помощью 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;
            }
        }
    };

}

для меня это работает: .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
Это просто продолжает перезагружать URL-адрес, не так ли?
Onheiron

-3

Я столкнулся с той же проблемой и решил.

Как было сказано ранее, вам необходимо создать собственный WebViewClient и переопределить метод shouldInterceptRequest.

WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)

Этот метод должен выдавать webView.loadUrl при возврате «пустого» WebResourceResponse.

Что-то вроде этого:

@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

@willcwf у вас есть пример такого сбоя?
Francesco

@Francesco, мое приложение тоже вылетает
Giacomo M

Слишком все опускают это, говоря, что сбой не помогает. Пожалуйста, будьте более конкретны, напишите информацию об ошибке.
Франческо

-14

Использовать это:

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

11
это не отвечает на вопрос
younes0 04

это не то же самое, что заголовок авторизации
Влад
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.