Dodaj niestandardowe nagłówki do żądań zasobów WebView - Android

97

Muszę dodać niestandardowe nagłówki do KAŻDEGO żądania pochodzącego z WebView. Wiem, że loadURLma parametr for extraHeaders, ale są one stosowane tylko do początkowego żądania. Wszystkie kolejne żądania nie zawierają nagłówków. Przyjrzałem się wszystkim zastąpieniom w programie WebViewClient, ale nic nie pozwala na dodawanie nagłówków do żądań zasobów - onLoadResource(WebView view, String url). Każda pomoc byłaby cudowna.

Dzięki, Ray

Promień
źródło
2
@MediumOne: To nie jest błąd, ponieważ jest to funkcja, której brakuje. Nie znam niczego w specyfikacji HTTP, które mówi, że kolejne żądania HTTP muszą odzwierciedlać dowolne nagłówki z poprzednich żądań HTTP.
CommonsWare
1
@CommonsWare: Słowo „kolejny” jest tutaj mylące. Kiedy wpisujęfacebook.com ” w dowolnej przeglądarce, aby załadować stronę główną facebook.com, pojawia się kilka „żądań zasobów”, które umożliwiają załadowanie plików CSS, js i img. Możesz to sprawdzić w Chrome za pomocą funkcji F12 (karta Sieć). W przypadku tych żądań widok sieciowy nie dodaje nagłówków. Próbowałem dodać niestandardowe nagłówki do żądań FireFox za pomocą wtyczki addons.mozilla.org/en-us/firefox/addon/modify-headers . Ta wtyczka była w stanie dodawać nagłówki do wszystkich takich obsługujących „żądań zasobów”. Myślę, że WebView powinien zrobić to samo.
MediumOne
1
@MediumOne: „Myślę, że WebView powinien robić to samo” - jest to funkcja, której Twoim zdaniem brakuje. Zauważ, że musiałeś skorzystać z wtyczki, aby Firefox to zrobił. Nie twierdzę, że proponowana funkcja to zły pomysł. Mówię, że określenie tego jako błędu prawdopodobnie nie pomoże twojej sprawie w dodaniu tej proponowanej funkcji do Androida.
CommonsWare
1
@CommonsWare: Załóżmy, że używam WebView do tworzenia przeglądarki, którą można skonfigurować do pracy z niestandardowym serwerem proxy HTTP. Ten serwer proxy używa niestandardowego uwierzytelniania, w którym żądania do niego powinny mieć niestandardowy nagłówek. Teraz webview zapewnia interfejs API do ustawiania niestandardowych nagłówków, ale wewnętrznie nie ustawia nagłówka dla wszystkich generowanych żądań zasobów. Nie ma też żadnych dodatkowych interfejsów API do ustawiania nagłówków dla tych żądań. Tak więc każda funkcja, która polega na dodawaniu niestandardowych nagłówków do żądań WebView, kończy się niepowodzeniem.
MediumOne
1
@CommonsWare - wracam do tej rozmowy po 4 latach. Zgadzam się teraz - to nie powinno być błąd. W specyfikacji HTTP nie ma nic, co mówi, że kolejne żądania powinny wysyłać te same nagłówki. :)
MediumOne

Odpowiedzi:

83

Próbować

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

Aby dodać nagłówki do żądań ładowania zasobów, utwórz niestandardowy WebViewClient i zastąp:

API 24+:
WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)
or
WebResourceResponse shouldInterceptRequest(WebView view, String url)
peceps
źródło
9
Przepraszamy, ale to nie działa. Stosuje tylko nagłówki również początkowe żądania. Nagłówki NIE są dodawane do żądań zasobów. Inne pomysły? Dzięki.
Ray
19
Tak, zastąp WebClient.shouldOverrideUrlLoading w następujący sposób: public boolean shouldOverrideUrlLoading (widok WebView, String url) {view.loadUrl (url, extraHeaders); powrót prawda; }
peceps
5
@peceps - wywołanie zwrotne „shouldOverrideUrlLoading” nie jest wywoływane podczas ładowania zasobu. Na przykład, kiedy próbujemy view.loadUrl("http://www.facebook.com", extraHeaders), jest wiele żądań zasobów, takich jak 'http://static.fb.com/images/logo.png'itp., Które są wysyłane z widoku internetowego. W przypadku tych żądań dodatkowe nagłówki nie są dodawane. I shouldOverrideUrlLoading nie jest wywoływana podczas takich żądań zasobów. Wywoływana jest funkcja zwrotna „OnLoadResource”, ale w tym momencie nie ma możliwości ustawienia nagłówków.
MediumOne
2
@MediumOne, aby załadować zasoby, zastąp APIWebViewClient.shouldInterceptRequest(android.webkit.WebView view, java.lang.String url) Check out, aby uzyskać więcej.
yorkw
3
@yorkw: ta metoda przechwytuje wszystkie adresy URL żądań zasobów. Ale nie ma możliwości dodania nagłówków do tych żądań. Moim celem jest dodanie niestandardowych nagłówków HTTP do wszystkich żądań. Jeśli można to osiągnąć za pomocą tej shouldInterceptRequestmetody, czy możesz wyjaśnić, w jaki sposób?
MediumOne
36

Będziesz musiał przechwycić każde żądanie za pomocą WebViewClient.shouldInterceptRequest

Przy każdym przechwyceniu będziesz musiał pobrać adres URL, samodzielnie wykonać to żądanie i zwrócić strumień treści:

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

Jeśli minimalnym celem API jest poziom 21 , możesz użyć nowego shouldInterceptRequest, który zapewnia dodatkowe informacje o żądaniu (takie jak nagłówki) zamiast tylko adresu URL.

Martin Konecny
źródło
2
Na wypadek, gdyby ktoś napotkał taką samą sytuację, jak ja podczas używania tej sztuczki. (To i tak jest dobre.) Oto uwaga dla ciebie. Ponieważ nagłówek typu content-type http, który może zawierać parametr opcjonalny, taki jak charset, nie jest w pełni zgodny z typem MIME, wymóg pierwszego parametru konstruktora WebResourceResponse, abyśmy w jakikolwiek sposób wyodrębnili część typu MIME z typu treści można wymyślić, takie jak RegExp, aby działało w większości przypadków.
James Chen,
2
To wydarzenie jest przestarzałe ... użyj public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)zamiast tego, znajdź więcej tutaj
Hirdesh Vishwdewa
3
@HirdeshVishwdewa - spójrz na ostatnie zdanie.
Martin Konecny
2
Możesz pominąć wykonywanie własnego ładowania, zwracając wyniki metody shouldInterceptRequest nadklasy z Twoim widokiem sieciowym i zmodyfikowanym żądaniem jako parametrami. Jest to szczególnie przydatne w scenariuszach, w których wyzwalasz na podstawie adresu URL, nie zmieniasz go przy ponownym załadowaniu i możesz uruchomić nieskończoną pętlę. Wielkie dzięki za nowy przykład żądania. Metody obsługi w języku Java są dla mnie wysoce sprzeczne z intuicją.
Erik Reppen
4
HttpClient nie może być używany z compileSdk 23 i nowszymi wersjami,
Tamás Kozmér
30

Może moja odpowiedź dość późno, ale obejmuje API poniżej i powyżej 21 poziomu.

Aby dodać nagłówki, powinniśmy przechwycić każde żądanie i utworzyć nowe z wymaganymi nagłówkami.

Musimy więc nadpisać metodę shouldInterceptRequest wywołaną w obu przypadkach: 1. dla API do poziomu 21; 2. dla poziomu 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;
            }

        }
   });

Jeśli typ odpowiedzi powinien zostać przetworzony, możesz go zmienić

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

do

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

i dodaj metodę

        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
źródło
1
Przepraszam, że odpowiadam na ten stary post, ale za pomocą tego kodu moja aplikacja próbuje pobrać plik (i kończy się to niepowodzeniem) zamiast wczytywać stronę.
Giacomo M
czy można to wykorzystać do żądań pocztowych?
mrid
21

Jak wspomniano wcześniej, możesz to zrobić:

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

Przetestowałem to i dalej za pomocą kontrolera MVC, który rozszerzyłem atrybut autoryzacji, aby sprawdzić nagłówek i nagłówek tam jest.

leeroya
źródło
Będę musiał zająć się tym ponownie, ponieważ kiedy został napisany i opublikowany, działał z Kit-Kat. Nie próbowałem z Lolly Pop.
leeroya
Nie działa na mnie na Jelly Bean lub Marshmallow ... niczego nie zmienia w nagłówkach
Erik Verboom
6
To nie robi tego, o co prosi OP. Chce dodać nagłówki do wszystkich żądań wysyłanych przez przeglądarkę internetową. Spowoduje to dodanie niestandardowego nagłówka tylko do pierwszego żądania
NinjaCoder
Nie o to pyta OP
Akshay,
Wiem, że to nie odpowiada, czego szukałem OP, ale właśnie tego chciałem, tj. Dodania dodatkowego nagłówka do adresu URL WebViewIntent. Dzięki, nieważne!
Joshua Pinter
9

To działa dla mnie:

  1. Najpierw musisz stworzyć metodę, która zwróci twoje nagłówki, które chcesz dodać do żądania:

    private Map<String, String> getCustomHeaders()
    {
        Map<String, String> headers = new HashMap<>();
        headers.put("YOURHEADER", "VALUE");
        return headers;
    }
    
  2. Po drugie, musisz utworzyć 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. Dodaj WebViewClient do swojego WebView:

    webView.setWebViewClient(getWebViewClient());
    

Mam nadzieję że to pomoże.

eltray
źródło
1
Wygląda dobrze, ale czy to dodaje nagłówek, czy zastępuje nagłówki?
Ivo Renkema
@IvoRenkema loadUrl(String url, Map<String, String> additionalHttpHeaders) oznacza dodawanie dodatkowych nagłówków
AbhinayMe
4

Powinieneś być w stanie kontrolować wszystkie swoje nagłówki, pomijając loadUrl i pisząc własną loadPage przy użyciu HttpURLConnection Java. Następnie użyj loadData webview, aby wyświetlić odpowiedź.

Nie ma dostępu do nagłówków udostępnianych przez Google. Są w wywołaniu JNI, głęboko w źródle WebView.

R Earle Harris
źródło
1
Czy masz jakieś odniesienie do tego, co mówisz w swojej odpowiedzi? Jeśli wraz z odpowiedziami podasz odniesienia do implementacji, przyda się innym.
Hirdesh Vishwdewa
1

Oto implementacja wykorzystująca 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
    }
}

Zauważ, że to nie działa w przypadku żądań POST, ponieważ WebResourceRequest nie dostarcza danych POST. Istnieje biblioteka danych żądania - WebViewClient, która wykorzystuje obejście iniekcji JavaScript do przechwytywania danych POST.

Miloš Černilovský
źródło
0

To zadziałało dla mnie. Utwórz WebViewClient w ten sposób poniżej i ustaw klienta internetowego na swój widok sieciowy. Musiałem użyć webview.loadDataWithBaseURL, ponieważ moje adresy URL (w mojej treści) nie miały adresu podstawowego, ale tylko względne adresy URL. Prawidłowy adres URL zostanie uzyskany tylko wtedy, gdy za pomocą loadDataWithBaseURL ustawiono podstawowy adresurl.

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
źródło
dla mnie to działa: .post (reqbody) gdzie RequestBody reqbody = RequestBody.create (null, "");
Karoly
-2

Możesz użyć tego:

@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
źródło
2
To po prostu ciągle ładuje adres URL, prawda?
Onheiron
-3

Przeszedłem przez ten sam problem i rozwiązałem.

Jak wspomniano wcześniej, musisz utworzyć niestandardowy WebViewClient i zastąpić metodę shouldInterceptRequest.

WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)

Ta metoda powinna wywołać webView.loadUrl podczas zwracania „pustej” odpowiedzi WebResourceResponse.

Coś takiego:

@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
źródło
Wywołanie view.loadUrl z tej metody wydaje się
powodować
@willcwf czy masz przykład tej awarii?
Francesco
@Francesco moja aplikacja również się zawiesza
Giacomo M
Zbyt wszyscy to odrzucają, mówiąc, że awaria nie pomaga. Podaj więcej szczegółów i napisz informacje o błędzie.
Francesco
-14

Użyj tego:

webView.getSettings().setUserAgentString("User-Agent");
Satish
źródło
11
to nie odpowiada na pytanie
younes0
to nie to samo, co nagłówek autoryzacji
Vlad