Aggiungi intestazioni personalizzate alle richieste di risorse WebView - Android


95

Devo aggiungere intestazioni personalizzate a OGNI richiesta proveniente da WebView. So che loadURLha il parametro per extraHeaders, ma quelli vengono applicati solo alla richiesta iniziale. Tutte le richieste successive non contengono le intestazioni. Ho esaminato tutte le sostituzioni WebViewClient, ma nulla consente di aggiungere intestazioni alle richieste di risorse - onLoadResource(WebView view, String url). Qualsiasi aiuto sarebbe meraviglioso.

Grazie, Ray


2
@MediumOne: questo non è un bug tanto quanto è una caratteristica che ritieni manchi. Non sono a conoscenza di nulla nella specifica HTTP che dica che le successive richieste HTTP devono rispecchiare intestazioni arbitrarie da precedenti richieste HTTP.
CommonsWare

1
@CommonsWare: la parola "successivo" qui è fuorviante. Quando digito " facebook.com " su qualsiasi browser per caricare la homepage di facebook.com, ci sono diverse "richieste di risorse" di supporto per caricare i file CSS, js e img. Puoi verificarlo in Chrome utilizzando la funzione F12 (scheda Rete). Per queste richieste, la visualizzazione web non aggiunge intestazioni. Ho provato ad aggiungere intestazioni personalizzate alle richieste di FireFox utilizzando il plug-in addons.mozilla.org/en-us/firefox/addon/modify-headers . Questo plugin è stato in grado di aggiungere intestazioni a tutte queste "richieste di risorse" di supporto. Penso che WebView dovrebbe fare lo stesso.
Medium:

1
@MediumOne: "Penso che WebView dovrebbe fare lo stesso" - che è una caratteristica che ritieni manchi. Nota che dovevi ricorrere a un plugin per farlo fare a Firefox. Non sto dicendo che la funzione proposta sia una cattiva idea. Sto dicendo che è improbabile che caratterizzarlo come un bug aiuti la tua causa a ottenere questa funzionalità proposta aggiunta ad Android.
CommonsWare

1
@CommonsWare: supponiamo che io stia utilizzando un WebView per creare un browser che può essere configurato per funzionare con un proxy HTTP personalizzato. Questo proxy utilizza l'autenticazione personalizzata in cui le richieste devono avere un'intestazione personalizzata. Ora, webview fornisce un'API per impostare intestazioni personalizzate, ma internamente non imposta l'intestazione per tutte le richieste di risorse che genera. Non ci sono API aggiuntive per impostare anche le intestazioni per queste richieste. Pertanto, qualsiasi funzionalità che si basa sull'aggiunta di intestazioni personalizzate alle richieste WebView fallisce.
Medium: il

1
@CommonsWare - Sto rivisitando questa conversazione dopo 4 anni. Sono d'accordo ora - questo non dovrebbe essere un bug. Non c'è niente nella specifica HTTP che dica che le richieste successive dovrebbero inviare le stesse intestazioni. :)
MediumOne

Risposte:


81

Provare

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

Per aggiungere intestazioni alle richieste di caricamento delle risorse, crea un WebViewClient personalizzato e sovrascrivi:

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

9
Spiacenti, ma non funziona. Applica solo le intestazioni anche alle richieste iniziali. Le intestazioni NON vengono aggiunte alle richieste di risorse. Altre idee? Grazie.
Ray

19
Sì, sovrascrivi WebClient.shouldOverrideUrlLoading in questo modo: public boolean shouldOverrideUrlLoading (visualizzazione WebView, URL stringa) {view.loadUrl (url, extraHeaders); restituire vero; }
peceps

5
@peceps - il callback "shouldOverrideUrlLoading" non viene chiamato durante il caricamento delle risorse. Ad esempio, quando proviamo view.loadUrl("http://www.facebook.com", extraHeaders), ci sono più richieste di risorse come 'http://static.fb.com/images/logo.png'ecc. Che vengono inviate da webiew. Per queste richieste, le intestazioni extra non vengono aggiunte. E shouldOverrideUrlLoading non viene chiamato durante tali richieste di risorse. Viene chiamato il callback "OnLoadResource", ma a questo punto non è possibile impostare le intestazioni.
Medium:

2
@MediumOne, per il caricamento delle risorse, sovrascrivi WebViewClient.shouldInterceptRequest(android.webkit.WebView view, java.lang.String url)l' API Check out per ulteriori informazioni.
yorkw

3
@yorkw: questo metodo acquisisce tutti gli URL di richiesta delle risorse. Ma non è possibile aggiungere intestazioni a queste richieste. Il mio obiettivo è aggiungere intestazioni HTTP personalizzate a tutte le richieste. Se questo può essere ottenuto utilizzando il shouldInterceptRequestmetodo, puoi spiegare come?
Medium:

36

Dovrai intercettare ogni richiesta utilizzando WebViewClient.shouldInterceptRequest

Ad ogni intercettazione, dovrai prendere l'URL, fare tu stesso questa richiesta e restituire il flusso di contenuti:

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 il tuo obiettivo API minimo è il livello 21 , puoi utilizzare il nuovo shouldInterceptRequest che fornisce ulteriori informazioni sulla richiesta (come le intestazioni) invece del solo URL.


2
Nel caso in cui qualcuno incontri la stessa situazione che ho durante l'utilizzo di questo trucco. (Questo è comunque buono.) Ecco una nota per te. Poiché l'intestazione del tipo di contenuto http, che può contenere un parametro opzionale come il set di caratteri, non è completamente compatibile con il tipo MIME, il requisito del primo parametro del costruttore WebResourceResponse, quindi dovremmo estrarre la parte del tipo MIME dal tipo di contenuto con qualsiasi mezzo tu può pensare, come RegExp, per farlo funzionare nella maggior parte dei casi.
James Chen

2
Questo evento è deprecato .. usa public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)invece trova altro qui
Hirdesh Vishwdewa

3
@HirdeshVishwdewa - guarda l'ultima frase.
Martin Konecny

2
Puoi saltare il caricamento personale restituendo i risultati del metodo shouldInterceptRequest della superclasse con la tua visualizzazione web e la richiesta modificata come parametri. Ciò è particolarmente utile negli scenari in cui si esegue l'attivazione in base all'URL, non lo si modifica durante il ricaricamento e si verifica un ciclo infinito. Grazie mille per il nuovo esempio di richiesta. I modi Java di gestire le cose sono altamente controintuitivi per me.
Erik Reppen

4
HttpClient non può essere utilizzato con compileSdk 23 e versioni successive,
Tamás Kozmér

30

Forse la mia risposta è abbastanza tardi, ma copre API sotto e sopra il livello 21.

Per aggiungere le intestazioni dovremmo intercettare ogni richiesta e crearne una nuova con le intestazioni obbligatorie.

Quindi dobbiamo sovrascrivere il metodo shouldInterceptRequest chiamato in entrambi i casi: 1. per API fino al livello 21; 2. per il livello 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;
            }

        }
   });

Se il tipo di risposta deve essere elaborato, è possibile modificare

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

per

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

e aggiungi metodo

        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
Mi dispiace rispondere a questo vecchio post, ma con questo codice la mia app prova a scaricare il file (e non riesce) invece di caricare la pagina.
Giacomo M

Grazie mille!
AlexS

21

Come accennato prima, puoi farlo:

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

Ho provato questo e con un controller MVC che ho esteso l'attributo di autorizzazione per ispezionare l'intestazione e l'intestazione è lì.


Dovrò affrontarlo di nuovo poiché quando è stato scritto e pubblicato funzionava con il Kit-Kat. Non ho provato con Lolly Pop.
leeroya

Non funziona per me su Jelly bean o Marshmallow ... non cambia nulla nelle intestazioni
Erik Verboom

6
Questo non fa quello che chiede OP. Vuole aggiungere intestazioni a tutte le richieste fatte dal webview. Questo aggiunge un'intestazione personalizzata solo alla prima richiesta
NinjaCoder

Questo non è ciò che chiede OP
Akshay

So che questo non risponde a ciò che l'OP stava cercando, ma questo era esattamente quello che volevo, cioè aggiungere un'intestazione extra a un URL WebViewIntent. Grazie, a prescindere!
Joshua Pinter

9

Questo funziona per me:

  1. Per prima cosa devi creare il metodo, che restituirà le intestazioni che desideri aggiungere alla richiesta:

    private Map<String, String> getCustomHeaders()
    {
        Map<String, String> headers = new HashMap<>();
        headers.put("YOURHEADER", "VALUE");
        return headers;
    }
  2. Secondo è necessario creare 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. Aggiungi WebViewClient al tuo WebView:

    webView.setWebViewClient(getWebViewClient());

Spero che questo ti aiuti.


1
Sembra buono, ma questo aggiunge un'intestazione o sostituisce le intestazioni?
Ivo Renkema

@IvoRenkema loadUrl(String url, Map<String, String> additionalHttpHeaders) significa aggiungere intestazioni di aggiunte
AbhinayMe

4

Dovresti essere in grado di controllare tutte le tue intestazioni saltando loadUrl e scrivendo la tua loadPage usando HttpURLConnection di Java. Quindi utilizzare loadData della visualizzazione Web per visualizzare la risposta.

Non è possibile accedere alle intestazioni fornite da Google. Sono impegnati in una chiamata JNI, nel profondo del sorgente WebView.


1
Hai qualche riferimento a ciò che dici nella tua risposta ... sarà utile per gli altri se fornisci riferimenti all'implementazione con le tue risposte.
Hirdesh Vishwdewa

1

Ecco un'implementazione che utilizza 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
    }
}

Notare che questo non funziona per le richieste POST perché WebResourceRequest non fornisce i dati POST. È disponibile una libreria di dati di richiesta - WebViewClient che utilizza una soluzione alternativa per l'iniezione di JavaScript per l'intercettazione dei dati POST.


0

Questo ha funzionato per me. Crea WebViewClient come questo di seguito e imposta il webclient sulla tua visualizzazione web. Ho dovuto usare webview.loadDataWithBaseURL poiché i miei URL (nel mio contenuto) non avevano il baseurl ma solo gli URL relativi. Otterrai l'URL correttamente solo quando è presente un baseurl impostato utilizzando 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;
            }
        }
    };

}

per me funziona: .post (reqbody) dove RequestBody reqbody = RequestBody.create (null, "");
Karoly

-2

Puoi usare questo:

@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
Questo continua a ricaricare l'URL, vero?
Onheiron

-3

Mi sono imbattuto nello stesso problema e l'ho risolto.

Come detto prima, devi creare il tuo WebViewClient personalizzato e sovrascrivere il metodo shouldInterceptRequest.

WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)

Questo metodo dovrebbe emettere un webView.loadUrl restituendo un WebResourceResponse "vuoto".

Qualcosa come questo:

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

La chiamata a view.loadUrl da questo metodo sembra
causare

@willcwf hai un esempio di questo arresto anomalo?
Francesco

@Francesco anche la mia app si blocca
Giacomo M

Troppo tutto sta facendo il downvoting di questo, dicendo che il crash non aiuta. Sii più specifico, scrivi alcune informazioni sull'errore.
Francesco

-14

Usa questo:

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

11
questo non risponde alla domanda
younes0

questo non è lo stesso dell'intestazione di autorizzazione
Vlad
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.