Note that there are some explanatory texts on larger screens.

plurals
  1. POIssues with CORS. Flask <-> AngularJS
    text
    copied!<p>Starting a new project with a angularjs client app and a flask app providing the api. I'm using mongodb as the database. I had to immediately rule out jsonp since I would need the ability to POST across different ports. So we have localhost:9000 for the angular app and localhost:9001 for the flask app.</p> <p>I went through and made the changed needed for CORS in my API as well as my angular files. See source below. First issue I ran in to was that there is a bug that CORS allow header does not recognize localhost in Chrome. I updated my hosts file so I could use moneybooks.dev and this worked for my GET requests without using JSONP.</p> <p>Now, to the issues I'm facing. When submitting a POST request, its stating <code>Origin http://moneybooks.dev:9000 is not allowed by Access-Control-Allow-Origin</code> What? GET can go through but POST is declined. I see the request come through to flask but it returns HTTP 400. I need help making POST requests work.</p> <p>Another issue, which may be related, is that on my GET requests, sometimes the GET request doesn't fire at all. Like in <code>BudgetCtrl</code> the loadBudget function. On #/budgets/budgetID the name of the budget will sometimes not load at all. I check the flask log and don't see a request coming through. Then I click refresh, I see the request, the budget name appears on the page however in the flask log I see an error. <code>[Errno 10053] An established connection was aborted by the software in your host machine.</code> Its a connection error that only appears in the flask log when the GET request succeeds.</p> <p>Are these issues related? Can anyone see what I'm doing wrong?</p> <p><strong>app.js</strong></p> <pre><code>'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: '/' }); }]); </code></pre> <p><strong>budgets.js</strong></p> <pre><code>'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 &gt; y ? -1 : x &lt; 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() &gt; pos.top &amp;&amp; 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() &lt; pos.top &amp;&amp; header.css('position') == 'fixed') { header.css({ position: 'static' }).removeClass('pinned'); budget.css({ 'margin-top': '-='+header.height() }); } }); })(jQuery); }); }); </code></pre> <p><strong>API.py</strong></p> <pre><code>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/&lt;budget_id&gt;', 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/&lt;budget_id&gt;/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() </code></pre> <p><strong>decorators.py</strong></p> <pre><code>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 </code></pre> <p><strong>Edit</strong></p> <p>Output from chrome dev console.</p> <p>Console:</p> <p><code>XMLHttpRequest cannot load http://moneybooks.dev:9001/api/budgets/5223e780f58e4d20509b4b8b/transactions. Origin http://moneybooks.dev:9000 is not allowed by Access-Control-Allow-Origin.</code></p> <p>Network</p> <pre><code>Name: transactions /api/budgets/5223e780f58e4d20509b4b8b Method: POST Status: (canceled) Type: Pending Initiator: angular.js:9499 Size: 13 B / 0 B Latency: 21 ms </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload