web-dev-qa-db-fra.com

Rails Répond avec 404 à la demande d'options de contrôle en amont CORS

Je crée un ensemble de services en utilisant Rails 4, que je consomme avec une application de navigateur JavaScript. Cross-Origin GETS fonctionne bien, mais mes POST échouent à la vérification OPTIONS de contrôle en amont avec un Erreur 404. Du moins, je pense que c'est ce qui se passe. Voici les erreurs telles qu'elles apparaissent dans la console. C'est Chrome 31.0.1650.63 sur un Mac.

OPTIONS http://localhost:3000/confessor_requests 404 (Not Found) jquery-1.10.2.js:8706
OPTIONS http://localhost:3000/confessor_requests No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access. jquery-1.10.2.js:8706
XMLHttpRequest cannot load http://localhost:3000/confessor_requests. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access. main.html:1

J'ai cherché haut et bas pour obtenir des instructions sur l'activation de CORS, et je suis perplexe. La recommandation habituelle semble être de mettre quelque chose comme ça dans le contrôleur d'application, ce que j'ai fait.

before_filter :cors_preflight_check
after_filter :cors_set_access_control_headers

def cors_set_access_control_headers
  headers['Access-Control-Allow-Origin'] = '*'
  headers['Access-Control-Allow-Methods'] = 'POST, PUT, GET, OPTIONS'
  headers['Access-Control-Allow-Headers'] = '*'
  headers['Access-Control-Max-Age'] = "1728000"
end

def cors_preflight_check
  if request.method == :options
    headers['Access-Control-Allow-Origin'] = '*'
    headers['Access-Control-Allow-Methods'] = 'POST, PUT, GET, OPTIONS'
    headers['Access-Control-Allow-Headers'] = '*'
    headers['Access-Control-Max-Age'] = '1728000'
    render :text => '', :content_type => 'text/plain'
  end
end

Suivi d'une sorte de route dans routes.rb qui redirigera vers cette action lorsqu'une demande OPTIONS arrivera.

match "/*all" => "application#cors_preflight_check", :constraints => { :method => "OPTIONS" }

La directive 'match' ne fonctionne plus dans Rails 4, donc je l'ai tripotée, essayant de la faire correspondre directement aux POSTS, comme ceci:

post "/*all" => "application#cors_preflight_check", :constraints => { :method => :options }

Mais ça ne marche toujours pas. Étant donné que les demandes GET fonctionnent, je suppose que ce qui me manque est la bonne route pour la demande OPTIONS. Cependant, j'ai essayé toutes les voies auxquelles je peux penser, et rien ne semble laisser passer la demande.

J'ai également essayé d'installer cyu/rack-cors , et cela donne le même résultat.

Quelqu'un sait ce que je fais mal?

44
Rolandus

Voici une solution avec le gemme rack-cors, que vous avez dit avoir essayé. Comme d'autres l'ont mentionné, vous n'avez pas donné beaucoup de détails sur le cadre frontal que vous utilisez et à quoi ressemble la demande réelle. Donc, ce qui suit peut ne pas s'appliquer à vous, mais j'espère que cela aide quelqu'un.

Dans mon cas, la gemme a bien fonctionné jusqu'à ce que j'utilise PUT (ou PATCH ou DELETE).

Si vous regardez dans la console de développement de votre navigateur, regardez les en-têtes de demande, et vous devriez avoir une ligne comme celle-ci:

Access-Control-Request-Method: PUT

La chose importante à noter est que les methods que vous passez à resource sont pour les Access-Control-Request-Method, pas la méthode de demande qui doit suivre après le contrôle pré-vol.

Notez comment j'ai :methods => [:get, :post, :options, :delete, :put, :patch] qui inclura toutes les méthodes qui me tiennent à cœur.

Ainsi, toute votre section de configuration devrait ressembler à ceci, pour development.rb:

# This handles cross-Origin resource sharing.
# See: https://github.com/cyu/rack-cors
config.middleware.insert_before 0, "Rack::Cors" do
  allow do
    # In development, we don't care about the Origin.
    origins '*'
    # Reminder: On the following line, the 'methods' refer to the 'Access-
    # Control-Request-Method', not the normal Request Method.
    resource '*', :headers => :any, :methods => [:get, :post, :options, :delete, :put, :patch], credentials: true
  end
end
24
Tyler Collier

Travailler sur Rails 3.2.11.

Je mets

match '*path', :controller => 'application', :action => 'handle_options_request', :constraints => {:method => 'OPTIONS'}

dans mon fichier routes.rb. La clé était de le mettre en priorité absolue (en haut du fichier routes.rb). A créé cette action pour qu'elle soit publiquement disponible:

  def handle_options_request
    head(:ok) if request.request_method == "OPTIONS"
  end

Et un filtre dans le contrôleur d'application:

 after_filter :set_access_control_headers

  def set_access_control_headers
    headers['Access-Control-Allow-Origin'] = '*'
    headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE'
  end
11
ancajic

Oui, comme d'autres l'ont souligné, il existe un GEM pour peut-être faire mieux. Mais comme j'ai beaucoup aimé la méthode indiquée dans le article de blog original avec le code cors, j'ai trouvé les Rails 4 solution si vous utilisez ce code.

Dans vos routes.rb:

match '*all' => 'my_method_name#cor', via: :options

Dans votre contrôleur my_method_name:

def cor
    # blank section for CORR
    render :text => ''
end

Tant que vous en avez, plus votre autre code:

before_filter :cors_preflight_check
after_filter :cors_set_access_control_headers
...

Ensuite, vous devez être défini pour Rails 4.

6

Peut-être que ce Gist peut vous aider: CORS dans Rails 4 API

Il ajoute la méthode OPTIONS à la définition de l'itinéraire et ajoute un filtre au contrôleur de base API qui répond directement aux requêtes OPTIONS avec l'en-tête correct et définit les en-têtes CORS corrects pour toutes les autres actions , aussi.

3
dhoelzgen

Ran dans ce problème avec Rails 4 et Devise. J'ai fini par utiliser Rack CORS middlewaregem par Calvin Yu. Un excellent blog gratuit article .

2
Tom Doe

Je ne sais pas quel framework frontal javascript vous utilisez (ou si vous l'êtes) car vous n'avez pas précisé ce que vous faites du côté client pour vous connecter à votre Rails 4 API, mais j'ai pensé ajouter ma réponse au cas où cela aiderait quelqu'un.

J'ai rencontré exactement le même problème lors de la connexion à une API Rails 4 avec la gemme Devise à partir d'un frontal AngularJS (les deux fonctionnaient sur des ports locaux différents). J'essayais de me connecter à la back-end en utilisant une demande POST à partir d'un formulaire AngularJS mais j'ai continué à recevoir une erreur 404 NOT FOUND parce que j'envoyais une demande OPTIONS avec contrôle en amont. Il a fallu plus de 2 jours pour comprendre comment résoudre le problème le problème.

Fondamentalement, vous devez configurer un serveur proxy pour votre frontal (Angular, Backbone, peu importe) afin de vous connecter à votre API afin que votre frontal pense que la demande utilise la même origine. Il existe quelques solutions simples pour la configuration de proxy à l'aide de GruntJS. J'utilise Gulp pour mon projet avec Gulp-Connect et proxy-middleware avec la configuration suivante (basée sur la solution trouvée ici ):

var gulp            = require('gulp'),
    connect         = require('gulp-connect');

gulp.task('devServer', function() {
      connect.server({
        root: './dist',
        fallback: './dist/index.html',
        port: 5000,
        livereload: true,
        middleware: function(connect, o) {
            return [ (function() {
                var url = require('url');
                var proxy = require('proxy-middleware');
                var options = url.parse('http://localhost:3000/');
                options.route = '/api';
                return proxy(options);
            })() ];
        }
      });
    });

J'espère que ça aidera quelqu'un!

1
po3t

J'ai rencontré le même problème et j'évalue actuellement les itinéraires suivants pour tous les problèmes de sécurité/performances possibles. Ils résolvent le problème, mais ...

match '/', via: [:options], 
 to:  lambda {|env| [200, {'Content-Type' => 'text/plain'}, ["OK\n"]]}
match '*unmatched', via: [:options],  
 to:  lambda {|env| [200, {'Content-Type' => 'text/plain'}, ["OK\n"]]}

Bien que 'match' ne fonctionne censément pas dans Rails 4, apparemment cela fonctionne fonctionne si vous le limitez à une méthode spécifique.

1
RonLugge