Problème de CORS & D'architecture avec Kotlin et SparkJava

Le problème exposé dans ce sujet a été résolu.

Salut,

Je suis actuellement entrain de poser les bases d’une API que vous pourrez trouver à cette adresse : https://github.com/GIPSMC/GazetteAPI

Cependant, j’ai des soucis avec au niveau du CORS lors d’une requête JS :

Access to fetch at 'http://localhost:8080/api/register' from origin 'http://localhost/' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. 

Pourtant, je gère bien le CORS dans l’application…

Ma deuxième question serait au niveau du code lui même, je me demande si la manière dont je gère les réponses est la bonne (par ce que je me vois mal faire 50 class pour les 50 possible réponse… Peut-être faire une classe général avec des champs Optionnal<> ?

Merci :)

Est-ce que tu vois bien l’en-tête apparaître Access-Control-Allow-Origin dans tes en-têtes de réponse ?

Ma deuxième question serait au niveau du code lui même, je me demande si la manière dont je gère les réponses est la bonne (par ce que je me vois mal faire 50 class pour les 50 possible réponse… Peut-être faire une classe général avec des champs Optionnal<> ?

C’est un problème que tu résoudras au fur et à mesure. Sois clair sur ce que doit faire ton code, quelles routes il doit servir, puis tu factorises le code commun naturellement. Pour moi tu te poses cette question beaucoup trop tôt.

Est-ce que tu vois bien l’en-tête apparaître Access-Control-Allow-Origin dans tes en-têtes de réponse ?

Non, malheureusement quand je vais dans ma réponse, j’ai :

image.png

C’est un problème que tu résoudras au fur et à mesure. Sois clair sur ce que doit faire ton code, quelles routes il doit servir, puis tu factorises le code commun naturellement. Pour moi tu te poses cette question beaucoup trop tôt.

ça marche, merci :)

Hello,

On dirait que ton serveur ne répond pas sur les requêtes OPTION pour récupérer les entêtes nécessaires.

En tout cas c’est comme ça que fonctionnent les preflight requests : le navigateur fait une requête OPTION pour savoir si ça vaut le coup de faire une requête complète (GET, POST ou autre). Il faut donc que le serveur réponde à ces appels avec les bons entêtes/headers.

Je confirme ce que dit viki53, voici le flow (du moins celui que Firefox implémente) que j’ai dû gérer sur un projet que j’ai mis en prod il y a pas plus d’un mois :

1 - le navigateur fait une requête OPTIONS et s’attend à y voir possiblement deux headers :

  • access-control-allow-origin: <XXX>
  • access-control-allow-headers: <XXX>

2 - si le retour est conforme, alors il s’occupe enfin de faire le GET, POST, …, mais ces appels aussi doivent envoyer headers adéquats.

J’ai dû implémenter niveau backend le handler pour répondre au OPTIONS. Pour te donner un exemple concret, ça donnait quelque chose comme :

const corsHeader = new Headers({
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': '*'
});

async function handleRequest(request) {
  if (request.method == "OPTIONS") {
    return new Response("OK", {status: 200, headers: corsHeader});
  }
  ...

(dans mon cas j’avais intérêt à mettre la valeur à *, mais ça ne sera pas forcément ton cas)

+0 -0

Hey,

Merci des informations

Le problème, c’est que même si je fais :

package com.lagazettedemonaco.utils

import spark.Spark.before
import spark.Spark.options

fun enableCORS(origin: String, methods: String, headers: String) {
    options("/*") { request, response ->
        val accessControlRequestHeaders: String = request.headers("Access-Control-Request-Headers")
        response.header("Access-Control-Allow-Headers", accessControlRequestHeaders)
        val accessControlRequestMethod: String = request.headers("Access-Control-Request-Method")
        response.header("Access-Control-Allow-Methods", accessControlRequestMethod)
        "OK"
    }
    before ({ request, response ->
        response.header("Access-Control-Allow-Origin", origin)
        response.header("Access-Control-Request-Method", methods)
        response.header("Access-Control-Allow-Headers", headers)
        response.type("application/json")
    })
}

suivis de

package com.lagazettedemonaco

import com.lagazettedemonaco.controllers.AuthController
import com.lagazettedemonaco.utils.enableCORS
import spark.kotlin.ignite


fun main() {
    val http = ignite()
    http.port(8080)
    enableCORS("*", "*", "*")
    val controllers = arrayOf(AuthController(http))

    for (controller in controllers) {
        controller.routes()
    }
 }

On reste au même stade

+0 -0

Il serait utile de connaitre la réponse à la question initiale de Ge0:

Est-ce que tu vois bien l’en-tête apparaître Access-Control-Allow-Origin dans tes en-têtes de réponse ?

Pour voir ça, tu devrais regarder l’onglet Headers de Postman

Salut,

Je suis actuellement entrain de poser les bases d’une API que vous pourrez trouver à cette adresse : https://github.com/GIPSMC/GazetteAPI

Cependant, j’ai des soucis avec au niveau du CORS lors d’une requête JS :

praemix

Selon la façon dont c’est codé, c’est normal que ton code ne renvoi pas d’entêtes CORS.

Quand tu appelles ta ligne Cors().apply(), tu essayes de modifier les entêtes via Spark.after(filter), ce qui n’est pas juste puisque toi ton contrôleur est basé sur l’instance http = ignite().

Donc pour corriger ton problème, tu peux commencer par passer ton instance http par quelque chose comme Cors().apply(http) (dans ton App.kt), ensuite tu peux modifier/simplifier ta fonction apply par quelque chose comme.

-    fun apply() {
-        val filter = Filter { request, response ->
-            corsHeaders.forEach { (key: String?, value: String?) ->
-                response.header(
-                    key,
-                    value
-                )
-            }
-        }
-        Spark.after(filter)
-    }

+    fun apply(http: Http) {
+        corsHeaders.forEach { (key: String?, value: String?) ->
+            http.after { response.header(key, value) }
+        }
+    }

ça devrait t’aider.

Pour info pour vérifier les entêtes que te renvoient ton API, tu peux aussi utiliser la ligne de commande avec un simple curl -i -X POST http://localhost:8080/chemin/vers/mon/endpoint (l’option -i te permet de lister le détail de ta réponse dont les entêtes).

Salut, j’ai modifié le code comme dis au dessus et la commande curl me donne :

HTTP/1.1 200 OK
Date: Thu, 06 Jan 2022 23:33:39 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,PUT,POST,DELETE,OPTIONS
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type,Authorization,X-Requested-With,Content-Length,Accept,Origin,
Content-Type: text/html;charset=utf-8
Transfer-Encoding: chunked
Server: Jetty(9.4.4.v20170414)

{"status":"error","message":"Merci de vous inscires"}

Pourtant, j’ai toujours l’erreur au niveau du navigateur

Voici le code pour la requête au niveau du front :

   const user = {
        username,
        email,
        social,
        address,
        address_second,
        city,
        postal,
        country,
        password
    }

    const URL = API_URL + "api/register"

    const callApi = await fetch(URL, {
        headers: {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        },
        method: "POST",
        body: JSON.stringify(user),
        mode: 'cors'
    })

    const response = await callApi.json()
+0 -0

Pourtant, j’ai toujours l’erreur au niveau du navigateur

C’est là qu’intervient les réponses de @viki53 et @sgble que je vais préciser ici.

Lorsque tu fais une requête POST via JS (avec ton fetch), l’API va d’abord faire une requête vers OPTIONS pour demander si ça vaut le coup d’aller plus loin. Le problème de ton code c’est qu’il ne gère nullement les requêtes de type OPTIONS. Pour t’en convaincre, il suffit de faire un curl -i -X OPTIONS http://localhost:8080/chemin/vers/mon/endpoint, ton API devrait répondre quelque chose comme une 404.

Tu dois donc implémenter pour chacun de tes endpoints accessible via fetch l’équivalent avec OPTIONS qui retourne un code http 200 (le contenu de la réponse importe peu. Une chaine vide ferait l’affaire).

En pas très propre, tu peut rajouter dans ton AuthController.kt quelque chose comme :

override fun routes() {
  // contenu initial
  this.http.options("/api/register") { response.body("")}
}

Et ça devrait te débloquer.

Ah pour info dans ton code JS, ce n’est pas à toi d’envoyer l’entête 'Access-Control-Allow-Origin': '*' dans ton fetch. Il faudra probablement supprimer cette ligne pour faire marcher ton appel.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte