Додайте власні заголовки до запитів ресурсів WebView - android


95

Мені потрібно додати власні заголовки до КОЖНОГО запиту, що надходить із 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

1
@MediumOne: "Я думаю, що WebView повинен робити те саме" - ця функція, яку ви вважаєте відсутнім. Зверніть увагу, що вам довелося вдатися до плагіна, щоб Firefox зробив це. Я не кажу, що запропонована вами функція - погана ідея. Я кажу, що характеризуючи його як помилку навряд чи допоможе вашій справі додати цю запропоновану функцію до Android.
CommonsWare

1
@CommonsWare: Припустимо, що я використовую WebView для побудови браузера, який можна налаштувати на роботу зі спеціальним проксі HTTP. Цей проксі використовує власну автентифікацію, де запити до нього повинні мати власний заголовок. Зараз Webview надає API для встановлення користувацьких заголовків, але внутрішньо він не встановлює заголовок для всіх запитів ресурсів, які він генерує. Немає додаткових API для встановлення заголовків для цих запитів. Отже, будь-яка функція, яка покладається на додавання власних заголовків до запитів WebView, не працює.
MediumOne 02

1
@CommonsWare - Я переглядаю цю розмову через 4 роки. Я згоден зараз - це не повинна бути помилка. У специфікації HTTP немає нічого, що говорить, що наступні запити повинні надсилати однакові заголовки. :)
MediumOne

Відповіді:


81

Спробуйте

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, 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, для завантаження ресурсу, перевизначення WebViewClient.shouldInterceptRequest(android.webkit.WebView view, java.lang.String url)Перевірте API, щоб дізнатись більше.
yorkw

3
@yorkw: Цей метод захоплює всі URL-адреси запитів ресурсів. Але немає можливості додати заголовки до цих запитів. Моя мета - додати власні заголовки HTTP до всіх запитів. Якщо цього можна досягти за допомогою shouldInterceptRequestметоду, чи можете ви пояснити, як?
MediumOne

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, щоб змусити його працювати в більшості випадків.
James Chen

2
Ця подія застаріла .. використовуйте public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)замість цього, знайдіть більше тут
Hirdesh Vishwdewa

3
@HirdeshVishwdewa - подивіться на останнє речення.
Martin Konecny

2
Ви можете пропустити самостійне завантаження, повернувши результати методу superInclaceptRequest суперкласу з вашим веб-переглядом та зміненим запитом як параметри. Це особливо зручно у сценаріях, коли ви запускаєте на основі 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
Вибачте, я відповів на цю стару публікацію, але за допомогою цього коду моя програма намагається завантажити файл (і це не вдається) замість завантаження сторінки.
Джакомо М

Велике спасибі!
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, що я розширив атрибут авторизації, щоб перевірити заголовок, і заголовок там є.


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

Не працює для мене на Jelly Bean або Marshmallow ... нічого не змінює в заголовках
Erik Verboom

6
Це не робить того, про що запитує OP. Він хоче додати заголовки до всіх запитів веб-перегляду. Це додає спеціальний заголовок лише до першого запиту
NinjaCoder

Це не те, що просить ОП
Акшай,

Я знаю, що це не відповідає на те, що шукав 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. Існує Request Data - бібліотека WebViewClient, яка використовує обхідний спосіб введення JavaScript для перехоплення даних POST.


0

Це спрацювало для мене. Створіть WebViewClient, як показано нижче, і встановіть для веб-клієнта ваш веб-перегляд. Мені довелося використовувати webview.loadDataWithBaseURL, оскільки мої URL-адреси (у моєму вмісті) не мали baseurl, а лише відносні URL-адреси. Ви отримаєте URL-адресу правильно лише тоді, коли є baseurl, встановлений за допомогою 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, чи не так?
Онхейрон,

-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 мій додаток також аварійно завершує роботу
Giacomo M

Занадто все голосує проти цього, кажучи, що це аварії не допомагає. Будь ласка, будьте більш конкретні, напишіть деяку інформацію про помилку.
Франческо

-14

Використовуй це:

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

11
це не відповідає на питання
younes0

це не те саме, що заголовок авторизації
Влад
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.