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

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

Спасибо рэй

14 ответов

Пытаться

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

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

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

Вам нужно будет перехватить каждый запрос, используя 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, вы можете использовать новый mustInterceptRequest, который дает вам дополнительную информацию о запросе (например, заголовки), а не только URL.

Может быть, мой ответ довольно поздно, но он охватывает 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;
        }

Как упоминалось ранее, вы можете сделать это:

 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 для проверки заголовка, и заголовок там.

Это работает для меня:

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

Надеюсь это поможет.

Вот реализация, использующая 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.

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

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

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

}

shouldInterceptRequestне смог перехватитьrequest.body. Я рекомендую вам сбросить userAgent передwerview.loadUrl.

нравиться:webview.settings.userAgentString += " key1/value1 key2/value2"

Вы можете попробовать мое решение этой проблемы, и хотя я признаю, что оно не может быть универсальным, оно доказало свою эффективность в удовлетворении моих конкретных требований, особенно при работе со значительным количеством вызовов выборки JavaScript, которые не запускают обратные вызовы WebViewClient.

Короче говоря:

Чтобы добавить пользовательские заголовки в каждый HTTP-запрос WebView, вы можете внедрить код Java-скрипта, который переопределяет поведение window.fetch в вашем собственном WebViewClient.

Ниже приведена подробная реализация

1. Создайте свой собственный WebViewClient.

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

В этом пользовательском WebViewClient вы можете передать headerValue, которое хотите внедрить в заголовки HTTP.

2. Определить код JS-инъекции

Создайте свойство, в котором будет храниться код внедрения JavaScript. Этот код изменит метод window.fetch, включив в него пользовательский заголовок:

      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. Переопределить метод onPageStarted

Вам необходимо переопределить метод onPageStarted для выполнения внедрения JavaScript при каждой навигации:

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

Примечание . В зависимости от реализации содержимого WebView вы можете выбрать разные обратные вызовы.

4. Наконец, не забудьте установить собственный WebViewClient для вашего WebView.

Примечание . Обратите внимание, что вам все равно может потребоваться вручную добавить другие пользовательские заголовки при использованииwebview.loadUrl(url, yourHeaders)согласно вашим требованиям.

Окончательный код:

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

Вы можете использовать это:

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

            }

Страница: MainActivity.java

импорт android.webkit.WebView;

открытый класс MainActivity расширяет AppCompatActivity {

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    WebView WebViw = (WebView) findViewById(R.id.webView1);
    WebViw.getSettings().setJavaScriptEnabled(true);
    WebViw.getSettings().setUserAgentString("User-Agent");
    WebViw.loadUrl("http://www.educationlife-online.com/");

}

}

этот путь - Успех.

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

Как уже было сказано, вам нужно создать свой собственный 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);
}

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

webView.getSettings().setUserAgentString("User-Agent");
Другие вопросы по тегам