Tambahkan header khusus ke permintaan sumber daya WebView - android

97

Saya perlu menambahkan tajuk khusus ke SETIAP permintaan yang datang dari WebView. Saya tahu loadURLmemiliki parameter untuk extraHeaders, tetapi itu hanya diterapkan pada permintaan awal. Semua permintaan berikutnya tidak mengandung header. Saya telah melihat semua timpaan WebViewClient, tetapi tidak ada yang memungkinkan untuk menambahkan header ke permintaan sumber daya - onLoadResource(WebView view, String url). Bantuan apa pun akan sangat bagus.

Terima kasih, Ray

sinar
sumber
2
@MediumOne: Ini bukan bug, melainkan fitur yang Anda anggap hilang. Saya tidak mengetahui apa pun dalam spesifikasi HTTP yang mengatakan permintaan HTTP berikutnya harus mencerminkan header arbitrer dari permintaan HTTP sebelumnya.
CommonsWare
1
@CommonsWare: Kata "berikutnya" menyesatkan di sini. Saat saya mengetik " facebook.com " di browser apa pun untuk memuat beranda facebook.com, ada beberapa "permintaan sumber daya" pendukung untuk memuat file CSS, js, dan img. Anda dapat memeriksa ini di Chrome menggunakan fitur F12 (tab Jaringan). Untuk permintaan ini, tampilan web tidak menambahkan header. Saya mencoba menambahkan header kustom ke permintaan FireFox menggunakan addons.mozilla.org/en-us/firefox/addon/modify-headers plug-in. Plugin ini dapat menambahkan header ke semua "permintaan sumber daya" yang mendukung. Saya pikir WebView harus melakukan hal yang sama.
MediumOne
1
@MediumOne: "Menurut saya WebView harus melakukan hal yang sama" - yang merupakan fitur yang Anda anggap hilang. Perhatikan bahwa Anda harus menggunakan plugin untuk membuat Firefox melakukan ini. Saya tidak mengatakan bahwa fitur yang Anda usulkan adalah ide yang buruk. Saya mengatakan bahwa mengkarakterisasikannya sebagai bug tidak mungkin membantu tujuan Anda untuk menambahkan fitur yang diusulkan ini ke Android.
CommonsWare
1
@CommonsWare: Misalkan saya menggunakan WebView untuk membangun browser yang dapat dikonfigurasi untuk bekerja dengan proxy HTTP kustom. Proksi ini menggunakan autentikasi khusus di mana permintaan ke sana harus memiliki tajuk khusus. Sekarang, webview menyediakan API untuk menyetel tajuk khusus, tetapi secara internal, ini tidak menyetel tajuk ke semua permintaan sumber daya yang dihasilkannya. Tidak ada API tambahan untuk menyetel tajuk untuk permintaan ini juga. Jadi, fitur apa pun yang mengandalkan penambahan header kustom ke permintaan WebView gagal.
MediumOne
1
@CommonsWare - Saya meninjau kembali percakapan ini setelah 4 tahun. Saya setuju sekarang - ini seharusnya bukan bug. Tidak ada dalam spesifikasi HTTP yang mengatakan permintaan selanjutnya harus mengirimkan header yang sama. :)
MediumOne

Jawaban:

84

Mencoba

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

Untuk menambahkan header ke permintaan pemuatan resource, buat WebViewClient kustom dan timpa:

API 24+:
WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)
or
WebResourceResponse shouldInterceptRequest(WebView view, String url)
peceps
sumber
9
Maaf, tapi ini tidak berhasil. Ini hanya menerapkan header juga pada permintaan awal. Header TIDAK ditambahkan ke permintaan sumber daya. Ide lain? Terima kasih.
Ray
19
Ya, timpa WebClient.shouldOverrideUrlLoading seperti ini: public boolean shouldOverrideUrlLoading (tampilan WebView, String url) {view.loadUrl (url, extraHeaders); kembali benar; }
peceps
5
@peceps - callback 'shouldOverrideUrlLoading' tidak dipanggil selama pemuatan sumber daya. Misalnya, ketika kami mencoba view.loadUrl("http://www.facebook.com", extraHeaders), ada beberapa permintaan sumber daya seperti 'http://static.fb.com/images/logo.png'dll yang dikirim dari webiew. Untuk permintaan ini, header tambahan tidak ditambahkan. Dan shouldOverrideUrlLoading tidak dipanggil selama permintaan sumber daya seperti itu. Callback 'OnLoadResource' dipanggil, tetapi belum ada cara untuk menyetel header pada saat ini.
MediumOne
2
@MediumOne, untuk memuat sumber daya, timpa WebViewClient.shouldInterceptRequest(android.webkit.WebView view, java.lang.String url)Lihat API untuk selengkapnya.
yorkw
3
@yorkw: Metode ini menangkap semua url permintaan sumber daya. Tetapi tidak ada cara untuk menambahkan header ke permintaan ini. Tujuan saya adalah menambahkan header HTTP khusus ke semua permintaan. Jika ini dapat dicapai dengan menggunakan shouldInterceptRequestmetode ini, dapatkah Anda menjelaskan caranya?
MediumOne
36

Anda perlu menghentikan setiap permintaan menggunakan WebViewClient.shouldInterceptRequest

Dengan setiap intersepsi, Anda perlu mengambil url, membuat permintaan ini sendiri, dan mengembalikan aliran konten:

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

Jika target API minimum Anda adalah level 21 , Anda bisa menggunakan shouldInterceptRequest baru yang memberi Anda informasi permintaan tambahan (seperti header), bukan hanya URL.

Martin Konecny
sumber
2
Untuk berjaga-jaga jika seseorang menghadapi situasi yang sama dengan yang saya alami saat menggunakan trik ini. (Lagipula ini bagus.) Ini catatan untukmu. Karena header tipe konten http, yang dapat berisi parameter opsional seperti charset, tidak sepenuhnya kompatibel dengan tipe MIME, persyaratan parameter pertama dari konstruktor WebResourceResponse, sehingga kita harus mengekstrak bagian tipe MIME dari tipe konten dengan cara apa pun Anda dapat memikirkan, seperti RegExp, untuk membuatnya berfungsi pada banyak kasus.
James Chen
2
Acara ini tidak berlaku lagi .. gunakan public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)sebagai gantinya temukan lebih banyak di sini
Hirdesh Vishwdewa
3
@HirdeshVishwdewa - lihat kalimat terakhir.
Martin Konecny
2
Anda bisa melewatkan melakukan pemuatan Anda sendiri dengan mengembalikan hasil metode shouldInterceptRequest kelas super dengan tampilan web Anda dan permintaan yang dimodifikasi sebagai parameter. Ini sangat berguna dalam skenario di mana Anda memicu berdasarkan URL, tidak mengubahnya saat memuat ulang dan akan mengalami putaran tak terbatas. Terima kasih banyak untuk contoh permintaan baru. Cara Java menangani sesuatu sangat berlawanan dengan intuisi saya.
Erik Reppen
4
HttpClient tidak dapat digunakan dengan compileSdk 23 ke atas,
Tamás Kozmér
30

Mungkin respon saya agak terlambat, tapi mencakup API di bawah dan di atas level 21.

Untuk menambahkan tajuk, kita harus menghentikan setiap permintaan dan membuat yang baru dengan tajuk yang diperlukan.

Jadi kita perlu mengganti metode shouldInterceptRequest yang dipanggil dalam kedua kasus: 1. untuk API hingga level 21; 2. untuk API level 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;
            }

        }
   });

Jika jenis respons harus diproses, Anda dapat mengubahnya

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

untuk

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

dan tambahkan metode

        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
sumber
1
Maaf menjawab posting lama ini, tetapi dengan kode ini aplikasi saya mencoba mengunduh file (dan gagal) alih-alih memuat halaman.
Giacomo M
bisakah ini digunakan untuk permintaan posting?
mrid
21

Seperti yang disebutkan sebelumnya, Anda dapat melakukan ini:

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

Saya menguji ini dan melanjutkan dengan MVC Controller yang saya tambahkan Atribut Otorisasi untuk memeriksa header dan header ada di sana.

leeroya
sumber
Saya harus membahas ini lagi seperti ketika itu ditulis dan diposting itu berfungsi dengan Kit-Kat. Saya belum mencoba dengan Lolly Pop.
leeroya
Tidak bekerja untuk saya di Jelly bean atau Marshmallow ... tidak mengubah apa pun di header
Erik Verboom
6
Ini tidak melakukan apa yang diminta OP. Dia ingin menambahkan tajuk ke semua permintaan yang dibuat oleh tampilan web. Ini menambahkan tajuk khusus ke permintaan pertama saja
NinjaCoder
Bukan ini yang diminta OP
Akshay
Saya tahu ini tidak menjawab apa yang dicari OP tetapi ini persis seperti yang saya inginkan, yaitu menambahkan header tambahan ke URL WebViewIntent. Terima kasih, apapun!
Joshua Pinter
9

Ini bekerja untuk saya:

  1. Pertama, Anda perlu membuat metode, yang akan mengembalikan header yang ingin Anda tambahkan ke permintaan:

    private Map<String, String> getCustomHeaders()
    {
        Map<String, String> headers = new HashMap<>();
        headers.put("YOURHEADER", "VALUE");
        return headers;
    }
    
  2. Kedua, Anda perlu membuat 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. Tambahkan WebViewClient ke WebView Anda:

    webView.setWebViewClient(getWebViewClient());
    

Semoga ini membantu.

eltray
sumber
1
Terlihat bagus, tetapi apakah ini menambahkan tajuk, atau apakah ini menggantikan tajuk?
Ivo Renkema
@IvoRenkema loadUrl(String url, Map<String, String> additionalHttpHeaders) berarti menambahkan tajuk tambahan
AbhinayMe
4

Anda harus dapat mengontrol semua header Anda dengan melewatkan loadUrl dan menulis loadPage Anda sendiri menggunakan HttpURLConnection Java. Kemudian gunakan loadData tampilan web untuk menampilkan respons.

Tidak ada akses ke header yang disediakan Google. Mereka berada dalam panggilan JNI, jauh di dalam sumber WebView.

R Earle Harris
sumber
1
Apakah Anda memiliki referensi untuk apa yang Anda katakan dalam jawaban Anda .. akan membantu orang lain jika Anda memberikan referensi implementasi dengan jawaban Anda.
Hirdesh Vishwdewa
1

Berikut adalah implementasi menggunakan 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
    }
}

Perhatikan bahwa ini tidak berfungsi untuk permintaan POST karena WebResourceRequest tidak menyediakan data POST. Ada Request Data - pustaka WebViewClient yang menggunakan solusi injeksi JavaScript untuk mencegat data POST.

Miloš Černilovský
sumber
0

Ini berhasil untuk saya. Buat WebViewClient seperti ini di bawah ini dan setel webclient ke tampilan web Anda. Saya harus menggunakan webview.loadDataWithBaseURL karena url saya (dalam konten saya) tidak memiliki baseurl tetapi hanya url relatif. Anda akan mendapatkan url dengan benar hanya jika ada kumpulan baseurl yang menggunakan 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
sumber
bagi saya ini berfungsi: .post (reqbody) di mana RequestBody reqbody = RequestBody.create (null, "");
Karoly
-2

Anda dapat menggunakan ini:

@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
sumber
2
Ini hanya terus memuat ulang url, bukan?
Onheiron
-3

Saya menemukan masalah yang sama dan menyelesaikannya.

Seperti yang dikatakan sebelumnya, Anda perlu membuat WebViewClient kustom dan mengganti metode shouldInterceptRequest.

WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)

Metode itu harus mengeluarkan webView.loadUrl sambil mengembalikan WebResourceResponse "kosong".

Sesuatu seperti ini:

@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
sumber
Memanggil view.loadUrl dari metode ini tampaknya merusak aplikasi
willcwf
@willcwf apakah Anda memiliki contoh kerusakan ini?
Francesco
@Francesco aplikasi saya juga macet
Giacomo M
Terlalu semua meremehkan ini, mengatakan bahwa itu crash tidak membantu. Harap lebih spesifik, tulis beberapa informasi kesalahan.
Francesco
-14

Gunakan ini:

webView.getSettings().setUserAgentString("User-Agent");
Satish
sumber
11
ini tidak menjawab pertanyaan
younes0
ini tidak sama dengan header otorisasi
Vlad