Search code examples
ajaxerlangyaws

YAWS, CORS: How to define what OPTIONS should return?


I need to make CORS work. It seems that jquery $ajax makes a OPTIONS call, and that should return the necessary CORS headers. All my GET and POST already do this, but it doesn't seems to be enough. In NGINX, you would do something like this:

location / {
    if ($request_method = OPTIONS ) {
        add_header Access-Control-Allow-Origin "http://example.com";
        add_header Access-Control-Allow-Methods "GET, OPTIONS";
        add_header Access-Control-Allow-Headers "Authorization";
        add_header Access-Control-Allow-Credentials "true";
        add_header Content-Length 0;
        add_header Content-Type text/plain;
        return 200;
    }
}

How do I do the same in YAWS?


Solution

  • One way to handle an OPTIONS request is to use a Yaws dispatchmod, which is similar to a Yaws appmod, but Yaws calls it prior to doing any other request handling. Here's an example dispatch module based on the information in your question:

    -module(options_dispatcher).
    -export([dispatch/1]).
    
    -include_lib("yaws_api.hrl").
    
    dispatch(Arg) ->
        Req = yaws_api:arg_req(Arg),
        case yaws_api:http_request_method(Req) of
            'OPTIONS' ->
                Vsn = yaws_api:http_request_version(Req),
                Resp = #http_response{
                          version=Vsn,
                          status=200,
                          phrase=yaws_api:code_to_phrase(200)},
                HdrVals = [{"Access-Control-Allow-Origin", "http://example.com"},
                           {"Access-Control-Allow-Methods", "GET, OPTIONS"},
                           {"Access-Control-Allow-Headers", "Authorization"},
                           {"Access-Control-Allow-Credentials", "true"},
                           {"Content-Length", "0"},
                           {"Content-Type", "text/plain"}],
                Headers = lists:foldl(fun({H,V}, Hdrs) ->
                                              yaws_api:set_header(Hdrs, H, V)
                                      end, #headers{}, HdrVals),
                HdrStrings = yaws_api:reformat_header(Headers),
                Reply = [yaws_api:reformat_response(Resp), "\r\n",
                         string:join(HdrStrings, "\r\n"), "\r\n\r\n"],
                Sock = yaws_api:arg_clisock(Arg),
                case yaws_api:get_sslsocket(Sock) of
                    {ok, SslSock} ->
                        ssl:send(SslSock, Reply);
                    undefined ->
                        gen_tcp:send(Sock, Reply)
                end,
                done;
            _ ->
                continue
        end.
    

    This code receives a Yaws #arg{} record, same as an appmod, except notice that a dispatchmod must export a dispatch/1 function whereas an appmod must export an out/1 function. From there it retrieves the request information and checks the HTTP request method. If it's OPTIONS, the code creates a response record and sets up the response headers, formats them as strings, and then creates the Reply value which is an iolist containing the HTTP response status line, the formatted HTTP response headers, and "\r\n\r\n" to mark the end of the HTTP response. It then uses either ssl:send/2 or gen_tcp:send/2, based on what type of socket received the request, to send the reply directly. Finally it returns done to tell Yaws there's no more work to do for that request. For any HTTP method other than OPTIONS, the code returns continue to tell Yaws to perform its normal dispatching.

    To deploy the dispatcher, compile the code and place the resulting beam file in the Yaws load path. Then modify the server portion of your Yaws config to include the setting:

    dispatchmod = options_dispatcher
    

    This tells Yaws that the server has a dispatchmod that should be called as part of the request dispatch flow for that server. Then either start/restart Yaws or use

    yaws --hup --id ID
    

    to tell a running instance of Yaws to reload its configuration.