เพิ่มส่วนหัวที่กำหนดเองในคำขอทรัพยากร WebView - Android


97

ฉันต้องการเพิ่มส่วนหัวที่กำหนดเองในทุกคำขอที่มาจาก WebView ฉันรู้ว่าloadURLมีพารามิเตอร์สำหรับextraHeadersแต่ใช้กับคำขอเริ่มต้นเท่านั้น คำขอที่ตามมาทั้งหมดไม่มีส่วนหัว ผมมองที่แทนที่ทั้งหมดในWebViewClientแต่ไม่มีอะไรที่ช่วยให้การเพิ่มส่วนหัวการร้องขอทรัพยากร onLoadResource(WebView view, String url)- ความช่วยเหลือใด ๆ จะวิเศษมาก

ขอบคุณเรย์


2
@MediumOne: นี่ไม่ใช่ข้อผิดพลาดมากนักเนื่องจากเป็นคุณลักษณะที่คุณคิดว่าขาดหายไป ฉันไม่รู้อะไรเลยในข้อกำหนด HTTP ที่ระบุว่าคำขอ HTTP ที่ตามมาต้องสะท้อนส่วนหัวที่กำหนดเองจากคำขอ HTTP ก่อนหน้านี้
CommonsWare

1
@CommonsWare: คำว่า "ภายหลัง" ทำให้เข้าใจผิดที่นี่ เมื่อฉันพิมพ์ " facebook.com " บนเบราว์เซอร์ใด ๆ เพื่อโหลดหน้าแรกของ facebook.com มี "คำขอทรัพยากร" ที่รองรับหลายรายการเพื่อโหลดไฟล์ CSS, js และ img คุณสามารถตรวจสอบสิ่งนี้ใน Chrome โดยใช้คุณสมบัติ F12 (แท็บเครือข่าย) สำหรับคำขอเหล่านี้ webview จะไม่เพิ่มส่วนหัว ฉันลองเพิ่มส่วนหัวที่กำหนดเองในคำขอ FireFox โดยใช้ปลั๊กอินaddons.mozilla.org/en-us/firefox/addon/modify-headers ปลั๊กอินนี้สามารถเพิ่มส่วนหัวให้กับ "คำขอทรัพยากร" ที่สนับสนุนดังกล่าวทั้งหมด ฉันคิดว่า WebView ควรทำเช่นเดียวกัน
MediumOne

1
@MediumOne: "ฉันคิดว่า WebView ควรทำเหมือนกัน" - ซึ่งเป็นคุณลักษณะที่คุณคิดว่าไม่มี โปรดทราบว่าคุณต้องใช้ปลั๊กอินเพื่อให้ Firefox ทำสิ่งนี้ได้ ฉันไม่ได้บอกว่าคุณลักษณะที่คุณเสนอเป็นความคิดที่ไม่ดี ฉันกำลังบอกว่าการระบุลักษณะเป็นข้อบกพร่องไม่น่าจะช่วยให้คุณได้รับคุณลักษณะที่เสนอนี้เพิ่มลงใน Android
CommonsWare

1
@CommonsWare: สมมติว่าฉันใช้ WebView เพื่อสร้างเบราว์เซอร์ที่สามารถกำหนดค่าให้ทำงานกับพร็อกซี HTTP ที่กำหนดเองได้ พร็อกซีนี้ใช้การพิสูจน์ตัวตนแบบกำหนดเองโดยที่คำขอไปยังควรมีส่วนหัวที่กำหนดเอง ตอนนี้ webview มี API สำหรับตั้งค่าส่วนหัวที่กำหนดเอง แต่ภายในไม่ได้ตั้งค่าส่วนหัวให้กับคำขอทรัพยากรทั้งหมดที่สร้างขึ้น ไม่มี API เพิ่มเติมสำหรับตั้งค่าส่วนหัวสำหรับคำขอเหล่านี้เช่นกัน ดังนั้นคุณลักษณะใด ๆ ที่อาศัยการเพิ่มส่วนหัวที่กำหนดเองในคำขอ WebView จึงล้มเหลว
MediumOne

1
@CommonsWare - ฉันกำลังทบทวนบทสนทนานี้อีกครั้งหลังจากผ่านไป 4 ปี ฉันเห็นด้วยตอนนี้ - นี่ไม่ควรเป็นข้อบกพร่อง ไม่มีสิ่งใดในข้อกำหนด HTTP ที่ระบุว่าคำขอในภายหลังควรส่งส่วนหัวเดียวกัน :)
MediumOne

คำตอบ:


84

ลอง

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

สำหรับการเพิ่มส่วนหัวให้กับคำขอโหลดทรัพยากรให้กำหนด WebViewClient แบบกำหนดเองและแทนที่:

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

9
ขออภัย แต่ไม่ได้ผล ใช้เฉพาะส่วนหัวด้วยกับคำขอเริ่มต้น ไม่ได้เพิ่มส่วนหัวในคำขอทรัพยากร ความคิดอื่น ๆ ? ขอบคุณ.
Ray

19
ใช่แทนที่ WebClient.shouldOverrideUrlLoading ดังนี้: บูลีนสาธารณะ shouldOverrideUrlLoading (มุมมอง WebView, String url) {view.loadUrl (url, extraHeaders); กลับจริง; }
peceps

5
@peceps - การเรียกกลับ 'shouldOverrideUrlLoading' ไม่ถูกเรียกในระหว่างการโหลดทรัพยากร ตัวอย่างเช่นเมื่อเราลองview.loadUrl("http://www.facebook.com", extraHeaders)มีคำขอทรัพยากรหลายรายการเช่น'http://static.fb.com/images/logo.png'etc ที่ส่งมาจาก webiew สำหรับคำขอเหล่านี้จะไม่มีการเพิ่มส่วนหัวพิเศษ และ shouldOverrideUrlLoading ไม่ถูกเรียกในระหว่างการร้องขอทรัพยากรดังกล่าว เรียกกลับ 'OnLoadResource' แต่ไม่มีวิธีการตั้งค่าส่วนหัวในตอนนี้
MediumOne

2
@MediumOne สำหรับการโหลดทรัพยากรแทนที่WebViewClient.shouldInterceptRequest(android.webkit.WebView view, java.lang.String url)ตรวจสอบAPIเพื่อดูข้อมูลเพิ่มเติม
yorkw

3
@yorkw: วิธีนี้จะจับ URL คำขอทรัพยากรทั้งหมด แต่ไม่มีวิธีใดในการเพิ่มส่วนหัวให้กับคำขอเหล่านี้ เป้าหมายของฉันคือเพิ่มส่วนหัว HTTP ที่กำหนดเองให้กับคำขอทั้งหมด หากสามารถทำได้โดยใช้shouldInterceptRequestวิธีนี้โปรดอธิบายวิธีการ?
MediumOne

36

คุณจะต้องสกัดกั้นแต่ละคำขอโดยใช้WebViewClient.shouldInterceptRequest

ในการสกัดกั้นแต่ละครั้งคุณจะต้องใช้ url ส่งคำขอนี้ด้วยตนเองและส่งคืนสตรีมเนื้อหา

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

หากเป้าหมาย API ขั้นต่ำของคุณคือระดับ 21คุณสามารถใช้shouldInterceptRequestใหม่ซึ่งให้ข้อมูลคำขอเพิ่มเติมแก่คุณ (เช่นส่วนหัว) แทนที่จะเป็นเพียง URL


2
ในกรณีที่มีคนเจอสถานการณ์เดียวกันกับฉันขณะใช้เคล็ดลับนี้ (นี่เป็นข้อควรทราบสำหรับคุณ) เนื่องจากส่วนหัวชนิดเนื้อหา http ซึ่งสามารถมีพารามิเตอร์ที่เป็นทางเลือกเช่นชุดอักขระจึงไม่สามารถใช้งานร่วมกับประเภท MIME ได้อย่างสมบูรณ์ความต้องการของพารามิเตอร์ตัวแรกของตัวสร้าง WebResourceResponse ดังนั้นเราจึงควรแยกส่วนประเภท MIME ออกจากประเภทเนื้อหาตามความหมายของคุณ สามารถคิดได้เช่น RegExp เพื่อให้ใช้งานได้กับกรณีส่วนใหญ่
James Chen

2
กิจกรรมนี้เลิกใช้แล้ว .. ใช้public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)แทนได้ที่นี่
Hirdesh Vishwdewa

3
@HirdeshVishwdewa - ดูประโยคสุดท้ายสิ
Martin Konecny

2
คุณสามารถข้ามการโหลดของคุณเองได้โดยส่งคืนผลลัพธ์ของเมธอด shouldInterceptRequest ของ superclass พร้อมกับ webview ของคุณและคำขอที่แก้ไขเป็นพารามิเตอร์ สิ่งนี้มีประโยชน์อย่างยิ่งในสถานการณ์ที่คุณเรียกใช้โดยอิงตาม URL ไม่ได้เปลี่ยนแปลงเมื่อโหลดซ้ำและจะทำงานในวงวนไม่สิ้นสุด ขอบคุณมากสำหรับตัวอย่างคำขอใหม่ วิธีการจัดการกับสิ่งต่างๆของ Java นั้นสวนทางกับฉันมาก
Erik Reppen

4
ไม่สามารถใช้ HttpClient กับ compileSdk 23 ขึ้นไป
TamásKozmér

30

บางทีคำตอบของฉันอาจจะค่อนข้างช้า แต่ครอบคลุม API ที่ต่ำกว่าและสูงกว่า 21 ระดับ

ในการเพิ่มส่วนหัวเราควรสกัดกั้นทุกคำขอและสร้างใหม่โดยมีส่วนหัวที่จำเป็น

ดังนั้นเราจำเป็นต้องแทนที่เมธอดshouldInterceptRequest ที่เรียกว่าในทั้งสองกรณี: 1. สำหรับ API จนถึงระดับ 21; 2. สำหรับ 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;
            }

        }
   });

หากควรประมวลผลประเภทการตอบกลับคุณสามารถเปลี่ยนแปลงได้

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

และเพิ่มวิธีการ

        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
ขออภัยที่ตอบโพสต์เก่านี้ แต่ด้วยรหัสนี้แอปของฉันพยายามดาวน์โหลดไฟล์ (และล้มเหลว) แทนที่จะโหลดหน้า
Giacomo M

สามารถใช้สำหรับการขอโพสต์ได้หรือไม่?
พ.ค.

21

ดังที่ได้กล่าวไว้ก่อนหน้านี้คุณสามารถทำได้:

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

ฉันทดสอบสิ่งนี้และเปิดด้วย MVC Controller ซึ่งฉันขยาย Authorize Attribute เพื่อตรวจสอบส่วนหัวและส่วนหัวอยู่ที่นั่น


ฉันจะต้องพูดถึงเรื่องนี้อีกครั้งเหมือนตอนที่เขียนและโพสต์มันทำงานร่วมกับ Kit-Kat ฉันไม่ได้ลองกับ Lolly Pop
leeroya

ไม่ทำงานสำหรับฉันใน Jelly bean หรือ Marshmallow ... ไม่ได้เปลี่ยนแปลงอะไรในส่วนหัว
Erik Verboom

6
สิ่งนี้ไม่ได้ทำตามที่ OP ขอ เขาต้องการเพิ่มส่วนหัวให้กับคำขอทั้งหมดที่ทำโดย webview สิ่งนี้จะเพิ่มส่วนหัวที่กำหนดเองในคำขอแรกเท่านั้น
NinjaCoder

นี่ไม่ใช่สิ่งที่ OP กำลังถาม
Akshay

ฉันรู้ว่านี่ไม่ได้ตอบโจทย์สิ่งที่ OP กำลังมองหา แต่นี่คือสิ่งที่ฉันต้องการนั่นคือการเพิ่มส่วนหัวพิเศษให้กับ URL ของ WebViewIntent ขอบคุณไม่ว่า!
Joshua Pinter

9

สิ่งนี้ใช้ได้กับฉัน:

  1. ก่อนอื่นคุณต้องสร้างวิธีการซึ่งจะส่งคืนส่วนหัวของคุณที่คุณต้องการเพิ่มในคำขอ:

    private Map<String, String> getCustomHeaders()
    {
        Map<String, String> headers = new HashMap<>();
        headers.put("YOURHEADER", "VALUE");
        return headers;
    }
    
  2. ประการที่สองคุณต้องสร้าง 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. เพิ่ม WebViewClient ลงใน WebView ของคุณ:

    webView.setWebViewClient(getWebViewClient());
    

หวังว่านี่จะช่วยได้


1
ดูดี แต่เพิ่มส่วนหัวหรือไม่หรือแทนที่ส่วนหัว
Ivo Renkema

@IvoRenkema loadUrl(String url, Map<String, String> additionalHttpHeaders) หมายถึงเพิ่มส่วนหัวเพิ่มเติม
AbhinayMe

4

คุณควรจะสามารถควบคุมส่วนหัวทั้งหมดของคุณได้โดยการข้าม loadUrl และเขียน loadPage ของคุณเองโดยใช้ HttpURLConnection ของ Java จากนั้นใช้ loadData ของ webview เพื่อแสดงการตอบสนอง

ไม่มีการเข้าถึงส่วนหัวที่ Google มีให้ พวกเขาอยู่ในการเรียก JNI ซึ่งอยู่ลึกลงไปในแหล่งที่มาของ WebView


1
คุณมีการอ้างอิงถึงสิ่งที่คุณพูดในคำตอบของคุณหรือไม่ .. มันจะเป็นประโยชน์สำหรับคนอื่น ๆ หากคุณให้ข้อมูลอ้างอิงกับคำตอบของคุณ
Hirdesh Vishwdewa

1

นี่คือการใช้งานโดยใช้ 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
    }
}

โปรดทราบว่าสิ่งนี้ใช้ไม่ได้กับคำขอ POST เนื่องจาก WebResourceRequest ไม่ได้ให้ข้อมูล POST มีข้อมูลการร้องขอ - ไลบรารี WebViewClientซึ่งใช้วิธีแก้ปัญหาการแทรก JavaScript สำหรับการดักจับข้อมูล POST


0

สิ่งนี้ได้ผลสำหรับฉัน สร้าง WebViewClient แบบนี้ด้านล่างและตั้งค่า Webclient เป็น webview ของคุณ ฉันต้องใช้ webview.loadDataWithBaseURL เนื่องจาก URL ของฉัน (ในเนื้อหาของฉัน) ไม่มี baseurl แต่มีเพียง URL ที่สัมพันธ์กันเท่านั้น คุณจะได้รับ url อย่างถูกต้องก็ต่อเมื่อมีการตั้งค่า baseurl โดยใช้ 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;
            }
        }
    };

}

สำหรับฉันมันใช้งานได้: .post (reqbody) โดยที่ RequestBody reqbody = RequestBody.create (null, "");
Karoly

-2

คุณสามารถใช้สิ่งนี้:

@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
แค่นี้ก็โหลด url ซ้ำใช่ไหม
Onheiron

-3

ฉันเจอปัญหาเดียวกันและแก้ไขได้

ดังที่ได้กล่าวไว้ก่อนหน้านี้คุณต้องสร้าง WebViewClient ที่กำหนดเองและแทนที่เมธอด shouldInterceptRequest

WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request)

วิธีนี้ควรออก webView.loadUrl ในขณะที่ส่งคืน WebResourceResponse "ว่าง"

สิ่งนี้:

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

การเรียก view.loadUrl จากวิธีนี้ดูเหมือนว่าแอปจะขัดข้อง
willcwf

@willcwf คุณมีตัวอย่างของการขัดข้องนี้หรือไม่?
Francesco

@ Francesco แอปของฉันก็ขัดข้องเช่นกัน
Giacomo M

เกินไปทั้งหมดคือการลงคะแนนสิ่งนี้โดยบอกว่าความผิดพลาดไม่ได้ช่วยอะไร โปรดระบุให้เจาะจงมากขึ้นเขียนข้อมูลข้อผิดพลาด
Francesco

-14

ใช้สิ่งนี้:

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

11
สิ่งนี้ไม่ตอบคำถาม
younes0

ไม่เหมือนกับส่วนหัวการอนุญาต
Vlad
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.