Ajouter des en-têtes personnalisés aux demandes de ressources WebView - Android

95

Je dois ajouter des en-têtes personnalisés à CHAQUE demande provenant de WebView. Je sais qu'il loadURLa le paramètre pour extraHeaders, mais ceux-ci ne sont appliqués qu'à la demande initiale. Toutes les demandes suivantes ne contiennent pas les en-têtes. J'ai regardé tous les remplacements dans WebViewClient, mais rien ne permet d'ajouter des en-têtes aux demandes de ressources - onLoadResource(WebView view, String url). Toute aide serait merveilleuse.

Merci, Ray

Rayon
la source
2
@MediumOne: Ce n'est pas un bug, c'est une fonctionnalité que vous considérez comme manquante. Je n'ai connaissance de rien dans la spécification HTTP qui stipule que les requêtes HTTP suivantes doivent refléter les en-têtes arbitraires des requêtes HTTP précédentes.
CommonsWare
1
@CommonsWare: Le mot «subséquent» est ici trompeur. Lorsque je tape " facebook.com " sur n'importe quel navigateur pour charger la page d'accueil facebook.com, il y a plusieurs "demandes de ressources" pour charger les fichiers CSS, js et img. Vous pouvez vérifier cela dans Chrome à l'aide de la fonction F12 (onglet Réseau). Pour ces demandes, la vue Web n'ajoute pas d'en-têtes. J'ai essayé d'ajouter des en-têtes personnalisés aux requêtes FireFox en utilisant le plug-in addons.mozilla.org/en-us/firefox/addon/modify-headers . Ce plugin était capable d'ajouter des en-têtes à toutes ces "demandes de ressources". Je pense que WebView devrait faire de même.
MediumOne
1
@MediumOne: "Je pense que WebView devrait faire de même" - qui est une fonctionnalité que vous considérez comme manquante. Notez que vous avez dû recourir à un plugin pour que Firefox le fasse. Je ne dis pas que la fonctionnalité que vous proposez est une mauvaise idée. Je dis que le qualifier de bogue n'aidera probablement pas votre cause à ajouter cette fonctionnalité proposée à Android.
CommonsWare
1
@CommonsWare: Supposons que j'utilise une WebView pour créer un navigateur qui peut être configuré pour fonctionner avec un proxy HTTP personnalisé. Ce proxy utilise une authentification personnalisée où les demandes qui lui sont adressées doivent avoir un en-tête personnalisé. Désormais, webview fournit une API pour définir des en-têtes personnalisés, mais en interne, il ne définit pas l'en-tête de toutes les demandes de ressources qu'il génère. Il n'y a pas d'API supplémentaire pour définir des en-têtes pour ces demandes. Ainsi, toute fonctionnalité qui repose sur l'ajout d'en-têtes personnalisés aux requêtes WebView échoue.
MediumOne
1
@CommonsWare - Je revisite cette conversation après 4 ans. Je suis d'accord maintenant - cela ne devrait pas être un bug. Il n'y a rien dans la spécification HTTP qui indique que les requêtes suivantes doivent envoyer les mêmes en-têtes. :)
MediumOne

Réponses:

81

Essayer

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

Pour ajouter des en-têtes aux demandes de chargement de ressources, créez un WebViewClient personnalisé et remplacez:

API 24+:
WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)
or
WebResourceResponse shouldInterceptRequest(WebView view, String url)
peceps
la source
9
Désolé, mais cela ne fonctionne pas. Il applique uniquement les en-têtes aux requêtes initiales. Les en-têtes ne sont PAS ajoutés aux demandes de ressources. D'autres idées? Merci.
Ray
19
Oui, remplacez WebClient.shouldOverrideUrlLoading comme ceci: public boolean shouldOverrideUrlLoading (vue WebView, chaîne url) {view.loadUrl (url, extraHeaders); retourne vrai; }
peceps
5
@peceps - le rappel 'shouldOverrideUrlLoading' n'est pas appelé pendant le chargement des ressources. Par exemple, lorsque nous essayons view.loadUrl("http://www.facebook.com", extraHeaders), il y a plusieurs demandes de ressources telles 'http://static.fb.com/images/logo.png'que etc qui sont envoyées à partir de la vue Web. Pour ces demandes, les en-têtes supplémentaires ne sont pas ajoutés. Et shouldOverrideUrlLoading n'est pas appelé pendant de telles demandes de ressources. Le rappel «OnLoadResource» est appelé, mais il n'y a aucun moyen de définir des en-têtes à ce stade.
MediumOne
2
@MediumOne, pour le chargement des ressources, remplacez l' APIWebViewClient.shouldInterceptRequest(android.webkit.WebView view, java.lang.String url) Check out pour plus.
yorkw
3
@yorkw: cette méthode capture toutes les URL de demande de ressources. Mais il n'y a aucun moyen d'ajouter des en-têtes à ces demandes. Mon objectif est d'ajouter des en-têtes HTTP personnalisés à toutes les requêtes. Si cela peut être réalisé en utilisant leshouldInterceptRequest méthode, pouvez-vous expliquer comment?
MediumOne
36

Vous devrez intercepter chaque requête à l'aide de WebViewClient.shouldInterceptRequest

À chaque interception, vous devrez prendre l'url, faire cette demande vous-même et renvoyer le flux de contenu:

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 votre cible API minimale est de niveau 21 , vous pouvez utiliser le nouveau shouldInterceptRequest qui vous donne des informations de requête supplémentaires (telles que des en-têtes) au lieu de simplement l'URL.

Martin Konecny
la source
2
Juste au cas où quelqu'un rencontre la même situation que moi en utilisant cette astuce. (C'est bon de toute façon.) Voici une note pour vous. Étant donné que l'en-tête de type de contenu http, qui peut contenir un paramètre facultatif tel que charset, n'est pas entièrement compatible avec le type MIME, l'exigence du premier paramètre du constructeur WebResourceResponse, de sorte que nous devrions extraire la partie de type MIME du type de contenu par quelque moyen que ce soit, vous peut penser, comme RegExp, pour le faire fonctionner dans la plupart des cas.
James Chen
2
Cet événement est obsolète. Utilisez public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)plutôt pour en savoir plus ici
Hirdesh Vishwdewa
3
@HirdeshVishwdewa - regardez la dernière phrase.
Martin Konecny
2
Vous pouvez ignorer votre propre chargement en renvoyant les résultats de la méthode shouldInterceptRequest de la superclasse avec votre vue Web et votre requête modifiée comme paramètres. Ceci est particulièrement pratique dans les scénarios où vous déclenchez en fonction de l'URL, ne la modifiez pas lors du rechargement et se déroulerait dans une boucle infinie. Merci beaucoup pour le nouvel exemple de demande. Les manières Java de gérer les choses sont très contre-intuitives pour moi.
Erik Reppen
4
HttpClient ne peut pas être utilisé avec compileSdk 23 et supérieur,
Tamás Kozmér
30

Peut-être ma réponse assez tardive, mais elle couvre l'API ci - dessous et au dessus du niveau 21.

Pour ajouter des en-têtes, nous devons intercepter chaque requête et créer une nouvelle avec les en-têtes requis.

Nous devons donc remplacer méthode shouldInterceptRequest appelée dans les deux cas: 1. pour l'API jusqu'au niveau 21; 2. pour le niveau d'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;
            }

        }
   });

Si le type de réponse doit être traité, vous pouvez changer

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

et ajouter une méthode

        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
la source
1
Désolé de répondre à cet ancien message, mais avec ce code, mon application essaie de télécharger le fichier (et échoue) au lieu de charger la page.
Giacomo M
Merci beaucoup!
AlexS
21

Comme mentionné précédemment, vous pouvez le faire:

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

J'ai testé cela et avec un contrôleur MVC que j'ai étendu l'attribut d'autorisation pour inspecter l'en-tête et l'en-tête est là.

Leeroya
la source
Je devrai aborder cette question à nouveau, car lorsqu'elle a été écrite et publiée, elle fonctionnait avec le Kit-Kat. Je n'ai pas essayé avec Lolly Pop.
leeroya
Ne pas travailler pour moi sur Jelly Bean ou Marshmallow ... ne change rien dans les en-têtes
Erik Verboom
6
Cela ne fait pas ce que demande OP. Il souhaite ajouter des en-têtes à toutes les demandes effectuées par la vue Web. Cela ajoute un en-tête personnalisé à la première demande uniquement
NinjaCoder
Ce n'est pas ce que demande OP
Akshay
Je sais que cela ne répond pas à ce que OP recherchait mais c'était exactement ce que je voulais, c'est-à-dire ajouter un en-tête supplémentaire à une URL WebViewIntent. Merci, peu importe!
Joshua Pinter
9

Cela fonctionne pour moi:

  1. Vous devez d'abord créer une méthode, qui retournera vos en-têtes que vous souhaitez ajouter à la requête:

    private Map<String, String> getCustomHeaders()
    {
        Map<String, String> headers = new HashMap<>();
        headers.put("YOURHEADER", "VALUE");
        return headers;
    }
  2. Deuxièmement, vous devez créer 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. Ajoutez WebViewClient à votre WebView:

    webView.setWebViewClient(getWebViewClient());

J'espère que cela t'aides.

eltray
la source
1
Cela a l'air bien, mais cela ajoute-t-il un en-tête ou remplace -t-il les en-têtes?
Ivo Renkema
@IvoRenkema loadUrl(String url, Map<String, String> additionalHttpHeaders) signifie ajouter des en
AbhinayMe
4

Vous devriez pouvoir contrôler tous vos en-têtes en ignorant loadUrl et en écrivant votre propre loadPage à l'aide de HttpURLConnection de Java. Utilisez ensuite loadData de la vue Web pour afficher la réponse.

Il n'y a pas d'accès aux en-têtes fournis par Google. Ils sont dans un appel JNI, au plus profond de la source WebView.

R Earle Harris
la source
1
Avez-vous des références à ce que vous dites dans votre réponse? Il sera utile pour les autres si vous donnez des références de mise en œuvre avec vos réponses.
Hirdesh Vishwdewa
1

Voici une implémentation utilisant 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
    }
}

Notez que cela ne fonctionne pas pour les requêtes POST car WebResourceRequest ne fournit pas de données POST. Il existe une bibliothèque Request Data - WebViewClient qui utilise une solution de contournement par injection JavaScript pour intercepter les données POST.

Miloš Černilovský
la source
0

Cela a fonctionné pour moi. Créez WebViewClient comme ci-dessous et définissez le client Web sur votre vue Web. J'ai dû utiliser webview.loadDataWithBaseURL car mes urls (dans mon contenu) n'avaient pas le baseurl mais seulement des URL relatives. Vous n'obtiendrez correctement l'URL que s'il y a un baseurl défini à l'aide de 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
la source
pour moi ça marche: .post (reqbody) où RequestBody reqbody = RequestBody.create (null, "");
Karoly
-2

Vous pouvez utiliser ceci:

@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
la source
2
Cela ne cesse de recharger l'url, n'est-ce pas?
Onheiron
-3

Je suis tombé sur le même problème et je l'ai résolu.

Comme indiqué précédemment, vous devez créer votre WebViewClient personnalisé et remplacer la méthode shouldInterceptRequest.

WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)

Cette méthode doit émettre un webView.loadUrl tout en renvoyant un WebResourceResponse «vide».

Quelque chose comme ça:

@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
la source
L'appel de view.loadUrl à partir de cette méthode semble planter l'application
willcwf
@willcwf avez-vous un exemple de ce crash?
Francesco
@Francesco mon application plante également
Giacomo M
Trop tout le monde vote contre cela, en disant que le crash n'aide pas. Veuillez être plus précis, écrivez quelques informations d'erreur.
Francesco
-14

Utilisez ceci:

webView.getSettings().setUserAgentString("User-Agent");
Satish
la source
11
cela ne répond pas à la question
younes0
ce n'est pas la même chose que l'en-tête d'autorisation
Vlad le