Adicionar cabeçalhos personalizados às solicitações de recursos do WebView - android

95

Preciso adicionar cabeçalhos personalizados a CADA solicitação proveniente do WebView. Eu sei que loadURLtem o parâmetro para extraHeaders, mas eles se aplicam apenas à solicitação inicial. Todas as solicitações subsequentes não contêm os cabeçalhos. Eu olhei todas as substituições emWebViewClient , mas nada permite adicionar cabeçalhos às solicitações de recursos - onLoadResource(WebView view, String url). Qualquer ajuda seria maravilhosa.

Obrigado, Ray

Raio
fonte
2
@MediumOne: Este não é um bug, mas um recurso que você considera estar faltando. Não estou ciente de nada na especificação HTTP que diga que as solicitações HTTP subsequentes devem espelhar cabeçalhos arbitrários de solicitações HTTP anteriores.
CommonsWare
1
@CommonsWare: A palavra "subsequente" é enganosa aqui. Quando eu digito " facebook.com " em qualquer navegador para carregar a página inicial do facebook.com, existem várias "solicitações de recursos" de suporte para carregar os arquivos CSS, js e img. Você pode verificar isso no Chrome usando o recurso F12 (guia Rede). Para essas solicitações, o webview não adiciona cabeçalhos. Tentei adicionar cabeçalhos personalizados às solicitações do FireFox usando o plug-in addons.mozilla.org/en-us/firefox/addon/modify-headers . Este plugin foi capaz de adicionar cabeçalhos a todos esses "pedidos de recursos" de suporte. Acho que o WebView deve fazer o mesmo.
MediumOne
1
@MediumOne: "Acho que o WebView deve fazer o mesmo" - que é um recurso que você considera ausente. Observe que você teve que recorrer a um plugin para fazer o Firefox fazer isso. Não estou dizendo que o recurso proposto seja uma má ideia. Estou dizendo que caracterizá-lo como um bug dificilmente ajudará a sua causa para que esse recurso proposto seja adicionado ao Android.
CommonsWare
1
@CommonsWare: Suponha que estou usando um WebView para construir um navegador que pode ser configurado para funcionar com um proxy HTTP personalizado. Este proxy usa autenticação personalizada, onde as solicitações para ele devem ter um cabeçalho personalizado. Agora, o webview fornece uma API para definir cabeçalhos personalizados, mas internamente, ele não define o cabeçalho para todas as solicitações de recursos que gera. Não há APIs adicionais para definir cabeçalhos para essas solicitações também. Portanto, qualquer recurso que dependa da adição de cabeçalhos personalizados às solicitações do WebView falha.
MediumOne
1
@CommonsWare - Estou revisitando essa conversa depois de 4 anos. Eu concordo agora - isso não deve ser um bug. Não há nada na especificação HTTP que diga que as solicitações subsequentes devem enviar os mesmos cabeçalhos. :)
MediumOne

Respostas:

80

Experimentar

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

Para adicionar cabeçalhos às solicitações de carregamento de recursos, faça WebViewClient personalizado e substitua:

API 24+:
WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)
or
WebResourceResponse shouldInterceptRequest(WebView view, String url)
peceps
fonte
9
Desculpe, mas isso não funciona. Ele apenas aplica os cabeçalhos às solicitações iniciais. Os cabeçalhos NÃO são adicionados às solicitações de recursos. Outras idéias? Obrigado.
Ray
19
Sim, substitua WebClient.shouldOverrideUrlLoading assim: public boolean shouldOverrideUrlLoading (visualização WebView, String url) {view.loadUrl (url, extraHeaders); return true; }
peceps
5
@peceps - o retorno de chamada 'shouldOverrideUrlLoading' não é chamado durante o carregamento do recurso. Por exemplo, quando tentamos view.loadUrl("http://www.facebook.com", extraHeaders), há várias solicitações de recursos como 'http://static.fb.com/images/logo.png'etc que são enviadas da webiew. Para essas solicitações, os cabeçalhos extras não são adicionados. E shouldOverrideUrlLoading não é chamado durante essas solicitações de recursos. O retorno de chamada 'OnLoadResource' é chamado, mas não há como definir cabeçalhos neste ponto.
MediumOne
2
@MediumOne, para carregamento de recursos, substitua WebViewClient.shouldInterceptRequest(android.webkit.WebView view, java.lang.String url)a API Check-out para mais informações.
yorkw
3
@yorkw: Este método captura todos os urls de solicitação de recursos. Mas não há como adicionar cabeçalhos a essas solicitações. Meu objetivo é adicionar cabeçalhos HTTP personalizados a todas as solicitações. Se isso puder ser alcançado usando o shouldInterceptRequestmétodo, você pode explicar como?
MediumOne 01 de
36

Você precisará interceptar cada solicitação usando WebViewClient.shouldInterceptRequest

Com cada interceptação, você precisará pegar o url, fazer essa solicitação você mesmo e retornar o fluxo de conteúdo:

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

Se sua meta de API mínima for o nível 21 , você pode usar o novo shouldInterceptRequest, que fornece informações de solicitação adicionais (como cabeçalhos) em vez de apenas o URL.

Martin Konecny
fonte
2
Apenas no caso de alguém encontrar a mesma situação que eu ao usar este truque. (Esta é uma boa, de qualquer maneira.) Aqui está uma nota para você. Como o cabeçalho do tipo de conteúdo http, que pode conter parâmetros opcionais como charset, não é totalmente compatível com o tipo MIME, o requisito do primeiro parâmetro do construtor WebResourceResponse, de modo que devemos extrair a parte do tipo MIME do tipo de conteúdo por qualquer meio que você pode pensar, como RegExp, para fazê-lo funcionar na maioria dos casos.
James Chen
2
Este evento está obsoleto .. use em public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)vez disso encontre mais aqui
Hirdesh Vishwdewa
3
@HirdeshVishwdewa - veja a última frase.
Martin Konecny
2
Você pode pular o seu próprio carregamento retornando os resultados do método shouldInterceptRequest da superclasse com seu webview e solicitação modificada como parâmetros. Isso é particularmente útil em cenários em que você está acionando com base no URL, não está alterando no recarregamento e seria executado em um loop infinito. Muito obrigado pelo novo exemplo de solicitação. Maneiras Java de lidar com as coisas são altamente contra-intuitivas para mim.
Erik Reppen de
4
HttpClient não pode ser usado com compileSdk 23 e superior,
Tamás Kozmér
30

Talvez minha resposta seja bem tarde, mas cobre a API abaixo e acima 21 nível.

Para adicionar cabeçalhos, devemos interceptar cada solicitação e criar um novo com os cabeçalhos necessários.

Portanto, precisamos substituir o método shouldInterceptRequest chamado em ambos os casos: 1. para API até o nível 21; 2. para API de nível 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;
            }

        }
   });

Se o tipo de resposta deve ser processado, você pode alterar

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

para

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

e adicionar 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
fonte
1
Desculpe responder a este post antigo, mas com este código meu aplicativo tenta baixar o arquivo (e não consegue) em vez de carregar a página.
Giacomo M
Muito obrigado!
AlexS
21

Como mencionado antes, você pode fazer isso:

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

Eu testei isso e continuei com um controlador MVC que estendi o atributo Authorize para inspecionar o cabeçalho e o cabeçalho está lá.

Leeroya
fonte
Terei que resolver isso novamente, pois quando foi escrito e postado funcionava com o Kit-Kat. Eu não tentei com Lolly Pop.
leeroya de
Não está funcionando para mim no Jelly bean ou Marshmallow ... não muda nada nos cabeçalhos
Erik Verboom
6
Isso não faz o que OP está pedindo. Ele deseja adicionar cabeçalhos a todas as solicitações feitas pelo webview. Isso adiciona o cabeçalho personalizado apenas à primeira solicitação
NinjaCoder
Não é isso que OP está perguntando
Akshay,
Sei que isso não responde ao que o OP estava procurando, mas era exatamente o que eu queria, ou seja, adicionar um cabeçalho extra a uma URL WebViewIntent. Obrigado, independentemente!
Joshua Pinter
9

Isso funciona para mim:

  1. Primeiro você precisa criar o método, que retornará os cabeçalhos que deseja adicionar à solicitação:

    private Map<String, String> getCustomHeaders()
    {
        Map<String, String> headers = new HashMap<>();
        headers.put("YOURHEADER", "VALUE");
        return headers;
    }
  2. Em segundo lugar, você precisa criar 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. Adicione WebViewClient ao seu WebView:

    webView.setWebViewClient(getWebViewClient());

Espero que isto ajude.

eltray
fonte
1
Parece bom, mas isso adiciona um cabeçalho ou substitui os cabeçalhos?
Ivo Renkema de
@IvoRenkema loadUrl(String url, Map<String, String> additionalHttpHeaders) significa adicionar cabeçalhos de adições
AbhinayMe
4

Você deve ser capaz de controlar todos os seus cabeçalhos pulando loadUrl e escrevendo sua própria loadPage usando HttpURLConnection do Java. Em seguida, use o loadData do webview para exibir a resposta.

Não há acesso aos cabeçalhos que o Google fornece. Eles estão em uma chamada JNI, profundamente na origem do WebView.

R Earle Harris
fonte
1
Você tem alguma referência ao que disse em sua resposta ... será útil para outros se você fornecer referências de implementação com suas respostas.
Hirdesh Vishwdewa,
1

Aqui está uma implementação 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
    }
}

Observe que isso não funciona para solicitações POST porque WebResourceRequest não fornece dados POST. Há uma biblioteca Request Data - WebViewClient que usa uma solução alternativa de injeção de JavaScript para interceptar dados POST.

Miloš Černilovský
fonte
0

Isso funcionou para mim. Crie WebViewClient como este abaixo e defina o webclient para o seu webview. Eu tive que usar webview.loadDataWithBaseURL como meus urls (em meu conteúdo) não tinha o baseurl, mas apenas urls relativos. Você obterá a url corretamente apenas quando houver uma baseurl definida 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
fonte
para mim funciona: .post (reqbody) onde RequestBody reqbody = RequestBody.create (null, "");
Karoly
-2

Você pode usar isto:

@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
fonte
2
Isso continua recarregando a url, não é?
Onheiron
-3

Eu me deparei com o mesmo problema e resolvi.

Como dito antes, você precisa criar seu WebViewClient personalizado e substituir o método shouldInterceptRequest.

WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)

Esse método deve emitir um webView.loadUrl enquanto retorna um WebResourceResponse "vazio".

Algo assim:

@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
fonte
Chamar view.loadUrl a partir deste método parece travar o aplicativo
willcwf
@willcwf você tem um exemplo desse travamento?
Francesco
@Francesco, meu aplicativo também falha
Giacomo M
Todos estão rejeitando isso, dizendo que o crash não está ajudando. Por favor, seja mais específico, escreva algumas informações sobre o erro.
Francesco
-14

Usa isto:

webView.getSettings().setUserAgentString("User-Agent");
Satish
fonte
11
isso não responde à pergunta
younes0
este não é o mesmo que cabeçalho de autorização
Vlad