El recurso AngularJS $ realiza una solicitud de OPCIONES HTTP en lugar de HTTP POST para el método $ save

Estoy en el proceso de escribir una aplicación de biblioteca simple para prepararme para un proyecto más grande con AngularJS. Después de leer mucho en línea sobre el uso de $resource para interactuar con una API RESTful, decidí que probablemente ofrecería algunos beneficios de ahorro de tiempo y escala para implementarlo en lugar de usar $http para cada solicitud. El problema es que por alguna razón (no soy un experto en CORS y la solicitud se está enviando a un dominio cruzado) al usar el método $save , la consola de Node.js muestra:

 OPTIONS /books 200 1ms - 161b 

El uso del método query() funciona bien: la consola Node muestra:

 GET /books 200 1ms - 228b 

He estado atascado durante varias horas en este punto, probando variaciones en el siguiente, pero siempre termina siendo una solicitud de OPCIONES en lugar de POST (que es lo que debería ser de acuerdo con la documentación de Angular) para el método $save .

Aplicación web AngularJS

app.js

 var libraryApp = angular.module('libraryApp', ['ngResource', 'ngRoute', 'libraryControllers']); libraryApp.factory('$book', ['$resource', function ($resource) { return $resource('http://mywebserver\\:1337/books/:bookId', { bookId: '@bookId' }); }]); 

controllers.js

 var libraryControllers = angular.module('libraryControllers', []); libraryControllers.controller('BookCtrl', ['$scope', '$book', function($scope, $book) { ... $scope.addBook = function () { var b = new $book; b.isbn = "TEST"; b.description = "TEST"; b.price = 9.99; b.$save(); }; }]); 

Node.js con Express REST API

app.js

 var express = require('express'), books = require('./routes/books'), http = require('http'), path = require('path'); var app = express(); ... // enable cross-domain scripting app.all('*', function(req, res, next) { res.header("Access-Control-Allow-Origin", req.headers.origin); res.header("Access-Control-Allow-Headers", "X-Requested-With"); next(); }); // routing app.get('/books', books.getAll); app.get('/books/:isbn', books.get); // This is what I want to fire with the $save method app.post('/books', books.add); http.createServer(app).listen(app.get('port'), function(){ console.log('Express server listening on port ' + app.get('port')); }); 

./rutas/books.js

 ... exports.add = function(req, res) { console.log("POST request received..."); console.log(req.body.isbn); }; 

Intenté poner esta línea en mi función de configuración delete $httpProvider.defaults.headers.common["X-Requested-With"]; pero sin cambio

No soy un profesional de Angular / Node, pero en este momento estoy pensando que tiene que ver con ser un dominio cruzado y, como dije, no soy un experto en CORS.

Gracias por adelantado.

Sé que puede ser de mal gusto responder a mi propia pregunta, pero resolví el problema unos días después de publicar esto.

Todo se reduce a cómo los navegadores administran CORS. Cuando se realiza una solicitud de dominio cruzado en JavaScript que no es “simple” (es decir, una solicitud GET, que explica por qué funcionó la función de query() ), el navegador automáticamente realizará una solicitud de OPCIONES HTTP a la URL / URI especificada, llamada “Pre-vuelo” solicitud o “promesa”. Siempre que la fuente remota devuelva un código de estado HTTP de 200 y detalles relevantes sobre lo que aceptará en los encabezados de respuesta, entonces el navegador continuará con la llamada de JavaScript original.

Aquí hay un breve ejemplo de jQuery:

 function makeRequest() { // browser makes HTTP OPTIONS request to www.myotherwebsite.com/api/test // and if it receives a HTTP status code of 200 and relevant details about // what it will accept in HTTP headers, then it will make this POST request... $.post( "www.myotherwebsite.com/api/test", function(data) { alert(data); }); // ...if not then it won't - it's that simple. } 

Todo lo que tenía que hacer era agregar los detalles de lo que el servidor aceptará en los encabezados de respuesta:

 // apply this rule to all requests accessing any URL/URI app.all('*', function(req, res, next) { // add details of what is allowed in HTTP request headers to the response headers res.header('Access-Control-Allow-Origin', req.headers.origin); res.header('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE, OPTIONS'); res.header('Access-Control-Allow-Credentials', false); res.header('Access-Control-Max-Age', '86400'); res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept'); // the next() function continues execution and will move onto the requested URL/URI next(); }); 

Y luego inserte estas pocas líneas antes del enrutamiento Express para simplemente devolver un código de estado HTTP 200 para cada solicitud de OPCIONES:

 // fulfils pre-flight/promise request app.options('*', function(req, res) { res.send(200); }); 

Esperemos que esto ayude a cualquiera que tropiece en esta página sufriendo el mismo problema.

En realidad no intenté esto, pero ¿no sería suficiente decirle a Ressource cómo manejar la solicitud de $ guardar?

 $resource('http://mywebserver\\:1337/books/:bookId', { bookId: '@bookId' }, {save: {method: 'POST'});