{"action":"created","issue":{"url":"https://api.github.com/repos/ninenines/cowboy/issues/947","repository_url":"https://api.github.com/repos/ninenines/cowboy","labels_url":"https://api.github.com/repos/ninenines/cowboy/issues/947/labels{/name}","comments_url":"https://api.github.com/repos/ninenines/cowboy/issues/947/comments","events_url":"https://api.github.com/repos/ninenines/cowboy/issues/947/events","html_url":"https://github.com/ninenines/cowboy/issues/947","id":133076689,"number":947,"title":"Make Cowboy CORS friendly","user":{"login":"manifest","id":439058,"avatar_url":"https://avatars.githubusercontent.com/u/439058?v=3","gravatar_id":"","url":"https://api.github.com/users/manifest","html_url":"https://github.com/manifest","followers_url":"https://api.github.com/users/manifest/followers","following_url":"https://api.github.com/users/manifest/following{/other_user}","gists_url":"https://api.github.com/users/manifest/gists{/gist_id}","starred_url":"https://api.github.com/users/manifest/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/manifest/subscriptions","organizations_url":"https://api.github.com/users/manifest/orgs","repos_url":"https://api.github.com/users/manifest/repos","events_url":"https://api.github.com/users/manifest/events{/privacy}","received_events_url":"https://api.github.com/users/manifest/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/ninenines/cowboy/labels/Feature%20request","name":"Feature request","color":"5319e7"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":22,"created_at":"2016-02-11T20:10:16Z","updated_at":"2016-06-05T06:41:27Z","closed_at":null,"body":"Maybe we could make Cowboy a bit more CORS friendly?\r\nBy now, to enable CORS we should work with raw headers, copy-pasting code like this from one header to another:\r\n```erlang\r\noptions(Req, State) ->\r\n  Req1 = cowboy_req:set_resp_header(<<\"access-control-max-age\">>, <<\"1728000\">>, Req0),\r\n  Req2 = cowboy_req:set_resp_header(<<\"access-control-allow-methods\">>, <<\"HEAD, GET, POST\">>, Req1),\r\n  Req3 = cowboy_req:set_resp_header(<<\"access-control-allow-headers\">>, <<\"content-type, authorization\">>, Req2),\r\n  Req4 = cowboy_req:set_resp_header(<<\"access-control-allow-origin\">>, <<$*>>, Req3),\r\n  {ok, Req, State}.\r\n```\r\nIn this way we do not actually care about allowed header's value types. For instance, \"access-control-allow-methods\" and \"access-control-allow-headers\" should only accept a list of binaries, while only a numeric value makes sense for the \"access-control-max-age\" header, \"*\" origin cannot be used for a resource that supports credentials. Ignoring this information leads to misspelling errors, breaking CORS responses and isn't flexible for working with.\r\n\r\nMy proposal is to add two new functions (`set_cors_headers/2` and `set_cors_preflight_headers/2`) to the `cowboy_req` module extending functionality of `set_resp_header/3`.\r\n\r\n```erlang\r\n-type cors_header()\r\n  :: {origin, binary() | list(binary())}.\r\n\r\n-type cors_preflight_header()\r\n  :: {age, max | non_neg_integer()}\r\n   | {methods, list(binary())}\r\n   | {headers, list(binary())}\r\n   | cors_header().\r\n\r\n-spec set_cors_headers(list(cors_header()), Req) -> Req when Req :: cowboy_req:req().\r\n-spec set_cors_preflight_headers(list(cors_preflight_header()), Req) -> Req when Req :: cowboy_req:req().\r\n```\r\n\r\nIn my projects, I'm using this simple syntax sugar in REST handlers:\r\n\r\n```erlang\r\nallowed_methods(Req, State) ->\r\n  Req2 = cowboy_req:set_cors_headers([{origin, <<$*>>}], Req),\r\n  {[<<\"HEAD\">>, <<\"GET\">>, <<\"POST\">>, <<\"OPTIONS\">>], Req2, State}.\r\n\r\noptions(Req, State) ->\r\n  Headers =\r\n    [ {age, max},\r\n      {origin, <<$*>>},\r\n      {methods, [<<\"HEAD\">>, <<\"GET\">>, <<\"POST\">>]},\r\n      {headers, [<<\"content-type\">>, <<\"authorization\">>]} ],\r\n  {ok, cowboy_req:set_cors_preflight_headers(Headers, Req), State}.\r\n```\r\n\r\nIt's also possible to use those functions in a middleware:\r\n\r\n```erlang\r\nexecute(Req, Env) ->\r\n  case cowboy_req:method(Req) of\r\n    <<\"OPTIONS\">> ->\r\n      Headers =\r\n        [ {age, max},\r\n          {origin, <<$*>>},\r\n          {methods, [<<\"HEAD\">>, <<\"GET\">>, <<\"POST\">>]},\r\n          {headers, [<<\"content-type\">>, <<\"authorization\">>]} ],\r\n      {ok, cowboy_req:set_cors_preflight_headers(Headers, Req), Env};\r\n    _ ->\r\n      {ok, cowboy_req:set_cors_headers([{origin, <<$*>>}], Req), Env}\r\n  end.\r\n```\r\n\r\nI believe those functions could make Cowboy yet more friendly and useful tool and would love to make a pull request, if it's ok with Cowboy's philosophy."},"comment":{"url":"https://api.github.com/repos/ninenines/cowboy/issues/comments/223796248","html_url":"https://github.com/ninenines/cowboy/issues/947#issuecomment-223796248","issue_url":"https://api.github.com/repos/ninenines/cowboy/issues/947","id":223796248,"user":{"login":"essen","id":141739,"avatar_url":"https://avatars.githubusercontent.com/u/141739?v=3","gravatar_id":"","url":"https://api.github.com/users/essen","html_url":"https://github.com/essen","followers_url":"https://api.github.com/users/essen/followers","following_url":"https://api.github.com/users/essen/following{/other_user}","gists_url":"https://api.github.com/users/essen/gists{/gist_id}","starred_url":"https://api.github.com/users/essen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/essen/subscriptions","organizations_url":"https://api.github.com/users/essen/orgs","repos_url":"https://api.github.com/users/essen/repos","events_url":"https://api.github.com/users/essen/events{/privacy}","received_events_url":"https://api.github.com/users/essen/received_events","type":"User","site_admin":false},"created_at":"2016-06-05T06:41:27Z","updated_at":"2016-06-05T06:41:27Z","body":"Yep will do. Almost done fixing a breaking bug, so probably after that."}}
{"actor":{"display_login":"essen"}}