CORS的问题.烧瓶<->AngularJS
Issues with CORS. Flask <-> AngularJS
使用angularjs客户端应用程序和提供api的flask应用程序启动一个新项目。我使用mongodb作为数据库。我必须立即排除jsonp,因为我需要跨不同端口POST的能力。因此,angular应用程序使用localhost:9000,flask应用程序使用本地主机:9001。
我在API和我的角度文件中进行了CORS所需的更改。请参阅下面的来源。我遇到的第一个问题是,在Chrome中存在一个错误,即CORS允许标头无法识别localhost。我更新了我的主机文件,这样我就可以使用moneybooks.dev,这在不使用JSONP的情况下适用于我的GET请求。
现在,谈谈我面临的问题。当提交POST请求时,它说明Origin http://moneybooks.dev:9000 is not allowed by Access-Control-Allow-Origin
什么?GET可以通过,但POST被拒绝。我看到请求通过烧瓶,但它返回HTTP400。我需要帮助使POST请求工作。
另一个可能相关的问题是,在我的GET请求中,有时GET请求根本不会触发。与BudgetCtrl
中的loadBudget函数类似。在#/budgets/budgetID上,预算的名称有时根本不会加载。我查看了烧瓶日志,没有收到任何请求。然后我点击刷新,我看到了请求,预算名称出现在页面上,但在烧瓶日志中我看到了一个错误。[Errno 10053] An established connection was aborted by the software in your host machine.
是一个连接错误,只有在GET请求成功时才会出现在烧瓶日志中。
这些问题有关联吗?有人看到我做错了什么吗?
app.js
'use strict';
angular.module('MoneybooksApp', ['ui.bootstrap', 'ngResource'])
.config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) {
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
controller: 'MainCtrl'
})
.otherwise({
redirectTo: '/'
});
}]);
预算.js
'use strict';
angular.module('MoneybooksApp')
.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/budgets', {
templateUrl: 'views/budgets-list.html',
controller: 'BudgetListCtrl'
})
.when('/budgets/:budgetID', {
templateUrl: 'views/budget.html',
controller: 'BudgetCtrl'
});
}])
.controller('BudgetListCtrl', function ($scope, $http, $resource) {
$scope.budgets = [];
var init = function () {
$scope.loadBudgets();
}
$scope.loadBudgets = function() {
$http.get('http://moneybooks.dev:9001/api/budgets')
.success(function (data) {
$scope.budgets = data;
})
.error(function (data) {
console.error(data);
});
};
init();
})
.controller('BudgetCtrl', function ($scope, $http, $routeParams, $resource) {
$scope.budget = {};
var init = function () {
$scope.loadBudget();
};
$scope.loadBudget = function() {
$http.get('http://moneybooks.dev:9001/api/budgets/'+$routeParams['budgetID'])
.success(function (data) {
$scope.budget = data;
})
.error(function (data) {
console.error(data);
});
};
init();
})
.controller('TransactionCtrl', function ($scope, $http, $routeParams, $resource) {
$scope.transactions = [];
$scope.editing = false;
$scope.editingID;
var init = function () {};
$scope.syncUp = function () {
$http.post('http://moneybooks.dev:9001/api/budgets/'+$routeParams['budgetID']+'/transactions', {transactions: $scope.transactions});
};
$scope.syncDown = function () {
$http.get('http://moneybooks.dev:9001/api/budgets/'+$$routeParams['budgetID']+'/transactions')
.success(function (transactions) {
$scope.transactions = transactions;
});
};
$scope.add = function() {
$scope.transactions.push({
amount: $scope.amount,
description: $scope.description,
datetime: $scope.datetime
});
reset();
$scope.defaultSort();
};
$scope.edit = function(index) {
var transaction = $scope.transactions[index];
$scope.amount = transaction.amount;
$scope.description = transaction.description;
$scope.datetime = transaction.datetime;
$scope.inserting = false;
$scope.editing = true;
$scope.editingID = index;
};
$scope.save = function() {
$scope.transactions[$scope.editingID].amount = $scope.amount;
$scope.transactions[$scope.editingID].description = $scope.description;
$scope.transactions[$scope.editingID].datetime = $scope.datetime;
reset();
$scope.defaultSort();
};
var reset = function() {
$scope.editing = false;
$scope.editingID = undefined;
$scope.amount = '';
$scope.description = '';
$scope.datetime = '';
};
$scope.cancel = function() {
reset();
};
$scope.remove = function(index) {
$scope.transactions.splice(index, 1);
if ($scope.editing) {
reset();
}
};
$scope.defaultSort = function() {
var sortFunction = function(a, b) {
var a_date = new Date(a['datetime']);
var b_date = new Date(b['datetime']);
if (a['datetime'] === b['datetime']) {
var x = a['amount'], y = b['amount'];
return x > y ? -1 : x < y ? 1 : 0;
} else {
return a_date - b_date
}
};
$scope.transactions.sort(sortFunction);
};
$scope.descriptionSuggestions = function() {
var suggestions = [];
return $.map($scope.transactions, function(transaction) {
if ($.inArray(transaction.description, suggestions) === -1){
suggestions.push(transaction.description);
return transaction.description;
}
});
};
$scope.dateSuggestions = function () {
var suggestions = [];
return $.map($scope.transactions, function(transaction) {
if ($.inArray(transaction.datetime, suggestions) === -1){
suggestions.push(transaction.datetime);
return transaction.datetime;
}
});
}
$scope.getRunningTotal = function(index) {
var runningTotal = 0;
var selectedTransactions = $scope.transactions.slice(0, index+1);
angular.forEach(selectedTransactions, function(transaction, index){
runningTotal += transaction.amount;
});
return runningTotal;
};
init();
$(function(){
(function($){
var header = $('#budget-header');
var budget = $('#budget');
var pos = header.offset();
$(window).scroll(function(){
if ($(this).scrollTop() > pos.top && header.css('position') == 'static') {
header.css({
position: 'fixed',
width: header.width(),
top: 0
}).addClass('pinned');
budget.css({
'margin-top': '+='+header.height()
});
} else if ($(this).scrollTop() < pos.top && header.css('position') == 'fixed') {
header.css({
position: 'static'
}).removeClass('pinned');
budget.css({
'margin-top': '-='+header.height()
});
}
});
})(jQuery);
});
});
API.py
from flask import Flask, Response, Blueprint, request
from pymongo import MongoClient
from bson.json_util import dumps
from decorators import crossdomain
from bson.objectid import ObjectId
try:
import json
except ImportError:
import simplejson as json
class APIEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, objectid.ObjectID):
return str(obj)
app = Flask(__name__)
client = MongoClient()
db = client['moneybooks']
api = Blueprint('api', __name__, url_prefix="/api")
@api.route('/budgets', methods=['GET', 'POST', 'OPTIONS'])
@crossdomain(origin='*', methods=['GET', 'POST', 'OPTIONS'], headers=['X-Requested-With', 'Content-Type', 'Origin'])
def budgets():
if request.method == "POST":
budget_id = db.budgets.insert({
'name': request.form['name']
})
budget_json = dumps(db.budgets.find_one({'_id': budget_id}), cls=APIEncoder)
if request.method == "GET":
budget_json = dumps(db.budgets.find(), cls=APIEncoder)
return Response(budget_json, mimetype='application/json')
@api.route('/budgets/<budget_id>', methods=['GET', 'OPTIONS'])
@crossdomain(origin='*', methods=['GET', 'OPTIONS'], headers=['X-Requested-With', 'Content-Type', 'Origin'])
def budget(budget_id):
budget_json = dumps(db.budgets.find_one({'_id': ObjectId(budget_id)}), cls=APIEncoder)
return Response(budget_json, mimetype='application/json')
@api.route('/budgets/<budget_id>/transactions', methods=['GET', 'POST', 'OPTIONS'])
@crossdomain(origin='*', methods=['GET', 'POST', 'OPTIONS'], headers=['X-Requested-With', 'Content-Type', 'Origin'])
def transactions(budget_id):
if request.method == "POST":
db.budgets.update({
'_id': ObjectId(budget_id)
}, {
'$set': {
'transactions': request.form['transactions']
}
});
budget_json = dumps(db.budgets.find_one({'_id': ObjectId(budget_id)}), cls=APIEncoder)
if request.method == "GET":
budget_json = dumps(db.budgets.find_one({'_id': ObjectId(budget_id)}).transactions, cls=APIEncoder)
return Response(budget_json, mimetype='application/json')
app.register_blueprint(api)
if __name__ == '__main__':
app.config['debug'] = True
app.config['PROPAGATE_EXCEPTIONS'] = True
app.run()
装饰器.py
from datetime import timedelta
from flask import make_response, request, current_app
from functools import update_wrapper
def crossdomain(origin=None, methods=None, headers=None, max_age=21600, attach_to_all=True, automatic_options=True):
if methods is not None:
methods = ', '.join(sorted(x.upper() for x in methods))
if headers is not None and not isinstance(headers, basestring):
headers = ', '.join(x.upper() for x in headers)
if isinstance(max_age, timedelta):
max_age = max_age.total_seconds()
def get_methods():
if methods is not None:
return methods
options_resp = current_app.make_default_options_response()
return options_resp.headers['allow']
def decorator(f):
def wrapped_function(*args, **kwargs):
if automatic_options and request.method == 'OPTIONS':
resp = current_app.make_default_options_response()
else:
resp = make_response(f(*args, **kwargs))
if not attach_to_all and request.method != 'OPTIONS':
return resp
h = resp.headers
h['Access-Control-Allow-Origin'] = origin
h['Access-Control-Allow-Methods'] = get_methods()
h['Access-Control-Max-Age'] = str(max_age)
if headers is not None:
h['Access-Control-Allow-Headers'] = headers
return resp
f.provide_automatic_options = False
f.required_methods = ['OPTIONS']
return update_wrapper(wrapped_function, f)
return decorator
编辑
chrome dev控制台的输出。
控制台:
XMLHttpRequest cannot load http://moneybooks.dev:9001/api/budgets/5223e780f58e4d20509b4b8b/transactions. Origin http://moneybooks.dev:9000 is not allowed by Access-Control-Allow-Origin.
网络
Name: transactions /api/budgets/5223e780f58e4d20509b4b8b
Method: POST
Status: (canceled)
Type: Pending
Initiator: angular.js:9499
Size: 13 B / 0 B
Latency: 21 ms
正如@TheSharpieOne所指出的,CORS错误很可能是Chrome开发工具错误引起的转移注意力。如果是实际的CORS问题,飞行前OPTIONS调用应该返回相同的错误。
我相信您的400错误可能来自POST请求处理程序中的request.form['transactions']
。request.form
是一个MultiDict数据结构,根据http://werkzeug.pocoo.org/docs/datastructures/#werkzeug.datastructures.MultiDict:
从Werkzeug 0.3开始,这个类引发的KeyError也是BadRequest HTTP异常的一个子类,如果在HTTP异常的catch-all中被捕获,它将为400 BAD REQUEST呈现一个页面。
我相信,如果您在request.forms.keys()
中检查"transactions"密钥,您会发现它不存在。请注意,POST的内容类型是application/json
,而不是x-www-form-urlencoded
。根据上的文件http://flask.pocoo.org/docs/api/#flask.Request.get_json,当请求mimetype为application/json
时,您将希望使用request.get_json()
函数来获取请求数据。
POST是否发送内容?当尸体为空时,我也遇到了类似的问题。如果是这样的话,当对象为falsy时添加一个空的正文("),或者将ContentLength标头添加为0似乎都有效。
$scope.syncUp = function () {
var objToSend = $scope.transactions ? { transactions: $scope.transactions } : "";
$http.post('http://moneybooks.dev:9001/api/budgets/'+$routeParams['budgetID']+'/transactions', objToSend);
};
确保app.js包含在HTML页面中的budget.js之前
- 如何更改<选择>使用angularJS从控制器获得的值
- Typescript angularjs$http获取响应类型(避免使用<any>)
- AngularJS提取物<img>来自JSON
- 如何绑定“;这个“;在$http.post中->在AngularJS中解析/拒绝?(现在this=Window对象
- 如何在提交<选择>与AngularJS
- 调用AngularJS内部<脚本>标签
- AngularJS:隐藏<td>使用ng-hide/ng-show没有间隙
- CordovaApp-AngularJs-app.initialize()->undefined不是函数
- AngularJS变量转换为html<对象>属性
- AngularJS:将元素添加到<李>动态地
- 迭代<选项>AngularJS中带有按钮的值
- AngularJS/jQuery位于<身体>不适用于iPhone 5 iOS8.0
- CORS的问题.烧瓶<->AngularJS
- AngularJS DOM修改删除事件->需要可行的变通方法实现模式
- 为什么我的AngularJS控制器定义和标记没有按预期工作?-<控制器>不是函数错误
- AngularJS-动态创建<text区域>使用ngSanitize不会't显示(其他元素显示)
- Angularjs-点击<td>要素
- 以编程方式单击<A>来自AngularJS指令中的单元测试
- AngularJS-使用指令生成<td>不起作用
- Angularjs-转换<br/>转换为纯文本