วิธีรับคำขอโพสต์การแชร์ทรัพยากรข้ามแหล่ง (CORS) ทำงาน


216

ฉันมีเครื่องบน LAN ท้องถิ่นของฉัน (machineA) ที่มีเว็บเซิร์ฟเวอร์สองเครื่อง ที่แรกก็คือที่สร้างขึ้นใน XBMC (บนพอร์ต 8080) และแสดงห้องสมุดของเรา เซิร์ฟเวอร์ตัวที่สองเป็นสคริปต์ python ของ CherryPy (พอร์ต 8081) ที่ฉันใช้เพื่อทริกเกอร์การแปลงไฟล์แบบออนดีมานด์ การแปลงไฟล์จะถูกเรียกใช้โดยคำขอ AJAX POST จากหน้าที่ให้บริการจากเซิร์ฟเวอร์ XBMC

  • ไปที่http: // machineA: 8080ซึ่งแสดงไลบรารี่
  • ห้องสมุดจะปรากฏขึ้น
  • ผู้ใช้คลิกที่ลิงค์ 'แปลง' ซึ่งออกคำสั่งดังต่อไปนี้ -

คำขอ jQuery Ajax

$.post('http://machineA:8081', {file_url: 'asfd'}, function(d){console.log(d)})
  • เบราว์เซอร์ออกคำขอ HTTP OPTIONS กับส่วนหัวต่อไปนี้

ส่วนหัวขอ - ตัวเลือก

Host: machineA:8081
User-Agent: ... Firefox/4.01
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Origin: http://machineA:8080
Access-Control-Request-Method: POST
Access-Control-Request-Headers: x-requested-with
  • เซิร์ฟเวอร์ตอบสนองด้วยสิ่งต่อไปนี้;

ส่วนหัวการตอบสนอง - ตัวเลือก (สถานะ = 200 ตกลง)

Content-Length: 0
Access-Control-Allow-Headers: *
Access-Control-Max-Age: 1728000
Server: CherryPy/3.2.0
Date: Thu, 21 Apr 2011 22:40:29 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Content-Type: text/html;charset=ISO-8859-1
  • บทสนทนาก็จะหยุด ในทางทฤษฎีเบราว์เซอร์ควรออกคำขอ POST เมื่อเซิร์ฟเวอร์ตอบกลับด้วยส่วนหัว CORS ที่ถูกต้อง (?) - Access-Control-Allow-Origin: *

สำหรับการแก้ไขปัญหาผมยังได้ออก $ .post เดียวกันคำสั่งจากhttp://jquery.com นี่คือที่ฉันนิ่งงันจาก jquery.com การร้องขอโพสต์ทำงานการร้องขอ OPTIONS ถูกส่งโดย POST ส่วนหัวของธุรกรรมนี้อยู่ด้านล่าง

ส่วนหัวขอ - ตัวเลือก

Host: machineA:8081
User-Agent: ... Firefox/4.01
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Origin: http://jquery.com
Access-Control-Request-Method: POST

ส่วนหัวการตอบสนอง - ตัวเลือก (สถานะ = 200 ตกลง)

Content-Length: 0
Access-Control-Allow-Headers: *
Access-Control-Max-Age: 1728000
Server: CherryPy/3.2.0
Date: Thu, 21 Apr 2011 22:37:59 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Content-Type: text/html;charset=ISO-8859-1

ส่วนหัวคำขอ - POST

Host: machineA:8081
User-Agent: ... Firefox/4.01
Accept: */*
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://jquery.com/
Content-Length: 12
Origin: http://jquery.com
Pragma: no-cache
Cache-Control: no-cache

ส่วนหัวการตอบสนอง - POST (สถานะ = 200 ตกลง)

Content-Length: 32
Access-Control-Allow-Headers: *
Access-Control-Max-Age: 1728000
Server: CherryPy/3.2.0
Date: Thu, 21 Apr 2011 22:37:59 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Content-Type: application/json

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


CORS จำเป็นหรือไม่ถ้าเว็บเซิร์ฟเวอร์ทั้งสองอยู่ในเครื่องเดียวกัน
jdigital

8
เพื่อความรู้ที่ดีที่สุดของฉันมันเป็นคำขอ CORS เพราะพอร์ตที่แตกต่างกัน นอกจากนี้ยังมีการร้องขอ OPTIONS แสดงให้เห็นว่าเบราว์เซอร์จะรักษามันเป็น ธ ขอ
เจมส์

คำตอบ:


157

ในที่สุดฉันก็สะดุดกับลิงค์นี้ " คำขอ CORS POST ทำงานได้จากจาวาสคริปต์ธรรมดา แต่ทำไมไม่ใช้ jQuery? " ซึ่งตั้งข้อสังเกตว่า jQuery 1.5.1 เพิ่ม

 Access-Control-Request-Headers: x-requested-with

ส่วนหัวของการร้องขอ CORS ทั้งหมด jQuery 1.5.2 ไม่ได้ทำเช่นนี้ นอกจากนี้ตามคำถามเดียวกันให้ตั้งค่าส่วนหัวการตอบกลับของเซิร์ฟเวอร์

Access-Control-Allow-Headers: *

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

Access-Control-Allow-Headers: x-requested-with 

70

คำขอ:

 $.ajax({
            url: "http://localhost:8079/students/add/",
            type: "POST",
            crossDomain: true,
            data: JSON.stringify(somejson),
            dataType: "json",
            success: function (response) {
                var resp = JSON.parse(response)
                alert(resp.status);
            },
            error: function (xhr, status) {
                alert("error");
            }
        });

การตอบสนอง:

response = HttpResponse(json.dumps('{"status" : "success"}'))
response.__setitem__("Content-type", "application/json")
response.__setitem__("Access-Control-Allow-Origin", "*")

return response

3
หากเซิร์ฟเวอร์ไม่ยอมรับ cross origin โดเมน crossdomain = true ไม่จำเป็นต้องแก้ปัญหา ใช้ dataType: "jsonp" และการตั้งค่าการโทรกลับเช่น jsonpCallback: "การตอบสนอง" จะเป็นความคิดที่ดีกว่าในการทำเช่นนี้ ดูเพิ่มเติมที่: api.jquery.com/jquery.ajax
BonifatiusK

14

ฉันแก้ไขปัญหาของตัวเองเมื่อใช้ google distance matrix API โดยตั้งค่าส่วนหัวคำขอของฉันด้วย Jquery ajax ลองดูด้านล่าง

var settings = {
          'cache': false,
          'dataType': "jsonp",
          "async": true,
          "crossDomain": true,
          "url": "https://maps.googleapis.com/maps/api/distancematrix/json?units=metric&origins=place_id:"+me.originPlaceId+"&destinations=place_id:"+me.destinationPlaceId+"&region=ng&units=metric&key=mykey",
          "method": "GET",
          "headers": {
              "accept": "application/json",
              "Access-Control-Allow-Origin":"*"
          }
      }

      $.ajax(settings).done(function (response) {
          console.log(response);

      });

สังเกตสิ่งที่ฉันเพิ่มในการตั้งค่า
**

"headers": {
          "accept": "application/json",
          "Access-Control-Allow-Origin":"*"
      }

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


นี่เป็นทางออกเดียวที่ทำงานให้กับเราโดยไม่ต้องเปลี่ยนอะไรบนฝั่งเซิร์ฟเวอร์ ... thanx miracool
Timsta

1
@ Timsta มีความสุขข้อเสนอแนะของฉันทำงานให้คุณ ขอบคุณที่กองซ้อนกันด้วย ขอให้มีความสุขมาก ๆ ในวันนี้นะ
Miracool

13

เอาเวลาฉันหาวิธีแก้ปัญหา

ในกรณีที่การตอบสนองเซิร์ฟเวอร์ของคุณได้อย่างถูกต้องและขอเป็นปัญหาที่คุณควรเพิ่มwithCredentials: trueไปxhrFieldsในคำขอ:

$.ajax({
    url: url,
    type: method,
    // This is the important part
    xhrFields: {
        withCredentials: true
    },
    // This is the important part
    data: data,
    success: function (response) {
        // handle the response
    },
    error: function (xhr, status) {
        // handle errors
    }
});

หมายเหตุ: จำเป็นต้องใช้ jQuery> = 1.5.1


รุ่น jquery ตกลงหรือไม่ คุณตั้งค่าwithCredentials: trueหรือไม่ คุณแน่ใจว่าคุณมีส่วนหัวที่เกี่ยวข้องหรือไม่
Dekel

ใช่ 1withCredential: , รุ่น jquery จริง: 3.2.1` อันที่จริงมันทำงานผ่านบุรุษไปรษณีย์ แต่มันไม่ผ่านเบราว์เซอร์โครม
Mox Shah

1
ฉันเกือบแน่ใจว่าบุรุษไปรษณีย์ไม่ควรมีปัญหา CORS เพราะมันไม่ใช่เบราว์เซอร์และพฤติกรรมนั้นแตกต่างกัน คุณแน่ใจว่าคุณมีส่วนหัวที่ถูกต้องและเกี่ยวข้องส่งจากเซิร์ฟเวอร์ไปยังลูกค้าหรือไม่ อีกครั้ง - โปรดทราบว่าการเปลี่ยนแปลงนี้ไม่เพียงพอ คุณต้องแน่ใจว่าเซิร์ฟเวอร์ตอบสนองกับส่วนหัวที่ถูกต้อง
Dekel

คุณช่วยบอกฉันหน่อยได้ไหมว่าส่วนหัวตอบสนองที่ต้องการบนเซิร์ฟเวอร์คืออะไร?
Mox Shah

@MoxShah นี่เป็นคำถามที่แตกต่างกันที่สมบูรณ์และมีจำนวนมากของทรัพยากรที่มี :)
Dekel

9

ดีฉันต่อสู้กับปัญหานี้สองสามสัปดาห์

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

เช่น Facebook JavaScript API และ Google JS API

ในกรณีที่ผู้ให้บริการ API ของคุณไม่เป็นปัจจุบันและไม่สนับสนุนส่วนหัว Cross Origin Resource Origin '*' ในการตอบกลับและไม่มี JS api (ใช่ฉันกำลังพูดถึงคุณ Yahoo) คุณหลงทางด้วยหนึ่งในสามตัวเลือก -

  1. ใช้ jsonp ในคำขอของคุณซึ่งเพิ่มฟังก์ชั่นการโทรกลับไปยัง URL ของคุณซึ่งคุณสามารถจัดการกับการตอบสนองของคุณ ข้อแม้นี้จะเปลี่ยน URL คำขอเพื่อให้เซิร์ฟเวอร์ API ของคุณต้องติดตั้งเพื่อจัดการ? callback = ที่ท้าย URL

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

  3. น่าจะมีประโยชน์มากที่สุดในกรณีที่คุณร้องขอ OAuth และต้องจัดการกับผู้ใช้ฮ่าฮ่า! window.open('url',"newwindowname",'_blank', 'toolbar=0,location=0,menubar=0')


4

การใช้สิ่งนี้ร่วมกับ Laravel ช่วยแก้ปัญหาของฉันได้ เพียงเพิ่มส่วนหัวนี้ลงในคำขอ jquery ของคุณAccess-Control-Request-Headers: x-requested-withและตรวจสอบให้แน่ใจว่าการตอบสนองด้านเซิร์ฟเวอร์ของคุณมีชุดส่วนหัวAccess-Control-Allow-Headers: *นี้


6
ไม่มีเหตุผลที่จะเพิ่มส่วนหัว CORS ลงในคำขอด้วยตนเอง เบราว์เซอร์จะเพิ่มส่วนหัว prop CORS ให้กับคำขอของคุณ
เรย์นิโคลัส

1
ความท้าทายที่แท้จริงคือการให้เซิร์ฟเวอร์ตอบกลับด้วยความถูกต้องAccess-Control-Allow-Headersและ JQ ให้การแก้ไขที่ถูกต้องAccess-Control-Request-Headers(รวมถึงสิ่งที่คุณเพิ่มด้วยรหัส) ซึ่งไม่สามารถเป็นตัวแทน ใช้เวลาเพียงหนึ่งส่วนหัว "เลว" เพื่อระเบิดการบินล่วงหน้าเช่นใช้If-None-Matchสำหรับ GET แบบมีเงื่อนไขหากเซิร์ฟเวอร์ไม่มีรายการดังกล่าว
escape-llc

1

ด้วยเหตุผลบางอย่างคำถามเกี่ยวกับคำขอ GET ถูกรวมเข้ากับคำถามนี้ดังนั้นฉันจะตอบกลับที่นี่

ฟังก์ชั่นที่เรียบง่ายนี้จะได้รับการตอบกลับสถานะ HTTP แบบอะซิงโครนัสจากหน้าเปิดใช้งาน CORS หากคุณเรียกใช้คุณจะเห็นว่ามีเพียงเพจที่มีส่วนหัวที่เหมาะสมเท่านั้นที่ส่งคืนสถานะ 200 หากเข้าถึงผ่าน XMLHttpRequest - ไม่ว่าจะใช้ GET หรือ POST ไม่มีสิ่งใดที่สามารถทำได้ในฝั่งไคลเอ็นต์เพื่อหลีกเลี่ยงปัญหานี้ยกเว้นอาจใช้ JSONP หากคุณต้องการวัตถุ json

ข้อมูลต่อไปนี้สามารถแก้ไขได้อย่างง่ายดายเพื่อรับข้อมูลที่จัดเก็บในวัตถุ xmlHttpRequestObject:

function checkCorsSource(source) {
  var xmlHttpRequestObject;
  if (window.XMLHttpRequest) {
    xmlHttpRequestObject = new XMLHttpRequest();
    if (xmlHttpRequestObject != null) {
      var sUrl = "";
      if (source == "google") {
        var sUrl = "https://www.google.com";
      } else {
        var sUrl = "https://httpbin.org/get";
      }
      document.getElementById("txt1").innerHTML = "Request Sent...";
      xmlHttpRequestObject.open("GET", sUrl, true);
      xmlHttpRequestObject.onreadystatechange = function() {
        if (xmlHttpRequestObject.readyState == 4 && xmlHttpRequestObject.status == 200) {
          document.getElementById("txt1").innerHTML = "200 Response received!";
        } else {
          document.getElementById("txt1").innerHTML = "200 Response failed!";
        }
      }
      xmlHttpRequestObject.send();
    } else {
      window.alert("Error creating XmlHttpRequest object. Client is not CORS enabled");
    }
  }
}
<html>
<head>
  <title>Check if page is cors</title>
</head>
<body>
  <p>A CORS-enabled source has one of the following HTTP headers:</p>
  <ul>
    <li>Access-Control-Allow-Headers: *</li>
    <li>Access-Control-Allow-Headers: x-requested-with</li>
  </ul>
  <p>Click a button to see if the page allows CORS</p>
  <form name="form1" action="" method="get">
    <input type="button" name="btn1" value="Check Google Page" onClick="checkCorsSource('google')">
    <input type="button" name="btn1" value="Check Cors Page" onClick="checkCorsSource('cors')">
  </form>
  <p id="txt1" />
</body>
</html>


1

ฉันมีปัญหาเดียวกันที่ jquery ajax เพียงให้ฉันปัญหา cors ในคำขอโพสต์ที่ได้รับคำขอทำงานได้ดี - ฉันเหนื่อยทุกอย่างข้างต้นโดยไม่มีผลลัพธ์ ฉันมีส่วนหัวที่ถูกต้องในเซิร์ฟเวอร์ของฉัน ฯลฯ การเปลี่ยนไปใช้ XMLHTTPRequest แทน jquery แก้ไขปัญหาของฉันทันที ไม่ว่าฉันจะใช้ jQuery เวอร์ชันใดมันก็ไม่ได้แก้ไข การดึงข้อมูลยังทำงานได้โดยไม่มีปัญหาหากคุณไม่ต้องการความเข้ากันได้ของเบราว์เซอร์ย้อนหลัง

        var xhr = new XMLHttpRequest()
        xhr.open('POST', 'https://mywebsite.com', true)
        xhr.withCredentials = true
        xhr.onreadystatechange = function() {
          if (xhr.readyState === 2) {// do something}
        }
        xhr.setRequestHeader('Content-Type', 'application/json')
        xhr.send(json)

หวังว่านี่จะช่วยให้คนอื่นที่มีปัญหาเดียวกัน


1

นี่เป็นบทสรุปของสิ่งที่ได้ผลสำหรับฉัน:

กำหนดฟังก์ชั่นใหม่ (ห่อ$.ajaxเพื่อลดความซับซ้อน):

jQuery.postCORS = function(url, data, func) {
  if(func == undefined) func = function(){};
  return $.ajax({
    type: 'POST', 
    url: url, 
    data: data, 
    dataType: 'json', 
    contentType: 'application/x-www-form-urlencoded', 
    xhrFields: { withCredentials: true }, 
    success: function(res) { func(res) }, 
    error: function() { 
            func({}) 
    }
  });
}

การใช้งาน:

$.postCORS("https://example.com/service.json",{ x : 1 },function(obj){
      if(obj.ok) {
           ...
      }
});

ยังทำงานร่วมกับ.done, .failฯลฯ :

$.postCORS("https://example.com/service.json",{ x : 1 }).done(function(obj){
      if(obj.ok) {
           ...
      }
}).fail(function(){
    alert("Error!");
});

ฝั่งเซิร์ฟเวอร์ (ในกรณีนี้ที่โฮสต์ example.com) ตั้งค่าส่วนหัวเหล่านี้ (เพิ่มรหัสตัวอย่างบางส่วนใน PHP):

header('Access-Control-Allow-Origin: https://not-example.com');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 604800');
header("Content-type: application/json");
$array = array("ok" => $_POST["x"]);
echo json_encode($array);

นี่เป็นวิธีเดียวที่ฉันรู้ที่จะโพสต์ข้ามโดเมนจาก JS อย่างแท้จริง

JSONP แปลง POST เป็น GET ซึ่งอาจแสดงข้อมูลที่ละเอียดอ่อนในบันทึกของเซิร์ฟเวอร์


0

หากมีเหตุผลบางอย่างในขณะที่พยายามเพิ่มส่วนหัวหรือตั้งค่านโยบายการควบคุมที่คุณยังไม่ได้รับคุณอาจพิจารณาใช้ apache ProxyPass ...

ตัวอย่างเช่นในรายการ<VirtualHost>ที่ใช้ SSL เพิ่มสองคำสั่งต่อไปนี้:

SSLProxyEngine On
ProxyPass /oauth https://remote.tld/oauth

ตรวจสอบให้แน่ใจว่ามีการโหลดโมดูล apache ต่อไปนี้ (โหลดโดยใช้ a2enmod):

  • หนังสือมอบฉันทะ
  • proxy_connect
  • proxy_http

เห็นได้ชัดว่าคุณจะต้องเปลี่ยน URL คำขอ AJAX ของคุณเพื่อใช้ apache proxy ...


-3

งานนี้ช้าไปหน่อย แต่ฉันต้องดิ้นรนกับเรื่องนี้มาสองสามวันแล้ว เป็นไปได้และไม่มีคำตอบที่ฉันพบที่นี่ได้ทำงาน มันง่ายที่หลอกลวง นี่คือการโทร. jax:

    <!DOCTYPE HTML>
    <html>
    <head>
    <body>
     <title>Javascript Test</title>
     <script src="http://code.jquery.com/jquery-latest.min.js"></script>
     <script type="text/javascript">
     $(document).domain = 'XXX.com';
     $(document).ready(function () {
     $.ajax({
        xhrFields: {cors: false},
        type: "GET",
        url: "http://XXXX.com/test.php?email='steve@XXX.com'",
        success: function (data) {
           alert(data);
        },
        error: function (x, y, z) {
           alert(x.responseText + " :EEE: " + x.status);
        }
    });
    });
    </script> 
    </body>
    </html>

นี่คือ php ที่ฝั่งเซิร์ฟเวอร์:

    <html>
    <head>
     <title>PHP Test</title>
     </head>
    <body>
      <?php
      header('Origin: xxx.com');
      header('Access-Control-Allow-Origin:*');
      $servername = "sqlxxx";
      $username = "xxxx";
      $password = "sss";
      $conn = new mysqli($servername, $username, $password);
      if ($conn->connect_error) {
        die( "Connection failed: " . $conn->connect_error);
      }
      $sql = "SELECT email, status, userdata  FROM msi.usersLive";
      $result = $conn->query($sql);
      if ($result->num_rows > 0) {
      while($row = $result->fetch_assoc()) {
        echo $row["email"] . ":" . $row["status"] . ":" . $row["userdata"] .  "<br>";
      }
    } else {
      echo "{ }";
    }
    $conn->close();
    ?>
    </body>


1
สำหรับสิ่งที่คุ้มค่าส่วนหัว Origin เป็นส่วนหัวคำขอไม่ใช่ส่วนหัวการตอบกลับ สคริปต์ php ของคุณไม่ควรตั้งค่า
ก๋วยเตี๋ยว

Hmmph สิ่งนี้ทำให้ฉันมีความหวังมากขึ้นและเมื่อพวกเขาเห็นว่าคุณกำลังสร้าง AJAX "GET" ในรหัสของคุณเมื่อ OP ค่อนข้างชัดเจนว่าเขาพยายามหลีกเลี่ยงการใช้ "GET" และต้องการใช้ "POST"
Steve Sauder

ส่วนหัว CORS ทำงานเหมือนกันโดยไม่คำนึงถึงคำกริยาที่เกี่ยวข้อง เราสามารถเรียกใช้คำกริยาทั้งหมดผ่านทาง$.ajax()เซิร์ฟเวอร์ที่ตั้งค่าไว้อย่างถูกต้อง ส่วนที่ยากที่สุดคือการAccess-Control-Request-Headersแก้ไขให้ถูกต้อง แต่ก็ไม่ยากเกินไป ดังที่ระบุไว้โดยผู้โพสต์ก่อนหน้านี้จะต้องไม่เป็นตัวแทน แต่เป็นรายการที่อนุญาตของส่วนหัว
escape-llc
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.