Dlaczego otrzymuję żądanie OPTIONS zamiast żądania GET?

288
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.js" type="text/javascript"></script>
<script>
$.get("http://example.com/", function(data) {
     alert(data);
});
</script>

wysyła żądanie OPTIONS do tego adresu URL, a następnie oddzwanianie nigdy nie jest wywoływane z niczym.

Jeśli nie jest to domena wielodomenowa, działa dobrze.

Czy jQuery nie powinien po prostu wywoływać <script>węzła, a następnie wywoływać oddzwanianie po załadowaniu? Rozumiem, że nie będę w stanie uzyskać wyniku (ponieważ jest to dziedzina między domenami), ale to jest OK; Chcę tylko, żeby połączenie się zakończyło. Czy to błąd, czy robię coś złego?

Paul Tarjan
źródło
2
Może to być między domenami. Np. Jeśli znajdujesz się w pliku Plik: // PATH_TO_WEBSITE zamiast używać localhost /
WEBSITE_LINK

Odpowiedzi:

262

Według MDN ,

Wstępnie sprawdzone wnioski

W przeciwieństwie do prostych żądań (omówionych powyżej), wnioski „wstępnie sprawdzone” najpierw wysyłają nagłówek żądania OPCJE HTTP do zasobu w innej domenie, w celu ustalenia, czy faktyczne żądanie jest bezpieczne do wysłania. Żądania między witrynami są wstępnie sprawdzane w ten sposób, ponieważ mogą mieć wpływ na dane użytkownika. W szczególności żądanie jest wstępnie sprawdzane, jeśli:

  • Wykorzystuje metody inne niż GET lub POST. Ponadto, jeśli POST jest używany do wysyłania danych żądania z typem treści innym niż application / x-www-form-urlencoded, multipart / form-data lub text / plain, np. Jeśli żądanie POST wysyła ładunek XML do serwera przy użyciu application / xml lub text / xml, żądanie jest wstępnie sprawdzane.
  • Ustawia niestandardowe nagłówki w żądaniu (np. Żądanie używa nagłówka, takiego jak X-PINGOTHER)
arturgrigor
źródło
43
to naprawiło nasz problem, zmiana z „application / json” na „text / plain” zatrzymała okropne żądanie opcji
Keeno
10
nie rozumiem, dlaczego przeglądarka żąda metodą OPTIONS tylko po to, by sprawdzić, czy rzeczywiste żądanie jest bezpieczne do wysłania. ale w jakim sensie? mam na myśli, że serwer może również nakładać ograniczenia z pewnymi nagłówkami odpowiedzi, więc dlaczego jest to potrzebne?
hardik
11
@hardik Pamiętaj, że dodając CORS, potencjalnie akceptujesz żądania od wszystkich osób, w których mogłyby one manipulować danymi na twoim serwerze poprzez żądania (POST, PUT, DELETE itp.). W takich sytuacjach, na przykład podczas korzystania z niestandardowych nagłówków, przeglądarka najpierw sprawdza na serwerze, czy serwer jest gotowy zaakceptować żądanie, zanim je wyśle, ponieważ wysyłanie niezamówionych żądań do serwera może być naprawdę niebezpieczne dla danych, a także punkt w przeglądarce wysyłający potencjalnie duże ładunki, jeśli serwer nie chce ich zaakceptować, stąd sprawdzenie OPCJI przed lotem.
davidnknight
6
@davidnknight, jeśli wysyłanie danych na serwer może być niebezpieczne, co oznacza, że ​​serwer może zostać przejęty, wtedy oczywiście złośliwy serwer zareaguje na twoje żądanie OPTIONS słowem „Jasne, wyślij to wszystko!”. Jak to jest bezpieczeństwo? (szczere pytanie)
Matt
3
„Żądania inspekcji wstępnej nie są kwestią bezpieczeństwa. Raczej nie zmieniają zasad”. - Zobacz odpowiedź na pytanie, co jest motywacją do
składania
9

Jeśli próbujesz dokonać POST

Upewnij się, że JSON.stringifydane formularza i wyślij jako text/plain.

<form id="my-form" onSubmit="return postMyFormData();">
    <input type="text" name="name" placeholder="Your Name" required>
    <input type="email" name="email" placeholder="Your Email" required>
    <input type="submit" value="Submit My Form">
</form>

function postMyFormData() {

    var formData = $('#my-form').serializeArray();
    formData = formData.reduce(function(obj, item) {
        obj[item.name] = item.value;
        return obj;
    }, {});
    formData = JSON.stringify(formData);

    $.ajax({
        type: "POST",
        url: "https://website.com/path",
        data: formData,
        success: function() { ... },
        dataType: "text",
        contentType : "text/plain"
    });
}
Derek Soike
źródło
2

Nie wierzę, że jQuery po prostu wykona żądanie JSONP, jeśli otrzyma taki adres URL. Wykona jednak żądanie JSONP, gdy powiesz mu, jakiego argumentu użyć do wywołania zwrotnego:

$.get("http://metaward.com/import/http://metaward.com/u/ptarjan?jsoncallback=?", function(data) {
     alert(data);
});

Wykorzystanie tego argumentu (który nie musi być nazywany „jsoncallback”) zależy wyłącznie od skryptu odbierającego, więc w tym przypadku funkcja nigdy nie zostanie wywołana. Ale skoro powiedziałeś, że chcesz, aby skrypt w metaward.com był wykonywany, to by się udało.

VoteyDisciple
źródło
czy MOJE oddzwanianie nadal zostanie powiadomione, że element skryptu został w pełni załadowany? Chcę tylko upewnić się, że aktualizacja się wydarzyła, zanim zapytam o nią interfejs API.
Paul Tarjan,
Zrobisz to, jeśli skrypt odbierający obsługuje JSONP i jest skłonny wywołać zidentyfikowaną funkcję. Jeśli skrypt nic nie robi, ale generuje blok danych JSON bez żadnego innego zachowania, nie będziesz w stanie stwierdzić, kiedy zakończy się ładowanie. Jeśli konieczne jest określenie, kiedy ładowanie się zakończy, możesz rozważyć wdrożenie skryptu na własnym serwerze, który działa jak serwer proxy.
VoteyDisciple,
1

W rzeczywistości żądania AJAX między domenami (XMLHttp) są niedozwolone ze względów bezpieczeństwa (pomyśl o pobraniu „ograniczonej” strony internetowej po stronie klienta i odesłaniu jej z powrotem na serwer - byłoby to kwestią bezpieczeństwa).

Jedynym obejściem są połączenia zwrotne. Jest to: utworzenie nowego obiektu skryptu i skierowanie src na stronę JavaScript, która jest wywołaniem zwrotnym z wartościami JSON (myFunction ({data})), myFunction to funkcja, która coś robi z danymi (na przykład przechowuje je w zmiennej).

Adrián Navarro
źródło
1
tak, ale mogę załadować go do skryptu <skrypt src = ""> lub <img src = "">, a przeglądarka z przyjemnością go uderzy. Chcę tylko wiedzieć, kiedy jest w pełni załadowany, aby móc zapytać o wynik importu.
Paul Tarjan,
1

Wystarczy zmienić „application / json” na „text / plain” i nie zapomnij o JSON.stringify (żądanie):

var request = {Company: sapws.dbName, UserName: username, Password: userpass};
    console.log(request);
    $.ajax({
        type: "POST",
        url: this.wsUrl + "/Login",
        contentType: "text/plain",
        data: JSON.stringify(request),

        crossDomain: true,
    });
David Lopes
źródło
1

Miałem ten sam problem. Moją poprawką było dodanie nagłówków do mojego skryptu PHP, które są obecne tylko w środowisku deweloperskim.

Umożliwia to żądania między domenami:

header("Access-Control-Allow-Origin: *");

Mówi to żądaniu wstępnego sprawdzenia, że ​​klient może wysłać dowolne nagłówki:

header("Access-Control-Allow-Headers: *");

W ten sposób nie ma potrzeby modyfikowania żądania.

Jeśli w bazie danych deweloperów znajdują się wrażliwe dane, które mogą zostać ujawnione, możesz pomyśleć o tym dwa razy.

Fivebit
źródło
1

W moim przypadku problem nie był związany z CORS, ponieważ wysyłałem POST jQuery na ten sam serwer WWW. Dane to JSON, ale pominąłem parametr dataType: „json”.

Nie miałem (ani nie dodałem) parametru contentType, jak pokazano w odpowiedzi Davida Lopesa powyżej.

GarDavis
źródło
0

Wygląda na to, że Firefox i Opera (testowane również na Macu) nie podoba się temu cross-domena (ale Safari jest w porządku).

Może być konieczne wywołanie lokalnego kodu serwera, aby zawinąć zdalną stronę.

cześć
źródło
0

Udało mi się to naprawić za pomocą następujących nagłówków

Access-Control-Allow-Origin
Access-Control-Allow-Headers
Access-Control-Allow-Credentials
Access-Control-Allow-Methods

Jeśli korzystasz z Nodejs, oto kod, który możesz skopiować / wkleić.

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin','*');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  res.header('Access-Control-Allow-Credentials', true);
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH');
  next();
});
obai
źródło