简体   繁体   中英

How to handle “OPTIONS” requests in suave

I'm porting a prototype app I did using elm and python flask to use elm and a suave backend. The elm app is calling an API to load info from the site and do some other things. There does not seem to be an issue with get requests, but I'm getting funny behaviour when doing the POST from elm - works for flask but the request does not seem to be accepted by suave.

Sorry for the long post, details below:

Elm code:

--POST IS WORKING TO Flask BUT NOT TO Suave
testPassword editMode token =
    let
        data = 
            Json.Encode.object
                [ ( "email", Json.Encode.string editMode.email )
                , ( "password",  Json.Encode.string editMode.newValue )
                ]
        body = 
            Json.Encode.object [ ("data", data) ]
        decodeVal value = 
            Json.Decode.map2 RestResponse
                (field "success" Json.Decode.bool)        
                (field "errorMessage" Json.Decode.string)        
        valDecoder =
            Json.Decode.value
                |> Json.Decode.andThen decodeVal
        postTo =
            String.concat [ apiUrl, token, "/api/password/test" ]                
    in
    Json.Decode.field "data" valDecoder
        |> Http.post (postTo) (jsonBody body)
        |> Http.send UpdateValue            

Debugging in chrome I can see for python flask the OPTIONS request goes through and the response indicates a POST is required

General
Request URL: http://localhost:5000/api/password/test
Request Method: OPTIONS
Status Code: 200 OK
Remote Address: 127.0.0.1:5000
Referrer Policy: no-referrer-when-downgrade
Response Headers
Access-Control-Allow-Headers: *
Access-Control-Allow-Origin: *
Allow: OPTIONS, POST
Content-Length: 0
Content-Type: text/html; charset=utf-8
Date: Wed, 30 May 2018 09:35:08 GMT
Server: Werkzeug/0.14.1 Python/3.5.2

However, with Suave, the OPTIONS request is incomplete or interrupted:

General
Request URL: http://localhost:8080/api/password/test
Referrer Policy: no-referrer-when-downgrade
Request Headers
Provisional headers are shown
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: POST
Origin: http://localhost:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36

My question is what do I need to do on the suave program side to get this working? I suspect it is either suave configuration or I need to code a WebPart to respond to the options request. Suave code below:

let setCORSHeaders =
    Console.WriteLine("Enabling cross origin requests")
    addHeader  "Access-Control-Allow-Origin" "*" 
    >=> setHeader "Access-Control-Allow-Headers" "token" 
    >=> addHeader "Access-Control-Allow-Headers" "content-type" 
    >=> addHeader "Access-Control-Allow-Methods" "GET,OPTIONS,POST,PUT" 

let allowCors : WebPart =
    choose [
        GET >=>
            fun context ->
                context |> (
                    setCORSHeaders )
    ]

let app =
    ..
    statefulForSession
    >=> allowCors
    >=> choose
        [ GET >=> choose
            [ //.. 
            ]
          POST >=> choose
            [ //other posts working 
              path "/api/password/test" >=> context apiController.passwordTest 
            ] 
          OPTIONS >=> choose
            [ //tried this as well but don't think it's correct
              path "/api/password/test" >=> context apiController.passwordTest 
            ] ]

let suaveCfg =
  { defaultConfig with
      serverKey = Convert.FromBase64String encodedkey
      cookieSerialiser = new JsonNetCookieSerialiser()
    }

[<EntryPoint>]
let main argv =
    startWebServer suaveCfg app
    0

Thanks for reading

I suspect that this is your problem:

let allowCors : WebPart =
    choose [
        GET >=>
            fun context ->
                context |> (
                    setCORSHeaders )
    ]

There's no OPTIONS case here, so when an OPTIONS request comes through your app, the choose combinator finds nothing that matches the request and so returns None , which means further parts of the handling chain are never called. And in your app, allowCors comes before the part of the chain that handles OPTIONS :

let app =
    ..
    statefulForSession
    >=> allowCors
    >=> choose
        [ GET >=> choose
            [ //.. 
            ]
          // Elided the POST part here
          OPTIONS >=> choose
            [ //tried this as well but don't think it's correct
              path "/api/password/test" >=> context apiController.passwordTest 
            ] ]

Put an OPTIONS section in your allowCors WebPart and your code should work, I think.

Edit: Also, this chunk of code in allowCors can be improved:

            fun context ->
                context |> (
                    setCORSHeaders )

Any time you have fun a -> a |> otherFunction , you can replace that expression with otherFunction . So your allowCors function, as written, looks like this:

let allowCors : WebPart =
    choose [
        GET >=>
            fun context ->
                context |> (
                    setCORSHeaders )
    ]

But it could look like this instead:

let allowCors : WebPart =
    choose [
        GET >=> setCORSHeaders
    ]

Much easier to read, don't you think?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM