Jak sprawić, by działało żądanie udostępnienia zasobów pochodzących z różnych źródeł (CORS)


216

Mam komputer w lokalnej sieci LAN (machineA), który ma dwa serwery WWW. Pierwszy to wbudowany w XBMC (na porcie 8080) i wyświetla naszą bibliotekę. Drugi serwer to skrypt Pythona CherryPy (port 8081), którego używam do wyzwalania konwersji plików na żądanie. Konwersja pliku jest wyzwalana przez żądanie AJAX POST ze strony obsługiwanej z serwera XBMC.

  • Idź do http: // machineA: 8080, która wyświetla bibliotekę
  • Wyświetlana jest biblioteka
  • Użytkownik klika link „Konwertuj”, który wydaje następujące polecenie -

jQuery Ajax Request

$.post('http://machineA:8081', {file_url: 'asfd'}, function(d){console.log(d)})
  • Przeglądarka wysyła żądanie OPCJE HTTP z następującymi nagłówkami;

Żądanie nagłówka - OPCJE

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
  • Serwer odpowiada następującymi informacjami;

Nagłówek odpowiedzi - OPCJE (STATUS = 200 OK)

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
  • Następnie rozmowa się kończy. Przeglądarka powinna teoretycznie wysłać żądanie POST, ponieważ serwer odpowiedział poprawnymi (?) Nagłówkami CORS (Access-Control-Allow-Origin: *)

Do rozwiązywania problemów wydałem również to samo polecenie $ .post z http://jquery.com . To jest miejsce, w którym jestem zaskoczony, z jquery.com, żądanie postu działa, żądanie OPTIONS jest wysyłane po POST. Nagłówki tej transakcji znajdują się poniżej;

Żądanie nagłówka - OPCJE

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

Nagłówek odpowiedzi - OPCJE (STATUS = 200 OK)

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

Żądanie nagłówka - 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

Nagłówek odpowiedzi - POST (STATUS = 200 OK)

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

Nie mogę zrozumieć, dlaczego to samo żądanie zadziała z jednej strony, ale nie z drugiej. Mam nadzieję, że ktoś będzie w stanie wskazać, czego mi brakuje. Dzięki za pomoc!


Czy CORS jest potrzebny, jeśli oba serwery WWW znajdują się na tym samym komputerze?
jdigital

8
Według mojej najlepszej wiedzy jest to żądanie CORS ze względu na inny port. Ponadto żądanie OPTIONS wskazuje, że przeglądarka traktuje je jako żądanie CORS
James

Odpowiedzi:


157

W końcu natknąłem się na ten link „ Żądanie CORS POST działa ze zwykłego javascript, ale dlaczego nie z jQuery? ”, Który zauważa, że ​​jQuery 1.5.1 dodaje

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

nagłówek do wszystkich żądań CORS. jQuery 1.5.2 tego nie robi. Ponadto, zgodnie z tym samym pytaniem, ustawienie nagłówka odpowiedzi serwera na

Access-Control-Allow-Headers: *

nie pozwala na kontynuowanie odpowiedzi. Musisz upewnić się, że nagłówek odpowiedzi zawiera w szczególności wymagane nagłówki. to znaczy:

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

70

ŻĄDANIE:

 $.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");
            }
        });

ODPOWIEDŹ:

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

return response

3
jeśli serwer nie akceptuje pochodzenia krzyżowego, crossdomain = true nie musi rozwiązać problemu. przy użyciu dataType: „jsonp” i ustawieniu oddzwaniania jak jsonpCallback: lepszym pomysłem byłoby użycie „response”. Zobacz także: api.jquery.com/jquery.ajax
BonifatiusK

14

Rozwiązałem swój problem podczas korzystania z interfejsu API Google Matrix Distance, ustawiając nagłówek żądania za pomocą Jquery ajax. spójrz poniżej.

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

      });

Uwaga, co dodałem w ustawieniach
**

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

**
Mam nadzieję, że to pomoże.


to jedyne rozwiązanie, które działało dla nas bez zmiany czegokolwiek po stronie serwera ... thanx miracool
Timsta

1
@ Timsta, cieszę się, że moja sugestia zadziałała dla Ciebie. Wdzięczny za przepełnienie stosu. Miłego dnia.
Miracool,

13

Trochę mi zajęło znalezienie rozwiązania.

Jeśli Twój serwer odpowiada poprawnie, a żądanie jest problemem, dodaj withCredentials: truedo xhrFieldsżądania:

$.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
    }
});

Uwaga: wymagane jest jQuery> = 1.5.1


wersja jquery ok? ustawiłeś withCredentials: true? na pewno masz odpowiednie nagłówki?
Dekel

Tak, 1withCredential: true , jquery version: 3.2.1`. W rzeczywistości działa za pośrednictwem listonosza, ale nie przechodzi przez przeglądarkę Chrome
Mox Shah

1
Jestem prawie pewien, że listonosz nie powinien mieć problemów z CORS, ponieważ nie jest to przeglądarka, a jego zachowanie jest inne. Jesteś pewien, że masz prawidłowe i odpowiednie nagłówki wysłane z serwera do klienta? Znowu - zauważ, że ta zmiana nie wystarczy. Musisz upewnić się, że odpowiedź serwera zawiera prawidłowe nagłówki.
Dekel

Czy możesz mi powiedzieć, jakie nagłówki odpowiedzi są wymagane na serwerze?
Mox Shah,

@MoxShah to zupełnie inne pytanie i jest tam wiele zasobów :)
Dekel

9

Cóż, walczyłem z tym problemem przez kilka tygodni.

Najłatwiejszym, najbardziej zgodnym i niehackującym sposobem jest prawdopodobnie skorzystanie z API JavaScript dostawcy, który nie wykonuje wywołań przeglądarki i obsługuje żądania Cross Origin.

Np. Facebook JavaScript API i Google JS API.

Jeśli Twój dostawca interfejsu API nie jest aktualny i nie obsługuje w odpowiedzi nagłówka „Origin Origin Resource Origin” * i nie ma interfejsu API JS (tak, mówię o tobie Yahoo), pojawia się jedna z trzech opcji -

  1. Użycie jsonp w twoich żądaniach, które dodaje funkcję zwrotną do twojego adresu URL, gdzie możesz obsłużyć swoją odpowiedź. Zastrzeżenie to spowoduje zmianę adresu URL żądania, więc serwer interfejsu API musi być wyposażony do obsługi wywołania zwrotnego? Na końcu adresu URL.

  2. Wyślij żądanie na serwer API, który jest kontrolerem przez Ciebie i znajduje się w tej samej domenie co klient lub ma włączone współużytkowanie zasobów między źródłami, z którego możesz proxy proxy do serwera API innej firmy.

  3. Prawdopodobnie najbardziej przydatny w przypadkach, gdy wysyłasz żądania OAuth i potrzebujesz obsługi interakcji z użytkownikiem Haha! window.open('url',"newwindowname",'_blank', 'toolbar=0,location=0,menubar=0')


4

Używanie tego w połączeniu z Laravelem rozwiązało mój problem. Wystarczy dodać ten nagłówek do żądania jquery Access-Control-Request-Headers: x-requested-withi upewnić się, że odpowiedź po stronie serwera ma ustawiony ten nagłówek Access-Control-Allow-Headers: *.


6
Nie ma powodu, aby ręcznie dodawać nagłówki CORS do żądania. Przeglądarka zawsze doda do żądania nagłówki prop CORS.
Ray Nicholus,

1
Prawdziwym wyzwaniem jest nakłonienie serwera do odpowiedzi z poprawnym Access-Control-Allow-Headersi dostarczeniem poprawnego JQ Access-Control-Request-Headers(plus dowolne dodane przez kod), z których żadna nie może być symbolami wieloznacznymi. do wysadzenia przed lotem potrzebny jest tylko jeden „zły” nagłówek, np. użycie If-None-Matchwarunkowego GET, jeśli serwer nie ma tego na liście.
escape-llc

1

Z jakiegoś powodu pytanie dotyczące żądań GET zostało połączone z tym, więc odpowiem na to tutaj.

Ta prosta funkcja asynchronicznie otrzyma odpowiedź HTTP ze strony obsługującej CORS. Jeśli go uruchomisz, zobaczysz, że tylko strona z właściwymi nagłówkami zwraca status 200, jeśli jest dostępny poprzez XMLHttpRequest - niezależnie od tego, czy użyto GET, czy POST. Nic nie można zrobić po stronie klienta, aby obejść ten problem, z wyjątkiem użycia JSONP, jeśli potrzebujesz tylko obiektu json.

Poniższe elementy można łatwo zmodyfikować, aby dane były przechowywane w obiekcie 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

Miałem dokładnie ten sam problem, w którym jquery ajax dawało mi tylko problemy z corsami w żądaniach postów, w których żądania get działały dobrze - zmęczyłem wszystko powyżej bez rezultatów. Miałem właściwe nagłówki na moim serwerze itp. Przejście na używanie XMLHTTPRequest zamiast jquery natychmiast rozwiązało mój problem. Bez względu na to, której wersji jquery użyłem, nie naprawiłem tego. Pobieranie działa również bez problemów, jeśli nie potrzebujesz zgodności z poprzednimi przeglądarkami.

        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)

Mam nadzieję, że pomoże to każdemu, kto ma te same problemy.


1

Oto podsumowanie tego, co zadziałało dla mnie:

Zdefiniuj nową funkcję (opakowaną w $.ajaxcelu uproszczenia):

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

Stosowanie:

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

Współpracuje również z .done, .failitp:

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

Po stronie serwera (w tym przypadku, gdzie hostowany jest przyklad.com), ustaw te nagłówki (dodano przykładowy kod w 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);

To jedyny znany mi sposób, aby naprawdę POST między domenami z JS.

JSONP konwertuje POST na GET, który może wyświetlać poufne informacje w logach serwera.


0

Jeśli z jakichś powodów przy dodawaniu nagłówków lub ustawianiu zasad kontroli nadal nic nie dochodzi, możesz rozważyć użycie Apache ProxyPass…

Na przykład w jednym <VirtualHost>korzystającym z protokołu SSL dodaj dwie następujące dyrektywy:

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

Upewnij się, że załadowane są następujące moduły Apache (załaduj je za pomocą a2enmod):

  • pełnomocnik
  • proxy_connect
  • proxy_http

Oczywiście musisz zmienić adres URL żądań AJAX, aby korzystać z serwera proxy Apache…


-3

Jest trochę późno na imprezę, ale zmagam się z tym od kilku dni. Jest to możliwe i żadna z odpowiedzi, które tu znalazłem, nie zadziałała. To jest zwodniczo proste. Oto połączenie .ajax:

    <!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>

Oto php po stronie serwera:

    <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
Warto dodać, że nagłówek Origin jest nagłówkiem żądania, a nie nagłówkiem odpowiedzi. Twój skrypt php nie powinien go ustawiać.
Makaron

Hmmph. Wzbudziło to moje nadzieje, a potem zniszczyło je, gdy zobaczyłem, że tworzysz AJAX „GET” w swoim kodzie, gdy OP dość wyraźnie powiedział, że próbuje UNIKAĆ używając „GET” i chce użyć „POST”.
Steve Sauder

Nagłówki CORS działają tak samo bez względu na czasownik. Jesteśmy w stanie wywoływać wszystkie czasowniki za $.ajax()pomocą poprawnie skonfigurowanego serwera. Najtrudniejszą częścią jest uzyskanie Access-Control-Request-Headersprawidłowego, ale nawet to nie jest zbyt trudne. Jak zauważono w poprzednich plakatach, nie może to być symbol wieloznaczny, ale biała lista nagłówków.
escape-llc
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.