Pourquoi est-ce que je reçois une demande OPTIONS au lieu d'une demande 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>

il fait une requête OPTIONS à cette URL, puis le rappel n'est jamais appelé avec quoi que ce soit.

Quand ce n'est pas du domaine croisé, cela fonctionne très bien.

JQuery ne devrait-il pas simplement passer l'appel avec un <script>nœud, puis faire le rappel lorsqu'il est chargé? Je comprends que je ne pourrai pas obtenir le résultat (car il s'agit d'un domaine croisé), mais c'est OK; Je veux juste que l'appel passe. Est-ce un bug ou est-ce que je fais quelque chose de mal?

Paul Tarjan
la source
2
Pourrait être cos de domaine croisé. Par exemple, si vous êtes sur votre fichier File: // PATH_TO_WEBSITE au lieu d'utiliser localhost / WEBSITE_LINK
James111

Réponses:

262

Selon MDN ,

Demandes de contrôle en amont

Contrairement aux demandes simples (décrites ci-dessus), les demandes "de contrôle en amont" envoient d'abord un en-tête de demande HTTP OPTIONS à la ressource de l'autre domaine, afin de déterminer si la demande réelle peut être envoyée en toute sécurité. Les demandes intersites sont contrôlées en amont comme ceci car elles peuvent avoir des implications sur les données des utilisateurs. En particulier, une demande est contrôlée en amont si:

  • Il utilise des méthodes autres que GET ou POST. En outre, si POST est utilisé pour envoyer des données de demande avec un type de contenu autre que application / x-www-form-urlencoded, multipart / form-data ou text / plain, par exemple si la demande POST envoie une charge utile XML au serveur en utilisant application / xml ou text / xml, la demande est alors contrôlée en amont.
  • Il définit des en-têtes personnalisés dans la demande (par exemple, la demande utilise un en-tête tel que X-PINGOTHER)
arturgrigor
la source
43
cela a résolu notre problème, le passage de "application / json" à "text / plain" a arrêté la demande d'options horrible
Keeno
10
ce que je ne comprends pas, c'est pourquoi le navigateur demande avec la méthode OPTIONS juste pour vérifier que la demande réelle est sûre à envoyer. mais dans quel sens? Je veux dire que le serveur peut également mettre des restrictions avec certains en-têtes de réponse, alors pourquoi cela est-il nécessaire?
hardik
11
@hardik N'oubliez pas qu'en ajoutant CORS, vous acceptez potentiellement les demandes de n'importe qui, dans lequel il pourrait manipuler des données sur votre serveur via des demandes (POST, PUT, DELETE, etc.). Dans ces situations, comme lors de l'utilisation d'en-têtes personnalisés, le navigateur vérifie d'abord avec le serveur que le serveur est prêt à accepter la demande avant de l'envoyer car l'envoi de demandes non sollicitées au serveur pourrait être très dangereux pour vos données, et aussi, ce qui est le point dans le navigateur d'envoyer des charges utiles potentiellement importantes si le serveur ne veut pas les accepter, d'où la vérification OPTIONS avant le vol.
davidnknight
6
@davidnknight si l'envoi de vos données au serveur peut être dangereux, ce qui signifie que le serveur pourrait être compromis, alors bien sûr, le serveur malveillant répondrait à votre demande OPTIONS avec "Bien sûr, envoyez-le partout!". Comment est cette sécurité? (question honnête)
Matt
3
"Les demandes de contrôle en amont ne sont pas une chose de sécurité. Au contraire, elles ne changent pas les règles." - Voir la réponse à Quelle est la motivation derrière l'introduction des demandes de
contrôle en amont
9

Si vous essayez de POSTER

Assurez-vous de JSON.stringifyvos données de formulaire et envoyez en tant que 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
la source
2

Je ne pense pas que jQuery fera naturellement une requête JSONP quand on lui donnera une URL comme ça. Il fera cependant une requête JSONP lorsque vous lui direz quel argument utiliser pour un rappel:

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

Il appartient entièrement au script récepteur d'utiliser cet argument (qui ne doit pas être appelé "jsoncallback"), dans ce cas, la fonction ne sera jamais appelée. Mais, puisque vous avez déclaré que vous vouliez simplement que le script sur metaward.com s'exécute, cela suffirait.

VoteyDisciple
la source
mon rappel sera-t-il toujours notifié que l'élément de script est entièrement chargé? Je veux juste m'assurer que la mise à jour a eu lieu avant d'en faire la requête à l'API.
Paul Tarjan
Vous le ferez si le script de réception prend en charge JSONP et est prêt à appeler la fonction que vous identifiez. Si le script ne fait rien d'autre que générer un bloc de données JSON sans autre comportement, vous ne pourrez pas dire quand le chargement sera terminé. S'il est essentiel de savoir quand le chargement est terminé, vous pouvez envisager d'implémenter un script sur votre propre serveur qui agit comme un proxy.
VoteyDisciple
1

En fait, les demandes AJAX inter-domaines (XMLHttp) ne sont pas autorisées pour des raisons de sécurité (pensez à récupérer une page Web "restreinte" du côté client et à la renvoyer au serveur - ce serait un problème de sécurité).

La seule solution de contournement sont les rappels. C'est-à-dire: créer un nouvel objet de script et pointer le src vers le JavaScript de fin, qui est un rappel avec des valeurs JSON (myFunction ({data}), myFunction est une fonction qui fait quelque chose avec les données (par exemple, les stocker dans une variable).

Adrián Navarro
la source
1
à droite, mais je peux le charger dans un <script src = ""> ou <img src = ""> et le navigateur sera heureux de le frapper. Je veux juste savoir quand il est complètement chargé pour que je puisse demander le résultat de l'importation.
Paul Tarjan
1

Changez simplement "application / json" en "text / plain" et n'oubliez pas le JSON.stringify (demande):

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
la source
1

J'ai eu le même problème. Mon correctif a été d'ajouter des en-têtes à mon script PHP qui ne sont présents que dans un environnement de développement.

Cela permet des demandes interdomaines:

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

Cela indique à la demande de contrôle en amont que le client peut envoyer les en-têtes qu'il souhaite:

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

De cette façon, il n'est pas nécessaire de modifier la demande.

Si vous avez des données sensibles dans votre base de données de développement qui pourraient potentiellement être divulguées, vous pourriez y réfléchir à deux fois.

cinq bits
la source
1

Dans mon cas, le problème n'était pas lié à CORS puisque j'émettais un POST jQuery au même serveur Web. Les données étaient JSON mais j'avais omis le paramètre dataType: 'json'.

Je n'ai pas (ni ajouté) un paramètre contentType comme indiqué dans la réponse de David Lopes ci-dessus.

GarDavis
la source
0

Il semble que Firefox et Opera (testés sur mac également) n'aiment pas le caractère interdomaine de cela (mais Safari est très bien avec ça).

Vous devrez peut-être appeler un code côté serveur local pour boucler la page distante.

bonjour
la source
0

J'ai pu le réparer à l'aide des en-têtes suivants

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

Si vous êtes sur Nodejs, voici le code que vous pouvez copier / coller.

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
la source