ป้องกันการแคชเบราว์เซอร์ของผลการโทร AJAX


262

ดูเหมือนว่าหากฉันโหลดเนื้อหาแบบไดนามิกโดยใช้$.get()ผลลัพธ์จะถูกแคชในเบราว์เซอร์

การเพิ่มสตริงแบบสุ่มใน QueryString ดูเหมือนว่าจะแก้ปัญหานี้ (ฉันใช้new Date().toString()) แต่รู้สึกเหมือนแฮ็ค

มีวิธีอื่นในการบรรลุเป้าหมายนี้หรือไม่? หรือถ้าสตริงที่ไม่ซ้ำกันเป็นวิธีเดียวที่จะบรรลุเป้าหมายนี้ข้อเสนอแนะอื่น ๆ กว่าnew Date()?


คุณสามารถใช้สัญกรณ์สั้น ๆ$.now()แทนการทำ (วันที่ใหม่ (). getTime ()) ทุกครั้ง
Dayson

1
ชื่อคำถามของคุณทำให้เข้าใจผิดเล็กน้อย คุณอาจพิจารณาเปลี่ยนชื่อไหม
0112

4
คุณพิจารณาเลือกคำตอบอื่นเป็นคำตอบที่ยอมรับหรือไม่
M4N

คำตอบ:


241

ฉันใช้new Date().getTime()ซึ่งจะหลีกเลี่ยงการชนกันเว้นแต่คุณจะมีคำขอหลายอย่างเกิดขึ้นภายในมิลลิวินาทีเดียวกัน:

$.get('/getdata?_=' + new Date().getTime(), function(data) {
    console.log(data); 
});

แก้ไข:คำตอบนี้มีอายุหลายปี ก็ยังคงทำงาน (เพราะฉะนั้นฉันไม่ได้ลบมัน) แต่มีที่ดีกว่า / วิธีการทำความสะอาดของการบรรลุนี้ในขณะนี้ ตั้งค่าของฉันสำหรับการนี้วิธี แต่นี้คำตอบยังจะเป็นประโยชน์ถ้าคุณต้องการที่จะปิดการใช้งานแคชสำหรับทุกคำขอในช่วงชีวิตของหน้า


11
ฉันจะลงคะแนนเพียงเพราะทำความสะอาดเพื่อให้ jQuery ทำตามคำตอบของปีเตอร์เจโซลูชันของคุณจะทำงานได้ แต่ยากที่จะรักษาในระยะยาว
Niklas Wulff

11
ส่วนนี้ต้องการการบำรุงรักษาอย่างไร เมื่อเทียบกับ jQuery อะไร
Sunny R Gupta

5
มันอาจจะเป็นที่น่าสังเกตว่าnew Date().getTime()รหัสถูกนำมาใช้เช่นนี้ var nocache = new Date().getTime(); var path = 'http://hostname.domain.tld/api/somejsonapi/?cache=' + nocache;... ฉันใช้เวลาสองสามนาทีเพื่อคิดออกเอง แน่นอนว่า?cacheอาจเป็นถ้อยคำที่ API ไม่ต้องการ
doubleJJ

1
+1 แม้แต่คำตอบของปีเตอร์เจที่มีวิธีการที่ดีกว่าคำตอบนี้ไม่ผิดหรือเป็นคำตอบที่ไม่ดี ฉันเชื่อว่า DV เกิดขึ้นเพราะคุณเหนือกว่าของ Peter (ตามที่ยอมรับ) และ OP จะไม่ปรากฏบน SO ตั้งแต่ต้นปี 2013
Michel Ayres

1
url = url + (-1 === url.indexOf('?') ? '?' : '&') + "__=" + Number(new Date());

514

ข้อมูลต่อไปนี้จะป้องกันคำขอ AJAX ในอนาคตทั้งหมดไม่ให้ถูกแคชไม่ว่าคุณจะใช้วิธีใดใน jQuery ($ .get, $ .ajax ฯลฯ )

$.ajaxSetup({ cache: false });

7
เมื่อทำการตรวจสอบ (Fiddler) ดูเหมือนว่า jQuery จะใช้สิ่งนี้ภายในโดยเพียงแค่เพิ่มการประทับเวลาต่อไป (ตามที่กล่าวไว้ที่อื่นในคำตอบเหล่านี้) ให้ฉัน .ajaxSetup วิธีการทำความสะอาด (ในความคิดของฉัน.)
ปีเตอร์ J

8
แน่นอนมันไม่จำเป็นต้องอยู่ในเอกสารพร้อมเรียก
ปีเตอร์ J

19
เหตุใดจึงปิดใช้งานการแคชอาแจ็กซ์ทั่วโลก? ฉันคิดว่าควรทำแบบต่อสายเหมือนที่โจนาธานตอบ
Sunny R Gupta

5
สิ่งที่ใช้ได้กับแอปของคุณ ฉันยังคงใช้วิธีนี้ในแอพเชิงพาณิชย์ทุกครั้งที่ฉันต้องการข้อมูลใหม่สำหรับการโทร AJAX ทั้งหมด สำหรับคนอื่น ๆ ข้อมูลที่แคชก็ใช้ได้
Peter J

1
ลิงค์คำอธิบาย: ตั้งค่าเริ่มต้นสำหรับคำขอ Ajax ในอนาคต ไม่แนะนำให้ใช้
itwebdeveloper

319

$ .get () ของ JQuery จะแคชผลลัพธ์ แทน

$.get("myurl", myCallback)

คุณควรใช้ $ .ajax ซึ่งจะช่วยให้คุณปิดการแคช:

$.ajax({url: "myurl", success: myCallback, cache: false});

62
+1 นี่คือคำตอบที่ถูกต้อง โซลูชันของ Peter J เกี่ยวกับการปิดใช้งานการแคชทั่วโลกเป็นวิธีปฏิบัติที่ไม่ดี IMO
Salman von Abbas

7
สิ่งสำคัญที่ควรทราบคือ "ระดับโลก" สำหรับหน้า / คำขอเท่านั้น
ปีเตอร์ J

3
+1: การแคชควรเฉพาะกับประเภทคำขอ บางคนร้องขอไปยัง server อาจต้องดีแคช (ที่ข้อมูลเซิร์ฟเวอร์เป็นแบบคงที่) ดังนั้นการเลือกแคชในคำขอตามคำขอพื้นฐานดีกว่าเพียงแค่เปลี่ยนมันออกทั้งหมด
Gone Coding

1
+1 สำหรับคำตอบที่ถูกต้อง - ป้องกันการแคชตามการโทรต่อหนึ่งครั้งโดยใช้วิธีการของ jQuery แทนที่จะเป็นการแฮกด้วยตนเอง
เบรนแดนฮิลล์

2
อีกคำตอบที่ดี ฉันต้องบอกว่าสำหรับฉันตลอดเวลาการปิดการใช้งานแคชทั่วโลกนั้นเป็นประโยชน์อย่างมาก ทุกอย่างขึ้นอยู่กับการออกแบบแอปพลิเคชันของคุณ ไม่มี bullet เงิน แต่ในสถานการณ์นี้ฉันอยากจะแนะนำฟังก์ชั่นที่ยอมรับบูลีนสำหรับการแคช, ฟังก์ชั่นสำหรับการโทรกลับและ URL สำหรับ modularity "แฮ็ค" แบบแมนนวลนั้นใช้ได้ แต่ถ้าคุณใช้ jQuery ให้ยึดฟังก์ชั่นของมันทุกครั้งที่ทำได้ สิ่งนี้จะไม่เพียงทำให้การพัฒนาง่ายขึ้นในขณะนี้ แต่ยังเป็นการอัพเกรดในอนาคตไปยังห้องสมุดอีกด้วย
Anthony Mason

24

คำตอบทั้งหมดที่นี่ปล่อยให้รอยเท้าบน URL ที่ร้องขอซึ่งจะปรากฏในบันทึกการเข้าถึงของเซิร์ฟเวอร์

ฉันต้องการโซลูชันที่อิงกับส่วนหัวโดยไม่มีผลข้างเคียงและฉันพบว่าสามารถทำได้โดยการตั้งค่าส่วนหัวที่กล่าวถึงในวิธีการควบคุมหน้าเว็บแคชในเบราว์เซอร์ทั้งหมดหรือไม่ .

ผลลัพธ์ที่ทำงานกับ Chrome ได้อย่างน้อยจะเป็น:

$.ajax({
   url: url, 
   headers: {
     'Cache-Control': 'no-cache, no-store, must-revalidate', 
     'Pragma': 'no-cache', 
     'Expires': '0'
   }
});


อาจเป็นคำถามโง่ ๆ แต่ถ้าอาแจ็กซ์ของฉันส่งคืนรูปภาพรูปภาพจะถูกแคชหรือไม่ เพื่อหลีกเลี่ยงการร้องขอขนาดใหญ่ของ Amazon S3?
Marcelo Agimóvel

MarceloAgimóvelซึ่งอาจเป็นคำถาม SO ที่แยกจากกันฉันเชื่อ
Aidin

23

อีกวิธีหนึ่งคือให้ไม่มีส่วนหัวแคชจากเซิร์ฟเวอร์ในรหัสที่สร้างการตอบสนองต่อการโทร ajax:

response.setHeader( "Pragma", "no-cache" );
response.setHeader( "Cache-Control", "no-cache" );
response.setDateHeader( "Expires", 0 );

17
ไม่ถูกต้อง ใน IE ส่วนหัวแบบไม่มีแคชจะถูกละเว้นสำหรับการโทร XMLHttpRequest ตามที่อธิบายไว้ที่นี่: stackoverflow.com/questions/244918// The DateTime (หรือวิธีการ. jaxSetup ของฉัน) เป็นโซลูชันเดียวที่ใช้งานได้จริง
ปีเตอร์ J

ฉันเพิ่งวางของฉันตามปกติไม่มีแคชมนต์มันไม่ได้ระบุว่ามันเป็นเฉพาะ IE
miceuz

2
สิ่งนี้ควรปิดการแคชสำหรับเบราว์เซอร์ทั้งหมด: response.setHeader ("การควบคุมแคช", "max-age = 0, ไม่มีแคช, ไม่มีร้านค้า, หลังการตรวจสอบ = 0, การตรวจสอบล่วงหน้า = 0");
Chris Broski

13

โดยส่วนตัวแล้วฉันรู้สึกว่าวิธีสตริงข้อความค้นหามีความน่าเชื่อถือมากกว่าการพยายามตั้งค่าส่วนหัวบนเซิร์ฟเวอร์ - ไม่มีการรับประกันว่าพร็อกซีหรือเบราว์เซอร์จะไม่เพียงแค่แคชมันอย่างไรก็ตามเบราว์เซอร์บางตัวก็แย่กว่าคนอื่น ๆ

ฉันมักจะใช้Math.random()แต่ฉันไม่เห็นอะไรผิดปกติกับการใช้วันที่ (คุณไม่ควรทำการร้องขอ AJAX เร็วพอที่จะรับค่าเดียวกันสองครั้ง)


2
รวมวันที่ (). getTime () ร่วมกับ Math.random () และคุณควรจะอยู่อย่างปลอดภัย ในหมายเหตุด้าน Ext.Ajax ยังใช้ getTime () เมื่อระบุ disableCaching
vividos


5

แน่นอนว่าเทคนิค "การแบ่งแคช" จะทำให้งานเสร็จ แต่จะไม่เกิดขึ้นตั้งแต่แรกถ้าเซิร์ฟเวอร์ระบุให้ลูกค้าทราบว่าการตอบสนองไม่ควรถูกแคช ในบางกรณีมันจะมีประโยชน์ในการตอบสนองแคชบางครั้งไม่ ปล่อยให้เซิร์ฟเวอร์เป็นตัวตัดสินอายุการใช้งานที่ถูกต้องของข้อมูล คุณอาจต้องการเปลี่ยนในภายหลัง ทำจากเซิร์ฟเวอร์ได้ง่ายกว่าจากหลาย ๆ ที่ในรหัส UI ของคุณ

แน่นอนว่ามันจะไม่ช่วยถ้าคุณไม่สามารถควบคุมเซิร์ฟเวอร์ได้


5

แล้วการใช้คำขอ POST แทนที่จะเป็น GET ... (ซึ่งคุณควรจะทำ ... )


ฉันคิดว่ามันเป็นทางออกที่ดีกว่า แต่น่าเสียดายที่ฉัน (อย่างใด) สามารถทำได้เพียงขอ GET ดังนั้น .. มันเป็นวันที่ใหม่ (). getTime () สำหรับตอนนี้
Salamander2007

โปรดเพิ่มคำอธิบายให้กับคำตอบของคุณเพื่อให้คนอื่นสามารถเรียนรู้ได้ - ทำไมต้องมีคำขอ POST
Nico Haase

5

คำถามที่แท้จริงคือทำไมคุณไม่ต้องการให้แคชนี้ หากไม่ควรแคชเนื่องจากมีการเปลี่ยนแปลงตลอดเวลาเซิร์ฟเวอร์ควรระบุเพื่อไม่แคชทรัพยากร หากบางครั้งมันเปลี่ยนไป (เพราะทรัพยากรอย่างใดอย่างหนึ่งมันขึ้นอยู่กับสามารถเปลี่ยนแปลงได้) และหากรหัสลูกค้ามีวิธีการรู้เกี่ยวกับมันก็สามารถผนวกพารามิเตอร์ดัมมี่ไปยัง URL ที่คำนวณจากกัญชาบางวันหรือวันที่แก้ไขล่าสุด ของทรัพยากรเหล่านั้น (นั่นคือสิ่งที่เราทำในทรัพยากรสคริปต์ของ Microsoft Ajax เพื่อให้สามารถแคชได้ตลอดไป แต่ยังสามารถแสดงเวอร์ชันใหม่ได้ตามที่ปรากฏ) หากไคลเอนต์ไม่ทราบการเปลี่ยนแปลงวิธีที่ถูกต้องควรสำหรับเซิร์ฟเวอร์เพื่อจัดการคำขอ HEAD อย่างถูกต้องและแจ้งให้ไคลเอ็นต์ทราบว่าจะใช้เวอร์ชันแคชหรือไม่ ดูเหมือนว่าฉันจะต่อท้ายพารามิเตอร์แบบสุ่มหรือการบอกจากไคลเอนต์ว่าแคชไม่ถูกต้องเพราะความสามารถในการแคชเป็นคุณสมบัติของทรัพยากรเซิร์ฟเวอร์และดังนั้นควรตัดสินใจด้านเซิร์ฟเวอร์ คำถามอีกข้อหนึ่งที่ถามตัวเองคือควรให้บริการผ่าน GET หรือผ่าน POST นั่นเป็นคำถามของซีแมนทิกส์ แต่ก็มีผลกระทบด้านความปลอดภัยด้วย (มีการโจมตีที่ทำงานได้ก็ต่อเมื่อเซิร์ฟเวอร์ยอมให้รับ) POST จะไม่ถูกแคช


6
ถ้าคุณกำลังใช้พร็อกซีเซิร์ฟเวอร์ที่คุณไม่ได้ควบคุมนโยบายการแคช เกิดอะไรขึ้นถ้าแอปของคุณต้องการทำคำขอใหม่ทุกครั้งอย่างชัดเจน คำตอบของสิ่งต่าง ๆ ไม่ได้ชัดเจนเสมอดำและขาวมีพื้นที่สีเทาเสมอ
7wp

ทรูมันไม่ได้ตัดชัดเจนเสมอ แต่การเห็นคำตอบนี้ทำให้ฉันตั้งคำถามกับข้อสันนิษฐานของฉันและทำให้ฉันค้นหาสาเหตุของปัญหา นั่นอาจไม่ใช่กรณีสำหรับทุกคน แต่มันช่วยฉันได้ หากคุณอยู่ที่นี่การอ่านนี้คุณควรพิจารณาด้วย
Jonathan Tran

สิ่งนี้ช่วยฉันได้เลย ResponseCaching ถูกตั้งค่าเป็นฝั่งเซิร์ฟเวอร์ 60m โดยค่าเริ่มต้น เปลี่ยนเป็น No-Cache และหยุดการแคชกับไคลเอ็นต์
mattygee

4

บางทีคุณควรดูที่ $ .ajax () แทน (ถ้าคุณใช้ jQuery ซึ่งดูเหมือนว่า) ลองดูที่: http://docs.jquery.com/Ajax/jQuery.ajax#optionsและตัวเลือก "แคช"

อีกวิธีหนึ่งคือดูว่าคุณทำสิ่งต่าง ๆ บนฝั่งเซิร์ฟเวอร์


1
น่าเสียดายที่หลังจากการตรวจสอบโดยใช้ $ .ajax () และ set cache = false โดยทั่วไปแล้วจะทำสิ่งเดียวกัน jQuery จะเพิ่มหมายเลขสุ่มลงในการสืบค้นและจะไม่ตรวจสอบการสืบค้นที่มีอยู่ ดังนั้นฉันเดาว่าใช้ $ .get () จะพอเพียง
Salamander2007

อาโอเค ไม่เคยลองเลยจำได้ว่าฉันเคยเห็นบางอย่างเกี่ยวกับเรื่องนี้ในเอกสาร :)
finpingvin

ไม่จำเป็นแม้แต่ใช้ $ .ajax เพียงใช้. jaxSetup
Peter J

3

คำตอบที่ยอดเยี่ยมเล็ก ๆ น้อย ๆ ที่ได้รับ: หากคุณกำลังใช้งานโซลูชันสำรองข้อมูลที่ไม่ใช่อาแจ็กซ์สำหรับผู้ใช้ที่ไม่มีจาวาสคริปต์คุณจะต้องแก้ไขส่วนหัวฝั่งเซิร์ฟเวอร์เหล่านั้นให้ถูกต้อง นี่เป็นไปไม่ได้แม้ว่าฉันจะเข้าใจผู้ที่ยอมแพ้ก็ตาม)

ฉันแน่ใจว่ามีคำถามอื่นเกี่ยวกับ SO ที่จะให้ส่วนหัวแบบเต็มที่เหมาะสม ฉันไม่เชื่อว่าการตอบกลับของหนูจะครอบคลุมพื้นฐานทั้งหมด 100%


3

สำหรับคนที่คุณใช้cacheตัวเลือก$.ajaxSetup()บนมือถือ Safari ดูเหมือนว่าคุณอาจต้องใช้การประทับเวลาสำหรับ POSTs เนื่องจาก Safari มือถือนั้นแคชด้วยเช่นกัน ตามเอกสารประกอบของ$.ajax()(ซึ่งคุณได้รับคำแนะนำจาก$.ajaxSetup()):

การตั้งค่าแคชเป็นเท็จจะทำงานได้อย่างถูกต้องกับคำขอ HEAD และ GET มันทำงานได้โดยการผนวก "_ = {timestamp}" กับพารามิเตอร์ GET ไม่จำเป็นต้องใช้พารามิเตอร์สำหรับคำขอประเภทอื่นยกเว้นใน IE8 เมื่อทำการ POST กับ URL ที่ GET ร้องขอไปแล้ว

ดังนั้นการตั้งค่าตัวเลือกนั้นเพียงอย่างเดียวจะไม่ช่วยคุณในกรณีที่ฉันกล่าวถึงข้างต้น


2

โดยทั่วไปเพียงเพิ่มcache:false;ใน Ajax ที่คุณคิดว่าเนื้อหาจะเปลี่ยนไปตามความคืบหน้า และสถานที่ที่เนื้อหาจะไม่เปลี่ยนแปลงคุณสามารถละเว้นสิ่งนี้ได้ ด้วยวิธีนี้คุณจะได้รับการตอบสนองใหม่ทุกครั้ง


2

Ajax Caching ของ Internet Explorer: คุณจะทำอะไรกับมัน แนะนำสามแนวทาง:

  1. เพิ่มโทเค็นการจับแคชลงในสตริงแบบสอบถามเช่น? date = [timestamp] ใน jQuery และ YUI คุณสามารถบอกให้พวกเขาทำสิ่งนี้โดยอัตโนมัติ
  2. ใช้ POST แทน GET
  3. ส่งส่วนหัวตอบกลับ HTTP ที่ห้ามเบราว์เซอร์เพื่อแคชโดยเฉพาะ

2

ตอนนี้คุณสามารถทำได้โดยการเปิด / ปิดตัวเลือกแคชในคำขอ ajax ของคุณเช่นนี้

$(function () {
    var url = 'your url goes here';
    $('#ajaxButton').click(function (e) {
        $.ajax({
            url: url,
            data: {
                test: 'value'
            },
                cache: true, //cache enabled, false to reverse
                complete: doSomething
            });
        });
    });
    //ToDo after ajax call finishes
    function doSomething(data) {
        console.log(data);
    }
});

3
หลังจาก 6 ปีคุณก็ตอบสนองแบบเดียวกับที่โจนาธาน? ಠ_ಠ
redent84

ผู้คนสามารถบอกได้ว่าเป็นเวลา 6 ปีหลังจากที่โพสต์คำถาม และคำตอบของฉันสำหรับคำถามนั้นแตกต่างจากคนอื่นไม่พูดถึงมันเป็นคำตอบที่ถูกต้อง การตอบคำถามดังกล่าวไม่ใช่สำหรับ "ผู้ถาม" สำหรับชุมชนและผู้เริ่มต้น! ขอบคุณที่เพิ่มคำอธิบายไว้แล้ว!
Omar El Don

และสิ่งที่แตกต่างระหว่างคุณและคนที่stackoverflow.com/a/735084/469218 ?
redent84

อาจจะเป็นความชัดเจนจากมุมมองของผู้เริ่มต้นถามคำถามดังกล่าว !!
Omar El Don

1

หากคุณใช้ IE 9 คุณจำเป็นต้องใช้สิ่งต่อไปนี้ด้านหน้าคำจำกัดความของคลาสคอนโทรลเลอร์ของคุณ:

[OutputCache (NoStore = true, Duration = 0, VaryByParam = "*")]

TestController ระดับสาธารณะ: ควบคุม

นี่จะเป็นการป้องกันไม่ให้เบราว์เซอร์ทำการแคช

รายละเอียดเกี่ยวกับลิงค์นี้: http://dougwilsonsa.wordpress.com/2011/04/29/disabling-ie9-ajax-response-caching-asp-net-mvc-3-jquery/

ที่จริงแล้วสิ่งนี้สามารถแก้ไขปัญหาของฉันได้


1

ดังที่ @Athasach กล่าวไว้ตามเอกสาร jQuery $.ajaxSetup({cache:false})จะไม่สามารถทำงานได้นอกเหนือจากคำขอ GET และ HEAD

คุณดีกว่าส่งกลับ Cache-Control: no-cacheหัวจากเซิร์ฟเวอร์ของคุณกลับไป มันให้แยกความกังวลที่สะอาด

แน่นอนว่าสิ่งนี้จะไม่ทำงานสำหรับ URL บริการที่คุณไม่ได้อยู่ในโครงการของคุณ ในกรณีดังกล่าวคุณอาจพิจารณาการให้บริการของบุคคลที่สามจากรหัสเซิร์ฟเวอร์แทนที่จะเรียกจากรหัสลูกค้า


1

หากคุณใช้. net ASP MVC ให้ปิดใช้งานการแคชกับแอ็คชันคอนโทรลเลอร์โดยการเพิ่มแอ็ตทริบิวต์ต่อไปนี้บนฟังก์ชัน end point:

[OutputCacheAttribute(VaryByParam = "*", Duration = 0, NoStore = true)]

คุณช่วยอธิบายเพิ่มเติมได้ไหม อาร์เรย์นั้นเกี่ยวข้องกับ AJAX อย่างไร
Nico Haase

มันไม่ได้เป็นอาร์เรย์มันเป็นคุณสมบัติของ MVC Controller Action
Marius

0

เพิ่มส่วนหัว

headers: {
                'Cache-Control':'no-cache'
            }

กรุณาเพิ่มคำอธิบายบางอย่างเพื่อให้คำตอบของคุณเช่นที่คนอื่น ๆ สามารถเรียนรู้จากมัน - ที่ควรส่วนหัวดังกล่าวจะเพิ่ม?
Nico Haase

-3

ต่อท้ายMath.random() URL คำขอ


2
สิ่งนี้จะให้ผลลัพธ์ที่ไม่เสถียร
aj.toulan

Math.random จะทำหน้าที่เป็น prameter เท่านั้นเช่น url? _ = [Math.Random ()] ไม่มีอะไรเกี่ยวข้องกับผลลัพธ์ที่ไม่เสถียร
xiaoyifang

4
ฉันเข้าใจสิ่งที่คุณกำลังทำ ฉันแค่แสดงความคิดเห็นว่าบางครั้ง Math.Random () จะให้หมายเลขเดียวกันสองครั้ง หากคุณกรอกระบบของคุณด้วยความไม่แน่นอนระบบจะเพิ่มเฉพาะอีกระบบ
aj.toulan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.