7

angularjs クライアント アプリと API を提供するフラスコ アプリを使用して新しいプロジェクトを開始します。データベースとしてmongodbを使用しています。異なるポート間で POST する機能が必要になるため、jsonp をすぐに除外する必要がありました。したがって、angular アプリには localhost:9000 があり、flask アプリには localhost:9001 があります。

API と angular ファイルで CORS に必要な変更を行いました。以下のソースを参照してください。私が最初に遭遇した問題は、CORS allow ヘッダーが Chrome の localhost を認識しないというバグがあることでした。moneybooks.dev を使用できるようにホスト ファイルを更新しました。これは、JSONP を使用せずに GET 要求で機能しました。

今、私が直面している問題に。POST リクエストを送信すると、Origin http://moneybooks.dev:9000 is not allowed by Access-Control-Allow-OriginWhat? と表示されます。GET は通過できますが、POST は拒否されます。リクエストがフラスコに送られているのが見えますが、HTTP 400 が返されます。POST リクエストを機能させるには助けが必要です。

関連する可能性のある別の問題は、GET リクエストで、GET リクエストがまったく起動しない場合があることです。BudgetCtrlloadBudget 関数と同様です。#/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: '/'
      });
  }]);

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

編集

Chrome 開発コンソールからの出力。

コンソール:

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
4

3 に答える 3

5

@TheSharpieOne が指摘したように、CORS エラーは、Chrome 開発ツールのバグによって引き起こされた赤いニシンである可能性があります。実際の CORS の問題である場合は、飛行前の OPTIONS 呼び出しで同じエラーが返されるはずです。

あなたの 400 エラーはrequest.form['transactions']、POST リクエストのハンドラーから発生している可能性があると思います。 request.formMultiDict データ構造であり、http://werkzeug.pocoo.org/docs/datastructures/#werkzeug.datastructures.MultiDictのドキュメントによると:

Werkzeug 0.3 以降、このクラスによって発生する KeyError は、BadRequest HTTP 例外のサブクラスでもあり、HTTP 例外のキャッチオールでキャッチされた場合、400 BAD REQUEST のページをレンダリングします。

で「transactions」キーを確認すると、request.forms.keys()存在しないことがわかると思います。POST のコンテンツ タイプは ではapplication/jsonないことに注意してくださいx-www-form-urlencodedhttp://flask.pocoo.org/docs/api/#flask.Request.get_jsonのドキュメントによると、request.get_json()リクエストの mimetype がapplication/json.

于 2013-09-10T23:27:57.850 に答える
0

POST はコンテンツを送信していますか? ボディがnullの場合、同様の問題がありました。そうである場合、オブジェクトが偽の場合に空の本文 ("") を追加するか、ContentLength ヘッダーを 0 として追加すると、どちらも機能するように見えました。

$scope.syncUp = function () {
var objToSend = $scope.transactions ? { transactions: $scope.transactions } : "";
$http.post('http://moneybooks.dev:9001/api/budgets/'+$routeParams['budgetID']+'/transactions', objToSend);
};
于 2013-09-04T07:35:48.380 に答える
-2

HTMLページでbudget.jsの前にapp.jsが含まれていることを確認してください

于 2013-09-04T02:10:01.460 に答える