我经历了我的API以及我的角度文件中的CORS所需的更改.见下面的来源.我遇到的第一个问题是,有一个错误,CORS允许标头无法识别Chrome中的localhost.我更新了我的hosts文件,所以我可以使用moneybooks.dev,这适用于我的GET请求而不使用JSONP.
现在,我面临的问题.提交POST请求时,Access-Control-Allow-Origin不允许其声明来源http://moneybooks.dev:9000什么? GET可以通过,但POST被拒绝.我看到请求来到烧瓶,但它返回HTTP 400.我需要帮助使POST请求工作.
另一个可能相关的问题是,在我的GET请求中,有时GET请求根本不会触发.就像在BudgetCtrl中的loadBudget函数一样.在#/ budgetgets / budgetID上,预算名称有时根本不会加载.我检查烧瓶日志,看不到请求.然后我点击刷新,我看到请求,预算名称出现在页面上但是在烧瓶日志中我看到一个错误. [Errno 10053]已建立的连接已被主机中的软件中止.它是一个连接错误,只有在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: '/' }); }]);
budgets.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',$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',$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='*','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>','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','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,mimetype='application/json') app.register_blueprint(api) if __name__ == '__main__': app.config['debug'] = True app.config['PROPAGATE_EXCEPTIONS'] = True app.run()
decorators.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无法加载http://moneybooks.dev:9001/api/budgets/5223e780f58e4d20509b4b8b/transactions. Access-Control-Allow-Origin不允许来源http://moneybooks.dev:9000.
网络
Name: transactions /api/budgets/5223e780f58e4d20509b4b8b Method: POST Status: (canceled) Type: Pending Initiator: angular.js:9499 Size: 13 B / 0 B Latency: 21 ms
解决方法@H_301_40@
正如@TheSharpieOne指出的那样,CORS错误可能是由Chrome Dev Tools错误导致的红色鲱鱼.如果这是一个真正的CORS问题,那么飞行前的OPTIONS调用应该返回相同的错误.
我相信您的400错误可能来自POST请求的处理程序中的request.form [‘transactions’]. request.form是一个MultiDict数据结构,根据http://werkzeug.pocoo.org/docs/datastructures/#werkzeug.datastructures.MultiDict的文档:
From Werkzeug 0.3 onwards,the KeyError raised by this class is also a subclass of the BadRequest HTTP exception and will render a page for a 400 BAD REQUEST if caught in a catch-all for HTTP exceptions.
我相信如果你检查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()函数获取请求数据.
我相信您的400错误可能来自POST请求的处理程序中的request.form [‘transactions’]. request.form是一个MultiDict数据结构,根据http://werkzeug.pocoo.org/docs/datastructures/#werkzeug.datastructures.MultiDict的文档:
From Werkzeug 0.3 onwards,the KeyError raised by this class is also a subclass of the BadRequest HTTP exception and will render a page for a 400 BAD REQUEST if caught in a catch-all for HTTP exceptions.
我相信如果你检查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()函数获取请求数据.