Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CORS support #45

Closed
bitemyapp opened this issue May 11, 2016 · 24 comments
Closed

CORS support #45

bitemyapp opened this issue May 11, 2016 · 24 comments

Comments

@bitemyapp
Copy link

I can't get the demo to work with a recent version of Swagger because it seems to want fine-grained CORS. AFAICT, OPTIONS isn't even supported in Servant. How do I make this work?

@fizruk
Copy link
Member

fizruk commented May 13, 2016

Hi, I have CORS configured within Nginx config (see CORS on Nginx).

I am not sure there is currently any simple way to add CORS directly into Haskell server code, but maybe wai-cors can be used.

@bitemyapp
Copy link
Author

bitemyapp commented May 13, 2016

I'm looking for something in Haskell, yes, but thank you for linking the nginx docs. I want to be able to see the swagger docs without running nginx in dev.

@fizruk
Copy link
Member

fizruk commented May 13, 2016

@bitemyapp ok, so for swagger-ui it appears that simpleCors is enough (for dev at least)!

Here's a working example (tested with LTS 5.16):

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}
module Main where

import Servant
import Data.Text
import Data.Swagger
import Servant.Swagger
import Network.Wai.Middleware.Cors (simpleCors)
import Network.Wai.Handler.Warp (run)
import Network.Wai.Middleware.RequestLogger (logStdoutDev)

type HelloAPI = "hello" :> Get '[JSON] Text

type SwaggerAPI = "swagger.json" :> Get '[JSON] Swagger

type TestAPI = SwaggerAPI :<|> HelloAPI

server :: Server TestAPI
server = return (toSwagger (Proxy :: Proxy HelloAPI))
    :<|> return "Hello, world!"

main :: IO ()
main = run 8000 $ simpleCors $ logStdoutDev $ serve (Proxy :: Proxy TestAPI) server

It appears that at work we have something else demanding more complicated CORS configuration (in particular we have to allow more headers).

@leshow
Copy link

leshow commented Jul 3, 2016

if you do a more complicated request, such as https://github.com/leshow/elm-tut/blob/master/app/server/src/Api.hs#L17

does it still work for you? simpleCors was enough to get regular GET requests working from localhost:x to localhost:y , but anything more complicated like a PUT or POST with a request body, seemed to fail for me. You can try the simple app in the link

@fizruk
Copy link
Member

fizruk commented Jul 21, 2016

@leshow sorry for the delay in response.

simpleCors uses simpleCorsResourcePolicy. If you read it's documentation, you can see this:

If the request is a POST request the content type is constraint to simple content types (application/x-www-form-urlencoded, multipart/form-data, text/plain)

So you can't use application/json (which is what is used for ReqBody '[JSON] Player).

From here you can either use a different Content-Type (FormUrlEncoded or PlainText) or use a custom resource policy to enable custom Content-Types:

-- | Allow Content-Type header with values other then allowed by simpleCors.
corsWithContentType :: Middleware
corsWithContentType = cors (const $ Just policy)
    where
      policy = simpleCorsResourcePolicy
        { corsRequestHeaders = ["Content-Type"] }

I have tested it with this code (I also added servant-swagger-ui): https://gist.github.com/fizruk/f9213e20ea68a9d6128f515c70a6e0e3

@fizruk
Copy link
Member

fizruk commented Jul 21, 2016

@bitemyapp @leshow Can this issue be closed?

@leshow
Copy link

leshow commented Jul 21, 2016

I have added the custom content type like you mentioned above but I still can't get POST/PUT requests resolved from the browser

console errors from browser:

OPTIONS http://localhost:4000/api/player/1 
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

and logged from the backend

OPTIONS /api/player/1
  Accept: */*
  Status: 400 Bad Request 0.000008s

I have updated the project with the minimal changes to make it work (haven't added swagger yet, just wanted to see if i could reproduce the issue)

https://github.com/leshow/elm-tut

When you tested it, did you try sending a application/json request from the browser?

@fizruk
Copy link
Member

fizruk commented Jul 21, 2016

@leshow I have tested with this tool: http://test-cors.org
Specifically, using these parameters.

I have also tried sending requests from the Swagger UI (running on http://localhost:8080/ui/).

How are you sending your requests from the browser?
test-cors.org generates the following code for me:

var createCORSRequest = function(method, url) {
  var xhr = new XMLHttpRequest();
  if ("withCredentials" in xhr) {
    // Most browsers.
    xhr.open(method, url, true);
  } else if (typeof XDomainRequest != "undefined") {
    // IE8 & IE9
    xhr = new XDomainRequest();
    xhr.open(method, url);
  } else {
    // CORS not supported.
    xhr = null;
  }
  return xhr;
};

var url = 'http://localhost:8080/api/player/0';
var method = 'POST';
var xhr = createCORSRequest(method, url);

xhr.onload = function() {
  // Success code goes here.
};

xhr.onerror = function() {
  // Error code goes here.
};

xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Accept', 'application/json');
xhr.send();

@leshow
Copy link

leshow commented Jul 21, 2016

PUT or POST will cause the browser to send a preflight OPTIONS request to see if cors is supported. If you look in the chrome or firefox dev tools for the network requests, you should see that preflight OPTIONS before the post, if it isn't there, then the issue isn't being recreated properly.

@leshow
Copy link

leshow commented Jul 21, 2016

I believe it is relevant to this:

haskell-servant/servant#154

@fizruk
Copy link
Member

fizruk commented Jul 21, 2016

@leshow yes, I understand preflight requests, and corsWithContentType should handle those for non-standard Content-Types like application/json.

Since it works for me (in the browser) I am trying to figure out, what JavaScript code are you testing CORS with to recreate the problem on my side.

@leshow
Copy link

leshow commented Jul 21, 2016

I wrote the frontend in elm, you can see it here:
https://github.com/leshow/elm-tut/blob/master/app/src/Players/Commands.elm#L16

saveUrl : PlayerId -> String
saveUrl playerId =
    "http://localhost:4000/api/player/" ++ (toString playerId)


saveTask : Player -> Task.Task Http.Error Player
saveTask player =
    let
        body =
            playerEncoded player
                |> Encode.encode 0
                |> Http.string

        config =
            { verb = "PUT"
            , headers = [ ( "Content-Type", "application/json" ) ]
            , url = saveUrl player.id
            , body = body
            }
    in
        Http.send Http.defaultSettings config
            |> Http.fromJson playerDecode

@fizruk
Copy link
Member

fizruk commented Jul 21, 2016

@leshow just to be sure that app responds to OPTIONS preflight request:

$ curl -i -X OPTIONS http://localhost:8080/api/player/1 -H 'Origin: 127.0.0.1' -H 'Access-Control-Request-Method: GET'
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Date: Thu, 21 Jul 2016 22:19:20 GMT
Server: Warp/3.2.7
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, HEAD, POST

@fizruk
Copy link
Member

fizruk commented Jul 21, 2016

Does that work for you as well?

@leshow
Copy link

leshow commented Jul 22, 2016

yes, it appears to work for all except:

>  ~  curl -i -X OPTIONS http://localhost:4000/api/player/1 -H 'Origin: 127.0.0.1' -H 'Access-Control-Request-Method: PUT'
HTTP/1.1 400 Bad Request
Transfer-Encoding: chunked
Date: Fri, 22 Jul 2016 01:59:27 GMT
Server: Warp/3.2.6
Content-Type: text/html; charset-utf-8

Method requested in Access-Control-Request-Method of CORS request is not supported; requested: PUT; supported are GET, HEAD, POST.% 

@leshow
Copy link

leshow commented Jul 22, 2016

switching to POST in the app is a workaround I can live with. It appears this cors middleware only supports get/post/head like the console log says

@fizruk
Copy link
Member

fizruk commented Jul 22, 2016

Aha, so it does work for POST!
To make it work with PUT, you just need to add PUT to allowed methods:

myCors :: Middleware
myCors = cors (const $ Just policy)
    where
      policy = simpleCorsResourcePolicy
        { corsRequestHeaders = ["Content-Type"]
        , corsMethods = "PUT" : simpleMethods }

@leshow
Copy link

leshow commented Jul 22, 2016

thanks so much for your help, sorry it took me a while to get this, feel free to close the issue

@fizruk
Copy link
Member

fizruk commented Jul 22, 2016

@leshow you're welcome! Sorry again for the delay in response to your original question!

@fizruk fizruk closed this as completed Jul 22, 2016
@bitemyapp
Copy link
Author

👍 Good to go here as well, thank you very much @fizruk 🐻

@ivnsch
Copy link

ivnsch commented Nov 12, 2017

I get a compiler error. Snippet:

myCors :: Middleware
myCors = cors (const $ Just policy)
    where
      policy = simpleCorsResourcePolicy
        { corsRequestHeaders = ["Content-Type"] }

Error:

    • Couldn't match type ‘[Char]’
                     with ‘case-insensitive-1.2.0.10:Data.CaseInsensitive.Internal.CI
                             ByteString’
      Expected type: Network.HTTP.Types.Header.HeaderName
        Actual type: [Char]
    • In the expression: "Content-Type"
      In the ‘corsRequestHeaders’ field of a record
      In the expression:
        simpleCorsResourcePolicy {corsRequestHeaders = ["Content-Type"]}

How do I pass HeaderName instead of [Char]? Was the api changed or am I missing something (I'm very new to this, sorry)?

@fizruk
Copy link
Member

fizruk commented Nov 13, 2017

@i-schuetz seems like you forgot to enable -XOverloadedStrings
(or {-# LANGUAGE OverloadedStrings #-}).

@ivnsch
Copy link

ivnsch commented Nov 13, 2017

Thanks @fizruk! (sorry for using this thread for this newbie question, it was just convenient)

fisx pushed a commit to wireapp/servant-swagger that referenced this issue Jun 1, 2019
@cd155
Copy link

cd155 commented Apr 17, 2023

@fizruk, thanks, this post really helps. Can this information be included in servant documentation? It seems fairly common that people want to include enabling cors feature while in the development environment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants