AngularJS的$resource在$save方法中发出HTTP OPTIONS请求而不是HTTP POST请求

24

我正在编写一个简单的库应用程序,为了准备使用AngularJS进行更大的项目。在网上阅读了很多关于使用$resource与RESTful API交互的内容后,我决定实现它可能会节省一些时间,并具有可扩展性,而不是为每个请求使用$http。问题是由于某种原因(我不是CORS的专家,请求正在跨域发送),当使用$save方法时,我的Node.js控制台显示:

OPTIONS /books 200 1ms - 161b 

使用query()方法效果良好 - Node控制台显示:

GET /books 200 1ms - 228b

我已经卡了好几个小时了,尝试着变化下面的内容,但它总是以OPTIONS请求结束而不是POST请求(根据Angular文档应该是后者)$save方法。

AngularJS Web应用程序

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

使用Express构建的Node.js 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'));
});

./routes/books.js

...

exports.add = function(req, res) {

    console.log("POST request received...");
    console.log(req.body.isbn);
};

我尝试将这行代码放入我的config函数中delete $httpProvider.defaults.headers.common ["X-Requested-With"];,但没有改变。

我不是Angular/Node专业人士,但现在我认为这与它跨域有关,就像我说的,我不是CORS方面的专家。

提前致谢。


2
请查看此帖子 - Andrew Shustariov
2个回答

43

我知道自问自答可能不太好,但在发帖几天后,我找到了问题的解决方法。

问题的关键在于浏览器如何管理CORS。当使用JavaScript进行跨域请求时,如果不是“简单”请求(即GET请求 - 这就解释了为什么query()函数可以工作),浏览器将自动向指定的URL / URI 发出HTTP OPTIONS请求,称为“预检请求”或“承诺”。只要远程源返回200个HTTP状态码以及有关响应头中将接受什么的相关详细信息,浏览器就会继续原始的JavaScript调用。

这是一个简短的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.
}

我只需要在响应头中添加服务器所接受的详细信息即可:

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

在Express路由之前插入以下几行代码,以便为每个OPTIONS请求返回HTTP 200状态码:

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

希望这可以帮助任何遇到同样问题的人,偶然看到这个页面。

9
回答自己的问题并不是什么坏习惯,事实上,如果你自己找到解决问题的方法,这样做是受到鼓励的。 - Ed_
我担心会有额外的http 'OPTIONS'请求。有没有办法从angularjs中避免OPTION请求? - Dasun
不幸的是,这是由浏览器在CORS请求上完成的,我们无法控制。 - Tom P.
如果您不被允许更改服务器实现,则可能的解决方法是使用带有选项--disable-web-security的chromium-browser。我在Ubuntu上进行了测试,可以从任何位置运行以下命令:**$ chromium-browser --disable-web-security &** - Enrico Giurin

1
我其实没有尝试过这个,但告诉资源如何处理$save请求不就足够了吗?
$resource('http://mywebserver\\:1337/books/:bookId', { bookId: '@bookId' }, {save: {method: 'POST'});

不,问题是因为它是跨域请求。即使我使用了你提供的方法,浏览器仍然会发出OPTIONS请求。 - Tom P.
好的,只是出于好奇创建了一个plunker。同样的问题,POST被转换为OPTIONS。 - mausberger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接