Thêm tiêu đề tùy chỉnh vào yêu cầu tài nguyên WebView - Android


95

Tôi cần thêm tiêu đề tùy chỉnh vào MỌI yêu cầu đến từ WebView. Tôi biết loadURLcó tham số cho extraHeaders, nhưng những tham số đó chỉ được áp dụng cho yêu cầu ban đầu. Tất cả các yêu cầu tiếp theo không chứa tiêu đề. Tôi đã xem xét tất cả các ghi đè WebViewClient, nhưng không có gì cho phép thêm tiêu đề vào các yêu cầu tài nguyên - onLoadResource(WebView view, String url). Bất kỳ trợ giúp sẽ là tuyệt vời.

Cảm ơn, Ray


2
@MediumOne: Đây không phải là một lỗi vì nó là một tính năng mà bạn cho là bị thiếu. Tôi không biết bất kỳ điều gì trong đặc tả HTTP cho biết các yêu cầu HTTP tiếp theo phải phản chiếu các tiêu đề tùy ý từ các yêu cầu HTTP trước đó.
CommonsWare

1
@CommonsWare: Từ "tiếp theo" gây hiểu nhầm ở đây. Khi tôi gõ " facebook.com " trên bất kỳ trình duyệt nào để tải trang chủ facebook.com, sẽ có một số "yêu cầu tài nguyên" hỗ trợ để tải các tệp CSS, js và img. Bạn có thể kiểm tra điều này trong Chrome bằng cách sử dụng tính năng F12 (tab Mạng). Đối với những yêu cầu này, chế độ xem web không thêm tiêu đề. Tôi đã thử thêm tiêu đề tùy chỉnh vào các yêu cầu FireFox bằng cách sử dụng plugin addons.mozilla.org/en-us/firefox/addon/modify-headers . Plugin này có thể thêm tiêu đề vào tất cả các "yêu cầu tài nguyên" hỗ trợ như vậy. Tôi nghĩ WebView cũng nên làm như vậy.
MediumOne

1
@MediumOne: "Tôi nghĩ WebView cũng nên làm như vậy" - đó là một tính năng mà bạn cho là còn thiếu. Lưu ý rằng bạn phải dùng đến một plugin để Firefox thực hiện điều này. Tôi không nói rằng tính năng được đề xuất của bạn là một ý tưởng tồi. Tôi đang nói rằng việc mô tả nó như một lỗi không có khả năng giúp ích cho lý do của bạn để thêm tính năng được đề xuất này vào Android.
CommonsWare

1
@CommonsWare: Giả sử rằng tôi đang sử dụng WebView để xây dựng trình duyệt có thể được định cấu hình để hoạt động với proxy HTTP tùy chỉnh. Proxy này sử dụng xác thực tùy chỉnh trong đó các yêu cầu đến nó phải có tiêu đề tùy chỉnh. Giờ đây, webview cung cấp một API để đặt tiêu đề tùy chỉnh, nhưng về mặt nội bộ, nó không đặt tiêu đề cho tất cả các yêu cầu tài nguyên mà nó tạo ra. Không có bất kỳ API bổ sung nào để đặt tiêu đề cho những yêu cầu này. Vì vậy, bất kỳ tính năng nào dựa vào việc thêm tiêu đề tùy chỉnh vào các yêu cầu WebView đều không thành công.
Trung bìnhMột

1
@CommonsWare - Tôi sẽ xem lại cuộc trò chuyện này sau 4 năm. Bây giờ tôi đồng ý - đây không phải là một lỗi. Không có gì trong đặc tả HTTP cho biết các yêu cầu tiếp theo sẽ gửi các tiêu đề giống nhau. :)
Trung bìnhMột

Câu trả lời:


81

Thử

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

Để thêm tiêu đề vào các yêu cầu tải tài nguyên, hãy tạo WebViewClient tùy chỉnh và ghi đè:

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

9
Xin lỗi, nhưng điều này không hoạt động. Nó chỉ áp dụng các tiêu đề quá các yêu cầu ban đầu. Tiêu đề KHÔNG được thêm vào các yêu cầu tài nguyên. Những ý tưởng khác? Cảm ơn.
Ray

19
Có, ghi đè WebClient.shouldOverrideUrlLoading như thế này: public boolean shouldOverrideUrlLoading (WebView view, String url) {view.loadUrl (url, extraHeaders); trả về true; }
peceps

5
@peceps - lệnh gọi lại 'shouldOverrideUrlLoading' không được gọi trong quá trình tải tài nguyên. Ví dụ: khi chúng tôi thử view.loadUrl("http://www.facebook.com", extraHeaders), có nhiều yêu cầu tài nguyên như 'http://static.fb.com/images/logo.png'vv được gửi từ webiew. Đối với những yêu cầu này, các tiêu đề phụ không được thêm vào. Và shouldOverrideUrlLoading không được gọi trong các yêu cầu tài nguyên như vậy. Lệnh gọi lại 'OnLoadResource' được gọi, nhưng không có cách nào để đặt tiêu đề tại thời điểm này.
Trung bìnhMột

2
@MediumOne, để tải tài nguyên, ghi đè WebViewClient.shouldInterceptRequest(android.webkit.WebView view, java.lang.String url)Kiểm tra API để biết thêm.
yorkw

3
@yorkw: Phương pháp này nắm bắt tất cả các url yêu cầu tài nguyên. Nhưng không có cách nào để thêm tiêu đề vào các yêu cầu này. Mục tiêu của tôi là thêm tiêu đề HTTP tùy chỉnh vào tất cả các yêu cầu. Nếu điều này có thể đạt được bằng cách sử dụng shouldInterceptRequestphương pháp này, bạn có thể vui lòng giải thích cách nào không?
MediumOne

36

Bạn sẽ cần chặn từng yêu cầu bằng cách sử dụng WebViewClient.shouldInterceptRequest

Với mỗi lần chặn, bạn sẽ cần lấy url, tự thực hiện yêu cầu này và trả lại luồng nội dung:

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

Nếu mục tiêu API tối thiểu của bạn là cấp 21 , bạn có thể sử dụng shouldInterceptRequest mới cung cấp cho bạn thông tin yêu cầu bổ sung (chẳng hạn như tiêu đề) thay vì chỉ URL.


2
Đề phòng trường hợp ai đó gặp phải tình huống tương tự tôi gặp phải khi sử dụng thủ thuật này. (Dù sao đây cũng là một cái hay.) Đây là một lưu ý cho bạn. Vì tiêu đề kiểu nội dung http, có thể chứa tham số tùy chọn như bộ ký tự, không hoàn toàn tương thích với kiểu MIME, nên yêu cầu tham số đầu tiên của phương thức khởi tạo WebResourceResponse, vì vậy, chúng tôi nên trích xuất phần kiểu MIME từ kiểu nội dung bằng mọi cách. có thể nghĩ đến, chẳng hạn như RegExp, để làm cho nó hoạt động cho hầu hết các trường hợp.
James Chen

2
Sự kiện này bị phản đối .. sử dụng public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)thay vì tìm thêm ở đây
Hirdesh Vishwdewa

3
@HirdeshVishwdewa - nhìn vào câu cuối cùng.
Martin Konecny,

2
Bạn có thể bỏ qua việc tải của riêng mình bằng cách trả về kết quả của phương thức shouldInterceptRequest của lớp cha với chế độ xem web của bạn và yêu cầu đã sửa đổi dưới dạng tham số. Điều này đặc biệt hữu ích trong các tình huống mà bạn đang kích hoạt dựa trên URL, không thay đổi nó khi tải lại và sẽ chạy vào một vòng lặp vô hạn. Rất cảm ơn vì ví dụ yêu cầu mới. Với tôi, cách xử lý mọi thứ của Java rất phản trực giác.
Erik Reppen

4
Không thể sử dụng HttpClient với compileSdk 23 trở lên,
Tamás Kozmér

30

Có thể phản hồi của tôi khá muộn, nhưng nó bao gồm API dướitrên 21 cấp.

Để thêm tiêu đề, chúng ta nên chặn mọi yêu cầutạo một yêu cầu mới với tiêu đề bắt buộc.

Vì vậy, chúng ta cần ghi đè phương thức shouldInterceptRequest được gọi trong cả hai trường hợp: 1. for API cho đến cấp 21; 2. cho cấp 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;
            }

        }
   });

Nếu loại phản hồi phải được xử lý, bạn có thể thay đổi

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

đến

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

và thêm phương pháp

        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
Rất tiếc phải trả lời bài đăng cũ này, nhưng với mã này, ứng dụng của tôi cố gắng tải xuống tệp (và nó không thành công) thay vì tải trang.
Giacomo M

Cảm ơn rât nhiều!
AlexS

21

Như đã đề cập trước đây, bạn có thể làm điều này:

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

Tôi đã thử nghiệm điều này và tiếp tục với Bộ điều khiển MVC mà tôi đã mở rộng Thuộc tính ủy quyền để kiểm tra tiêu đề và tiêu đề ở đó.


Tôi sẽ phải giải quyết vấn đề này một lần nữa như khi nó được viết và đăng nó hoạt động với Kit-Kat. Tôi chưa thử với Lolly Pop.
leeroya

Không làm việc cho tôi trên Jelly đậu hoặc Marshmallow ... không thay đổi bất cứ điều gì trong các tiêu đề
Erik Verboom

6
Điều này không làm những gì OP đang yêu cầu. Anh ấy muốn thêm tiêu đề vào tất cả các yêu cầu do webview đưa ra. Điều này thêm tiêu đề tùy chỉnh để yêu cầu đầu tiên chỉ
NinjaCoder

Đây không phải là những gì OP là yêu cầu
Akshay

Tôi biết điều này không trả lời những gì OP đang tìm kiếm nhưng đây chính xác là những gì tôi muốn, tức là thêm một tiêu đề bổ sung vào URL WebViewIntent. Cảm ơn, bất chấp!
Joshua Pinter

9

Điều này phù hợp với tôi:

  1. Trước tiên, bạn cần tạo phương thức, phương thức này sẽ trả về các tiêu đề mà bạn muốn thêm vào yêu cầu:

    private Map<String, String> getCustomHeaders()
    {
        Map<String, String> headers = new HashMap<>();
        headers.put("YOURHEADER", "VALUE");
        return headers;
    }
  2. Thứ hai, bạn cần tạo 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. Thêm WebViewClient vào WebView của bạn:

    webView.setWebViewClient(getWebViewClient());

Hi vọng điêu nay co ich.


1
Có vẻ tốt, nhưng điều này có thêm tiêu đề hay điều này thay thế tiêu đề?
Ivo Renkema 17/02/17

@IvoRenkema loadUrl(String url, Map<String, String> additionalHttpHeaders) nghĩa là thêm tiêu đề bổ sung
AbhinayMe

4

Bạn sẽ có thể kiểm soát tất cả các tiêu đề của mình bằng cách bỏ qua loadUrl và viết loadPage của riêng bạn bằng cách sử dụng HttpURLConnection của Java. Sau đó, sử dụng loadData của webview để hiển thị phản hồi.

Không có quyền truy cập vào các tiêu đề mà Google cung cấp. Chúng đang ở trong một lệnh gọi JNI, nằm sâu trong nguồn WebView.


1
Bạn có bất kỳ tham chiếu nào đến những gì bạn nói trong câu trả lời của mình không .. sẽ hữu ích cho những người khác nếu bạn cung cấp các tham chiếu triển khai cùng với câu trả lời của mình.
Hirdesh Vishwdewa

1

Đây là cách triển khai bằng 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
    }
}

Lưu ý rằng điều này không hoạt động đối với các yêu cầu POST vì WebResourceRequest không cung cấp dữ liệu POST. Có một Thư viện Dữ liệu Yêu cầu - WebViewClient sử dụng giải pháp chèn JavaScript để chặn dữ liệu POST.


0

Điều này đã làm việc cho tôi. Tạo WebViewClient như bên dưới và đặt webclient thành chế độ xem web của bạn. Tôi đã phải sử dụng webview.loadDataWithBaseURL vì các url của tôi (trong nội dung của tôi) không có baseurl mà chỉ có các url tương đối. Bạn sẽ chỉ nhận được url chính xác khi có một bộ baseurl bằng cách sử dụng 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;
            }
        }
    };

}

đối với tôi nó hoạt động: .post (reqbody) where RequestBody reqbody = RequestBody.create (null, "");
Karoly

-2

Bạn có thể sử dụng cái này:

@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
Điều này chỉ tiếp tục tải lại url, phải không?
Onheiron

-3

Tôi đã vượt qua cùng một vấn đề và giải quyết.

Như đã nói trước đây, bạn cần tạo WebViewClient tùy chỉnh của mình và ghi đè phương thức shouldInterceptRequest.

WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)

Phương thức đó sẽ tạo ra một webView.loadUrl trong khi trả về một WebResourceResponse "trống".

Một cái gì đó như thế này:

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

Gọi view.loadUrl từ phương thức này có vẻ như làm hỏng ứng dụng
willcwf

@willcwf bạn có ví dụ về sự cố này không?
Francesco

@Facesco ứng dụng của tôi cũng bị lỗi
Giacomo M

Tất cả đều phản đối điều này, nói rằng nó sụp đổ không có ích gì. Xin vui lòng được cụ thể hơn, viết một số thông tin lỗi.
Francesco

-14

Dùng cái này:

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

11
điều này không trả lời câu hỏi
younes0

điều này không giống với tiêu đề ủy quyền
Vlad
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.