Launching a new project with the angularjs client application and a flash application providing api. I use mongodb as a database. I had to immediately rule out jsonp, as I would need the POST feature across different ports. So, we have localhost: 9000 for the angular application and localhost: 9001 for the flash application.
I went through and made the changes necessary for CORS in my API, as well as in my angular files. See Source below. The first problem I ran into was that there is an error that CORS resolves the header does not recognize localhost in Chrome. I updated my hosts file to use moneybooks.dev and this worked for my GET requests without using JSONP.
Now, to the problems that I am facing. When sending a POST request, its message is Origin http://moneybooks.dev:9000 is not allowed by Access-Control-Allow-Origin What? GET may pass, but the POST is rejected. I see that the request went into the flask, but it returns HTTP 400. I need help creating POST requests.
Another problem that may be related is that sometimes, for my GET requests, the GET request does not work at all. As in BudgetCtrl function. In # / budgetgets / budgetID, the budget name sometimes doesn't load at all. I check the flag log and cannot see the request. Then I click the Refresh button, I see a request, the name of the budget is displayed on the page, however, I see an error in the flack log. [Errno 10053] An established connection was aborted by the software in your host machine. A connection error that appears only in the flag log when a GET request is successful.
Are these issues related? Can anyone see what I'm doing wrong?
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', 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()
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
Edit
Exit the Chrome Chrome console.
Console:
XMLHttpRequest cannot load http://moneybooks.dev:9001/api/budgets/5223e780f58e4d20509b4b8b/transactions. Origin http://moneybooks.dev:9000 is not allowed by Access-Control-Allow-Origin.
Net
Name: transactions /api/budgets/5223e780f58e4d20509b4b8b Method: POST Status: (canceled) Type: Pending Initiator: angular.js:9499 Size: 13 B / 0 B Latency: 21 ms