Agregar encabezados personalizados a las solicitudes de recursos de WebView - android

95

Necesito agregar encabezados personalizados a CADA solicitud proveniente de WebView. Sé que loadURLtiene el parámetro para extraHeaders, pero esos solo se aplican a la solicitud inicial. Todas las solicitudes posteriores no contienen los encabezados. He analizado todas las anulaciones WebViewClient, pero nada permite agregar encabezados a las solicitudes de recursos - onLoadResource(WebView view, String url). Cualquier ayuda sería maravillosa.

Gracias Ray

Rayo
fuente
2
@MediumOne: Esto no es un error, sino una característica que considera que falta. No tengo conocimiento de nada en la especificación HTTP que diga que las solicitudes HTTP posteriores deben reflejar encabezados arbitrarios de solicitudes HTTP anteriores.
CommonsWare
1
@CommonsWare: La palabra "subsiguiente" es engañosa aquí. Cuando escribo " facebook.com " en cualquier navegador para cargar la página de inicio de facebook.com, hay varias "solicitudes de recursos" de apoyo para cargar los archivos CSS, js e img. Puede verificar esto en Chrome usando la función F12 (pestaña Red). Para estas solicitudes, la vista web no agrega encabezados. Intenté agregar encabezados personalizados a las solicitudes de FireFox usando el complemento addons.mozilla.org/en-us/firefox/addon/modify-headers . Este complemento pudo agregar encabezados a todas esas "solicitudes de recursos" de soporte. Creo que WebView debería hacer lo mismo.
MediumOne
1
@MediumOne: "Creo que WebView debería hacer lo mismo", que es una característica que consideras que falta. Tenga en cuenta que tuvo que recurrir a un complemento para que Firefox hiciera esto. No estoy diciendo que su función propuesta sea una mala idea. Estoy diciendo que caracterizarlo como un error es poco probable que ayude a su causa a agregar esta característica propuesta a Android.
CommonsWare
1
@CommonsWare: supongamos que estoy usando un WebView para construir un navegador que se puede configurar para que funcione con un proxy HTTP personalizado. Este proxy utiliza autenticación personalizada donde las solicitudes deben tener un encabezado personalizado. Ahora, webview proporciona una API para configurar encabezados personalizados, pero internamente, no configura el encabezado para todas las solicitudes de recursos que genera. Tampoco hay API adicionales para establecer encabezados para estas solicitudes. Por lo tanto, cualquier función que se base en agregar encabezados personalizados a las solicitudes de WebView falla.
MediumOne
1
@CommonsWare - Estoy revisando esta conversación después de 4 años. Estoy de acuerdo ahora, esto no debería ser un error. No hay nada en la especificación HTTP que indique que las solicitudes posteriores deben enviar los mismos encabezados. :)
MediumOne

Respuestas:

81

Tratar

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

Para agregar encabezados a las solicitudes de carga de recursos, cree WebViewClient personalizado y anule:

API 24+:
WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)
or
WebResourceResponse shouldInterceptRequest(WebView view, String url)
peceps
fuente
9
Lo siento, pero esto no funciona. Solo aplica los encabezados también las solicitudes iniciales. Los encabezados NO se agregan a las solicitudes de recursos. ¿Otras ideas? Gracias.
Ray
19
Sí, anule WebClient.shouldOverrideUrlLoading así: public boolean shouldOverrideUrlLoading (vista WebView, String url) {view.loadUrl (url, extraHeaders); devuelve verdadero; }
peceps
5
@peceps: la devolución de llamada 'shouldOverrideUrlLoading' no se llama durante la carga de recursos. Por ejemplo, cuando lo intentamos view.loadUrl("http://www.facebook.com", extraHeaders), hay varias solicitudes de recursos como, 'http://static.fb.com/images/logo.png'etc., que se envían desde la vista web. Para estas solicitudes, no se agregan los encabezados adicionales. Y shouldOverrideUrlLoading no se llama durante tales solicitudes de recursos. Se llama a la devolución de llamada 'OnLoadResource', pero no hay forma de establecer encabezados en este punto.
MediumOne
2
@MediumOne, para cargar recursos, anular WebViewClient.shouldInterceptRequest(android.webkit.WebView view, java.lang.String url)Consulte la API para obtener más información.
yorkw
3
@yorkw: este método captura todas las URL de solicitud de recursos. Pero no hay forma de agregar encabezados a estas solicitudes. Mi objetivo es agregar encabezados HTTP personalizados a todas las solicitudes. Si esto se puede lograr utilizando el shouldInterceptRequestmétodo, ¿podría explicar cómo?
MediumOne
36

Deberá interceptar cada solicitud mediante WebViewClient.shouldInterceptRequest

Con cada intercepción, deberá tomar la URL, realizar esta solicitud usted mismo y devolver el flujo de contenido:

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

Si su objetivo de API mínimo es el nivel 21 , puede usar el nuevo shouldInterceptRequest que le brinda información de solicitud adicional (como encabezados) en lugar de solo la URL.

Martin Konecny
fuente
2
En caso de que alguien se encuentre con la misma situación que tengo al usar este truco. (De todos modos, esta es buena.) Aquí hay una nota para ti. Dado que el encabezado de tipo de contenido http, que puede contener un parámetro opcional como charset, no es totalmente compatible con el tipo MIME, el requisito del primer parámetro del constructor WebResourceResponse, por lo que deberíamos extraer la parte de tipo MIME del tipo de contenido por cualquier medio que usted puede pensar, como RegExp, para que funcione en la mayoría de los casos.
James Chen
2
Este evento está desaprobado ... use public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)en su lugar busque más aquí
Hirdesh Vishwdewa
3
@HirdeshVishwdewa - mira la última oración.
Martin Konecny
2
Puede omitir hacer su propia carga devolviendo los resultados del método shouldInterceptRequest de la superclase con su vista web y la solicitud modificada como parámetros. Esto es particularmente útil en escenarios en los que se activa en función de la URL, no la cambia en la recarga y se ejecuta en un bucle infinito. Muchas gracias por el nuevo ejemplo de solicitud. Las formas de Java de manejar las cosas son muy contradictorias para mí.
Erik Reppen
4
HttpClient no se puede utilizar con compileSdk 23 y superior,
Tamás Kozmér
30

Quizás mi respuesta sea bastante tarde, pero cubre API por debajo y por encima del nivel 21.

Para agregar encabezados, debemos interceptar cada solicitud y crear una nueva con los encabezados requeridos.

Así que necesitamos anular el método shouldInterceptRequest llamado en ambos casos: 1. para API hasta el nivel 21; 2. para API nivel 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;
            }

        }
   });

Si se debe procesar el tipo de respuesta, puede cambiar

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

a

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

y agregue el método

        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;
        }
Sergey Bondarenko
fuente
1
Lamento responder a esta publicación anterior, pero con este código mi aplicación intenta descargar el archivo (y falla) en lugar de cargar la página.
Giacomo M
¡Muchas gracias!
AlexS
21

Como se mencionó anteriormente, puede hacer esto:

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

Probé esto y con un controlador MVC que extendí el atributo de autorización para inspeccionar el encabezado y el encabezado está allí.

leeroya
fuente
Tendré que abordar esto nuevamente, ya que cuando fue escrito y publicado funcionó con el Kit-Kat. No lo he probado con Lolly Pop.
leeroya
No funciona para mí en Jelly Bean o Marshmallow ... no cambia nada en los encabezados
Erik Verboom
6
Esto no hace lo que pide OP. Quiere agregar encabezados a todas las solicitudes realizadas por la vista web. Esto agrega un encabezado personalizado solo a la primera solicitud
NinjaCoder
Esto no es lo que pide OP
Akshay
Sé que esto no responde a lo que OP estaba buscando, pero esto era exactamente lo que quería, es decir, agregar un encabezado adicional a una URL de WebViewIntent. ¡Gracias de todos modos!
Joshua Pinter
9

Esto funciona para mi:

  1. Primero debe crear un método, que devolverá los encabezados que desea agregar a la solicitud:

    private Map<String, String> getCustomHeaders()
    {
        Map<String, String> headers = new HashMap<>();
        headers.put("YOURHEADER", "VALUE");
        return headers;
    }
  2. En segundo lugar, debe crear 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. Agregue WebViewClient a su WebView:

    webView.setWebViewClient(getWebViewClient());

Espero que esto ayude.

eltray
fuente
1
Se ve bien, pero ¿esto agrega un encabezado o reemplaza los encabezados?
Ivo Renkema
@IvoRenkema loadUrl(String url, Map<String, String> additionalHttpHeaders) significa agregar encabezados adicionales
AbhinayMe
4

Debería poder controlar todos sus encabezados omitiendo loadUrl y escribiendo su propia página de carga usando HttpURLConnection de Java. Luego use loadData de la vista web para mostrar la respuesta.

No hay acceso a los encabezados que proporciona Google. Están en una llamada JNI, en lo profundo de la fuente de WebView.

R Earle Harris
fuente
1
¿Tiene alguna referencia a lo que dice en su respuesta? Será útil para otros si proporciona referencias de implementación con sus respuestas.
Hirdesh Vishwdewa
1

Aquí hay una implementación usando 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
    }
}

Tenga en cuenta que esto no funciona para las solicitudes POST porque WebResourceRequest no proporciona datos POST. Existe una biblioteca de Solicitud de datos: WebViewClient que utiliza una solución alternativa de inyección de JavaScript para interceptar datos POST.

Miloš Černilovský
fuente
0

Esto funcionó para mí. Cree WebViewClient como este a continuación y configure el cliente web en su vista web. Tuve que usar webview.loadDataWithBaseURL ya que mis URL (en mi contenido) no tenían la baseurl sino solo las URL relativas. Obtendrá la URL correctamente solo cuando haya un conjunto de baseurl usando 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;
            }
        }
    };

}
Shekar
fuente
para mí funciona: .post (reqbody) donde RequestBody reqbody = RequestBody.create (null, "");
Karoly
-2

Puedes usar esto:

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

            }
demir
fuente
2
Esto solo vuelve a cargar la URL, ¿no es así?
Onheiron
-3

Me encontré con el mismo problema y lo resolví.

Como se dijo antes, debe crear su WebViewClient personalizado y anular el método shouldInterceptRequest.

WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)

Ese método debería emitir un webView.loadUrl mientras devuelve un WebResourceResponse "vacío".

Algo como esto:

@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);
}
Francesco
fuente
Llamar a view.loadUrl desde este método parece bloquear la aplicación
willcwf
@willcwf, ¿tiene un ejemplo de este bloqueo?
Francesco
@Francesco mi aplicación también se bloquea
Giacomo M
Demasiado todos están rechazando esto, diciendo que el colapso no está ayudando. Sea más específico, escriba alguna información de error.
Francesco
-14

Utilizar este:

webView.getSettings().setUserAgentString("User-Agent");
Satish
fuente
11
esto no responde la pregunta
younes0
esto no es lo mismo que el encabezado de autorización
Vlad