+23
.github/workflows/test.yml
+23
.github/workflows/test.yml
···
1
+
name: test
2
+
3
+
on:
4
+
push:
5
+
branches:
6
+
- master
7
+
- main
8
+
pull_request:
9
+
10
+
jobs:
11
+
test:
12
+
runs-on: ubuntu-latest
13
+
steps:
14
+
- uses: actions/checkout@v4
15
+
- uses: erlef/setup-beam@v1
16
+
with:
17
+
otp-version: "26.0.2"
18
+
gleam-version: "1.1.0"
19
+
rebar3-version: "3"
20
+
# elixir-version: "1.15.4"
21
+
- run: gleam deps download
22
+
- run: gleam test
23
+
- run: gleam format --check src test
+1
.prettierrc
+1
.prettierrc
···
1
+
{ "printWidth": 80, "proseWrap": "always" }
+108
README.md
+108
README.md
···
1
+
# cors
2
+
3
+
## Middlewares
4
+
5
+
```gleam
6
+
import gleam/http
7
+
import mist
8
+
import simple_cors as cors
9
+
import wisp.{type Request, type Response}
10
+
11
+
fn cors() {
12
+
cors.new()
13
+
|> cors.allow_origin("http://localhost:3000")
14
+
|> cors.allow_origin("http://localhost:4000")
15
+
|> cors.allow_method(http.Get)
16
+
|> cors.allow_method(http.Post)
17
+
}
18
+
19
+
fn handler(req: Request) -> Response {
20
+
use req <- cors.wisp_handle(req, cors())
21
+
wisp.ok()
22
+
}
23
+
24
+
fn main() {
25
+
handler
26
+
|> wisp.mist_handler(secret_key)
27
+
|> mist.new()
28
+
|> mist.port(3000)
29
+
|> mist.start_http()
30
+
}
31
+
```
32
+
33
+
## What is CORS?
34
+
35
+
Browsers apply a simple rules for every HTTP request: when the request
36
+
originates from a different origin than the target server URL — and if it's not
37
+
a simple request — the browser needs to authorize the cross-origin call.
38
+
39
+
> From the HTTP point of view, a simple request respects the following
40
+
> conditions:
41
+
>
42
+
> - Allowed methods are `GET`, `HEAD` or `POST`
43
+
> - Allowed headers are `Accept`, `Accept-Language`, `Content-Language` and
44
+
> `Content-Type`
45
+
> - `Content-Type` should be:
46
+
> - `application/x-www-form-urlencoded`
47
+
> - `multipart/form-data`
48
+
> - `text/plain`
49
+
> - No event listener has been added on `XMLHttpRequestUpload`.
50
+
> `XMLHttpRequestUpload.upload` is preferred.
51
+
> - No `ReadableStream` is used in the request.
52
+
53
+
To authorize the call, the browser will issue a first request, called a
54
+
"preflight" request. This request takes the form of an `OPTIONS` request, which
55
+
should be answered positively by the server (meaning the response status code
56
+
should be 2XX) and should contains the appropriate CORS headers
57
+
(`Access-Control` headers).
58
+
59
+
In case the preflight request is not successful, the server will simply cancel
60
+
the HTTP request. But if the preflight request is successful, then the browser
61
+
will then launch the real request, and the server will be able to handle it.
62
+
63
+
## What are the headers?
64
+
65
+
We distinguish different types of headers: the headers concerning the request
66
+
issuer (the caller) and the headers responded by the server.
67
+
68
+
### Response headers
69
+
70
+
Response headers are not automatically set by the server, and you should handle
71
+
them according on what you want to do. This package tries to abstract it to
72
+
simplify your development and let you focus on your application. We count 6 CORS
73
+
response headers:
74
+
75
+
- `Access-Control-Allow-Origin`, indicates which origins are allowed to access
76
+
the server. It can be a joker (`"*"`) or a unique domain
77
+
(`https://gleam.run`). It cannot contains multiple domains, but can response
78
+
to multiple different domains with the `VARY` header. You should not have to
79
+
take care of this, because the library provides it for you.
80
+
- `Access-Control-Expose-Headers`, provides a whitelist of allowed headers for
81
+
the browsers. Only the headers in the whitelist will be able to be used in the
82
+
response object in the JS code. It means if the response contains headers you
83
+
want to cache to the client, you can use this header.
84
+
- `Access-Control-Max-Age`, allows to put the preflight response in cache, for a
85
+
specified amount of time. This avoids to rerun the `OPTIONS` request multiple
86
+
times.
87
+
- `Access-Control-Allow-Credentials`, allows the request to includes credentials
88
+
authorizations. This can expose you to CSRF attack. Never activate this option
89
+
unless you carefully know what you're doing.
90
+
- `Access-Control-Allow-Methods`, provides a whitelist of subsequent authorized
91
+
methods in the future requests.
92
+
- `Access-Control-Allow-Headers`, indicates which headers are accepted by the
93
+
server, and thus, which headers the browser will be able to send in subsequent
94
+
requests.
95
+
96
+
### Request headers
97
+
98
+
Request headers are headers automatically set by the browser, when issuing a
99
+
request with `XMLHttpRequest` or `fetch`. You should not bother about it, but
100
+
they're still referenced it, in case you encounter them.We count 3 CORS request
101
+
headers:
102
+
103
+
- `Origin` contains the origin of the request. The browser will _always_ fill
104
+
this header automatically.
105
+
- `Access-Control-Request-Method` contains the desired methods to use when
106
+
talking with the server.
107
+
- `Access-Control-Request-Header` contains the desired headers that the request
108
+
want to have.
+23
e2e/mist_test/.github/workflows/test.yml
+23
e2e/mist_test/.github/workflows/test.yml
···
1
+
name: test
2
+
3
+
on:
4
+
push:
5
+
branches:
6
+
- master
7
+
- main
8
+
pull_request:
9
+
10
+
jobs:
11
+
test:
12
+
runs-on: ubuntu-latest
13
+
steps:
14
+
- uses: actions/checkout@v4
15
+
- uses: erlef/setup-beam@v1
16
+
with:
17
+
otp-version: "26.0.2"
18
+
gleam-version: "1.1.0"
19
+
rebar3-version: "3"
20
+
# elixir-version: "1.15.4"
21
+
- run: gleam deps download
22
+
- run: gleam test
23
+
- run: gleam format --check src test
+25
e2e/mist_test/README.md
+25
e2e/mist_test/README.md
···
1
+
# mist_test
2
+
3
+
[](https://hex.pm/packages/mist_test)
4
+
[](https://hexdocs.pm/mist_test/)
5
+
6
+
```sh
7
+
gleam add mist_test
8
+
```
9
+
```gleam
10
+
import mist_test
11
+
12
+
pub fn main() {
13
+
// TODO: An example of the project in use
14
+
}
15
+
```
16
+
17
+
Further documentation can be found at <https://hexdocs.pm/mist_test>.
18
+
19
+
## Development
20
+
21
+
```sh
22
+
gleam run # Run the project
23
+
gleam test # Run the tests
24
+
gleam shell # Run an Erlang shell
25
+
```
+23
e2e/mist_test/gleam.toml
+23
e2e/mist_test/gleam.toml
···
1
+
name = "mist_test"
2
+
version = "1.0.0"
3
+
4
+
# Fill out these fields if you intend to generate HTML documentation or publish
5
+
# your project to the Hex package manager.
6
+
#
7
+
# description = ""
8
+
# licences = ["Apache-2.0"]
9
+
# repository = { type = "github", user = "username", repo = "project" }
10
+
# links = [{ title = "Website", href = "https://gleam.run" }]
11
+
#
12
+
# For a full reference of all the available options, you can have a look at
13
+
# https://gleam.run/writing-gleam/gleam-toml/.
14
+
15
+
[dependencies]
16
+
gleam_http = ">= 3.6.0 and < 4.0.0"
17
+
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
18
+
mist = ">= 1.0.0 and < 2.0.0"
19
+
simple_cors = {path = "../.."}
20
+
gleam_erlang = ">= 0.25.0 and < 1.0.0"
21
+
22
+
[dev-dependencies]
23
+
gleeunit = ">= 1.0.0 and < 2.0.0"
+33
e2e/mist_test/manifest.toml
+33
e2e/mist_test/manifest.toml
···
1
+
# This file was generated by Gleam
2
+
# You typically do not need to edit this file
3
+
4
+
packages = [
5
+
{ name = "birl", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "976CFF85D34D50F7775896615A71745FBE0C325E50399787088F941B539A0497" },
6
+
{ name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" },
7
+
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
8
+
{ name = "gleam_crypto", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "ADD058DEDE8F0341F1ADE3AAC492A224F15700829D9A3A3F9ADF370F875C51B7" },
9
+
{ name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" },
10
+
{ name = "gleam_http", version = "3.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "8C07DF9DF8CC7F054C650839A51C30A7D3C26482AC241C899C1CEA86B22DBE51" },
11
+
{ name = "gleam_json", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "8B197DD5D578EA6AC2C0D4BDC634C71A5BCA8E7DB5F47091C263ECB411A60DF3" },
12
+
{ name = "gleam_otp", version = "0.10.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "0B04FE915ACECE539B317F9652CAADBBC0F000184D586AAAF2D94C100945D72B" },
13
+
{ name = "gleam_stdlib", version = "0.37.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "5398BD6C2ABA17338F676F42F404B9B7BABE1C8DC7380031ACB05BBE1BCF3742" },
14
+
{ name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" },
15
+
{ name = "glisten", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "glisten", source = "hex", outer_checksum = "CF3A9383E9BA4A8CBAF2F7B799716290D02F2AC34E7A77556B49376B662B9314" },
16
+
{ name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" },
17
+
{ name = "logging", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "82C112ED9B6C30C1772A6FE2613B94B13F62EA35F5869A2630D13948D297BD39" },
18
+
{ name = "marceau", version = "1.1.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "1AAD727A30BE0F95562C3403BB9B27C823797AD90037714255EEBF617B1CDA81" },
19
+
{ name = "mist", version = "1.0.0", build_tools = ["gleam"], requirements = ["birl", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "7765E53DCC9ACCACF217B8E0CA3DE7E848C783BFAE5118B75011E81C2C80385C" },
20
+
{ name = "ranger", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "1566C272B1D141B3BBA38B25CB761EF56E312E79EC0E2DFD4D3C19FB0CC1F98C" },
21
+
{ name = "simple_cors", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib", "mist", "wisp"], source = "local", path = "../.." },
22
+
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
23
+
{ name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" },
24
+
{ name = "wisp", version = "0.14.0", build_tools = ["gleam"], requirements = ["exception", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "9F5453AF1F9275E6F8707BC815D6A6A9DF41551921B16FBDBA52883773BAE684" },
25
+
]
26
+
27
+
[requirements]
28
+
gleam_erlang = { version = ">= 0.25.0 and < 1.0.0" }
29
+
gleam_http = { version = ">= 3.6.0 and < 4.0.0" }
30
+
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
31
+
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
32
+
mist = { version = ">= 1.0.0 and < 2.0.0" }
33
+
simple_cors = { path = "../.." }
+30
e2e/mist_test/src/mist_test.gleam
+30
e2e/mist_test/src/mist_test.gleam
···
1
+
import gleam/bytes_builder
2
+
import gleam/erlang/process
3
+
import gleam/http
4
+
import gleam/http/request.{type Request}
5
+
import gleam/http/response.{type Response}
6
+
import mist.{type Connection, type ResponseData}
7
+
import simple_cors as cors
8
+
9
+
fn cors() {
10
+
cors.new()
11
+
|> cors.allow_origin("http://localhost:3000")
12
+
|> cors.allow_method(http.Get)
13
+
|> cors.allow_method(http.Post)
14
+
}
15
+
16
+
fn main_handler(req: Request(Connection)) -> Response(ResponseData) {
17
+
use _req <- cors.mist_handle(req, cors())
18
+
let empty = mist.Bytes(bytes_builder.new())
19
+
response.new(200)
20
+
|> response.set_body(empty)
21
+
}
22
+
23
+
pub fn main() {
24
+
let assert Ok(_) =
25
+
main_handler
26
+
|> mist.new()
27
+
|> mist.port(8080)
28
+
|> mist.start_http()
29
+
process.sleep(5000)
30
+
}
+12
e2e/mist_test/test/mist_test_test.gleam
+12
e2e/mist_test/test/mist_test_test.gleam
+23
e2e/wisp_test/.github/workflows/test.yml
+23
e2e/wisp_test/.github/workflows/test.yml
···
1
+
name: test
2
+
3
+
on:
4
+
push:
5
+
branches:
6
+
- master
7
+
- main
8
+
pull_request:
9
+
10
+
jobs:
11
+
test:
12
+
runs-on: ubuntu-latest
13
+
steps:
14
+
- uses: actions/checkout@v4
15
+
- uses: erlef/setup-beam@v1
16
+
with:
17
+
otp-version: "26.0.2"
18
+
gleam-version: "1.1.0"
19
+
rebar3-version: "3"
20
+
# elixir-version: "1.15.4"
21
+
- run: gleam deps download
22
+
- run: gleam test
23
+
- run: gleam format --check src test
+25
e2e/wisp_test/README.md
+25
e2e/wisp_test/README.md
···
1
+
# wisp_test
2
+
3
+
[](https://hex.pm/packages/wisp_test)
4
+
[](https://hexdocs.pm/wisp_test/)
5
+
6
+
```sh
7
+
gleam add wisp_test
8
+
```
9
+
```gleam
10
+
import wisp_test
11
+
12
+
pub fn main() {
13
+
// TODO: An example of the project in use
14
+
}
15
+
```
16
+
17
+
Further documentation can be found at <https://hexdocs.pm/wisp_test>.
18
+
19
+
## Development
20
+
21
+
```sh
22
+
gleam run # Run the project
23
+
gleam test # Run the tests
24
+
gleam shell # Run an Erlang shell
25
+
```
+24
e2e/wisp_test/gleam.toml
+24
e2e/wisp_test/gleam.toml
···
1
+
name = "wisp_test"
2
+
version = "1.0.0"
3
+
4
+
# Fill out these fields if you intend to generate HTML documentation or publish
5
+
# your project to the Hex package manager.
6
+
#
7
+
# description = ""
8
+
# licences = ["Apache-2.0"]
9
+
# repository = { type = "github", user = "username", repo = "project" }
10
+
# links = [{ title = "Website", href = "https://gleam.run" }]
11
+
#
12
+
# For a full reference of all the available options, you can have a look at
13
+
# https://gleam.run/writing-gleam/gleam-toml/.
14
+
15
+
[dependencies]
16
+
gleam_http = ">= 3.6.0 and < 4.0.0"
17
+
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
18
+
mist = ">= 1.0.0 and < 2.0.0"
19
+
simple_cors = {path = "../.."}
20
+
wisp = ">= 0.14.0 and < 1.0.0"
21
+
gleam_erlang = ">= 0.25.0 and < 1.0.0"
22
+
23
+
[dev-dependencies]
24
+
gleeunit = ">= 1.0.0 and < 2.0.0"
+34
e2e/wisp_test/manifest.toml
+34
e2e/wisp_test/manifest.toml
···
1
+
# This file was generated by Gleam
2
+
# You typically do not need to edit this file
3
+
4
+
packages = [
5
+
{ name = "birl", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "976CFF85D34D50F7775896615A71745FBE0C325E50399787088F941B539A0497" },
6
+
{ name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" },
7
+
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
8
+
{ name = "gleam_crypto", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "ADD058DEDE8F0341F1ADE3AAC492A224F15700829D9A3A3F9ADF370F875C51B7" },
9
+
{ name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" },
10
+
{ name = "gleam_http", version = "3.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "8C07DF9DF8CC7F054C650839A51C30A7D3C26482AC241C899C1CEA86B22DBE51" },
11
+
{ name = "gleam_json", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "8B197DD5D578EA6AC2C0D4BDC634C71A5BCA8E7DB5F47091C263ECB411A60DF3" },
12
+
{ name = "gleam_otp", version = "0.10.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "0B04FE915ACECE539B317F9652CAADBBC0F000184D586AAAF2D94C100945D72B" },
13
+
{ name = "gleam_stdlib", version = "0.37.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "5398BD6C2ABA17338F676F42F404B9B7BABE1C8DC7380031ACB05BBE1BCF3742" },
14
+
{ name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" },
15
+
{ name = "glisten", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "glisten", source = "hex", outer_checksum = "CF3A9383E9BA4A8CBAF2F7B799716290D02F2AC34E7A77556B49376B662B9314" },
16
+
{ name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" },
17
+
{ name = "logging", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "82C112ED9B6C30C1772A6FE2613B94B13F62EA35F5869A2630D13948D297BD39" },
18
+
{ name = "marceau", version = "1.1.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "1AAD727A30BE0F95562C3403BB9B27C823797AD90037714255EEBF617B1CDA81" },
19
+
{ name = "mist", version = "1.0.0", build_tools = ["gleam"], requirements = ["birl", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "7765E53DCC9ACCACF217B8E0CA3DE7E848C783BFAE5118B75011E81C2C80385C" },
20
+
{ name = "ranger", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "1566C272B1D141B3BBA38B25CB761EF56E312E79EC0E2DFD4D3C19FB0CC1F98C" },
21
+
{ name = "simple_cors", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib", "mist", "wisp"], source = "local", path = "../.." },
22
+
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
23
+
{ name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" },
24
+
{ name = "wisp", version = "0.14.0", build_tools = ["gleam"], requirements = ["exception", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "9F5453AF1F9275E6F8707BC815D6A6A9DF41551921B16FBDBA52883773BAE684" },
25
+
]
26
+
27
+
[requirements]
28
+
gleam_erlang = { version = ">= 0.25.0 and < 1.0.0"}
29
+
gleam_http = { version = ">= 3.6.0 and < 4.0.0" }
30
+
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
31
+
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
32
+
mist = { version = ">= 1.0.0 and < 2.0.0" }
33
+
simple_cors = { path = "../.." }
34
+
wisp = { version = ">= 0.14.0 and < 1.0.0" }
+29
e2e/wisp_test/src/wisp_test.gleam
+29
e2e/wisp_test/src/wisp_test.gleam
···
1
+
import gleam/erlang/process
2
+
import gleam/http
3
+
import mist
4
+
import simple_cors as cors
5
+
import wisp.{type Request, type Response}
6
+
7
+
fn cors() {
8
+
cors.new()
9
+
|> cors.allow_origin("http://localhost:3000")
10
+
|> cors.allow_origin("http://localhost:4000")
11
+
|> cors.allow_method(http.Get)
12
+
|> cors.allow_method(http.Post)
13
+
}
14
+
15
+
fn main_handler(req: Request) -> Response {
16
+
use _req <- cors.wisp_handle(req, cors())
17
+
wisp.ok()
18
+
}
19
+
20
+
pub fn main() {
21
+
let secret_key = wisp.random_string(64)
22
+
let assert Ok(_) =
23
+
main_handler
24
+
|> wisp.mist_handler(secret_key)
25
+
|> mist.new()
26
+
|> mist.port(8080)
27
+
|> mist.start_http()
28
+
process.sleep(5000)
29
+
}
+12
e2e/wisp_test/test/wisp_test_test.gleam
+12
e2e/wisp_test/test/wisp_test_test.gleam
+22
gleam.toml
+22
gleam.toml
···
1
+
name = "simple_cors"
2
+
version = "1.0.0"
3
+
4
+
# Fill out these fields if you intend to generate HTML documentation or publish
5
+
# your project to the Hex package manager.
6
+
#
7
+
# description = ""
8
+
# licences = ["Apache-2.0"]
9
+
# repository = { type = "github", user = "username", repo = "project" }
10
+
# links = [{ title = "Website", href = "https://gleam.run" }]
11
+
#
12
+
# For a full reference of all the available options, you can have a look at
13
+
# https://gleam.run/writing-gleam/gleam-toml/.
14
+
15
+
[dependencies]
16
+
gleam_http = ">= 3.6.0 and < 4.0.0"
17
+
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
18
+
mist = ">= 1.0.0 and < 2.0.0"
19
+
wisp = ">= 0.14.0 and < 1.0.0"
20
+
21
+
[dev-dependencies]
22
+
gleeunit = ">= 1.0.0 and < 2.0.0"
+31
manifest.toml
+31
manifest.toml
···
1
+
# This file was generated by Gleam
2
+
# You typically do not need to edit this file
3
+
4
+
packages = [
5
+
{ name = "birl", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "976CFF85D34D50F7775896615A71745FBE0C325E50399787088F941B539A0497" },
6
+
{ name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" },
7
+
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
8
+
{ name = "gleam_crypto", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "ADD058DEDE8F0341F1ADE3AAC492A224F15700829D9A3A3F9ADF370F875C51B7" },
9
+
{ name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" },
10
+
{ name = "gleam_http", version = "3.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "8C07DF9DF8CC7F054C650839A51C30A7D3C26482AC241C899C1CEA86B22DBE51" },
11
+
{ name = "gleam_json", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "8B197DD5D578EA6AC2C0D4BDC634C71A5BCA8E7DB5F47091C263ECB411A60DF3" },
12
+
{ name = "gleam_otp", version = "0.10.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "0B04FE915ACECE539B317F9652CAADBBC0F000184D586AAAF2D94C100945D72B" },
13
+
{ name = "gleam_stdlib", version = "0.37.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "5398BD6C2ABA17338F676F42F404B9B7BABE1C8DC7380031ACB05BBE1BCF3742" },
14
+
{ name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" },
15
+
{ name = "glisten", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "glisten", source = "hex", outer_checksum = "CF3A9383E9BA4A8CBAF2F7B799716290D02F2AC34E7A77556B49376B662B9314" },
16
+
{ name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" },
17
+
{ name = "logging", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "82C112ED9B6C30C1772A6FE2613B94B13F62EA35F5869A2630D13948D297BD39" },
18
+
{ name = "marceau", version = "1.1.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "1AAD727A30BE0F95562C3403BB9B27C823797AD90037714255EEBF617B1CDA81" },
19
+
{ name = "mist", version = "1.0.0", build_tools = ["gleam"], requirements = ["birl", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "7765E53DCC9ACCACF217B8E0CA3DE7E848C783BFAE5118B75011E81C2C80385C" },
20
+
{ name = "ranger", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "1566C272B1D141B3BBA38B25CB761EF56E312E79EC0E2DFD4D3C19FB0CC1F98C" },
21
+
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
22
+
{ name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" },
23
+
{ name = "wisp", version = "0.14.0", build_tools = ["gleam"], requirements = ["exception", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "9F5453AF1F9275E6F8707BC815D6A6A9DF41551921B16FBDBA52883773BAE684" },
24
+
]
25
+
26
+
[requirements]
27
+
gleam_http = { version = ">= 3.6.0 and < 4.0.0" }
28
+
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
29
+
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
30
+
mist = { version = ">= 1.0.0 and < 2.0.0"}
31
+
wisp = { version = ">= 0.14.0 and < 1.0.0" }
+227
src/simple_cors.gleam
+227
src/simple_cors.gleam
···
1
+
import gleam/bool
2
+
import gleam/bytes_builder
3
+
import gleam/function
4
+
import gleam/http.{type Method}
5
+
import gleam/http/request.{type Request}
6
+
import gleam/http/response.{type Response, set_header}
7
+
import gleam/int
8
+
import gleam/list
9
+
import gleam/option.{type Option, None, Some}
10
+
import gleam/pair
11
+
import gleam/result
12
+
import gleam/set.{type Set}
13
+
import gleam/string
14
+
import mist
15
+
import wisp
16
+
17
+
pub opaque type Origin {
18
+
Wildcard
19
+
Origin(Set(String))
20
+
}
21
+
22
+
pub opaque type Cors {
23
+
Cors(
24
+
allow_origin: Option(Origin),
25
+
expose_headers: Set(String),
26
+
max_age: Option(Int),
27
+
allow_credentials: Option(Bool),
28
+
allow_methods: Set(Method),
29
+
allow_headers: Set(String),
30
+
)
31
+
}
32
+
33
+
pub fn new() -> Cors {
34
+
Cors(
35
+
allow_origin: None,
36
+
expose_headers: set.new(),
37
+
max_age: None,
38
+
allow_credentials: None,
39
+
allow_methods: set.new(),
40
+
allow_headers: set.new(),
41
+
)
42
+
}
43
+
44
+
/// Be extremely careful, you should not use this function in production!
45
+
/// Allowing all origins can easily be a huge security flaw!
46
+
/// Allow only the origins you need, and use this function only locally, in dev mode.
47
+
pub fn allow_all_origins(cors: Cors) {
48
+
let allow_origin = Some(Wildcard)
49
+
Cors(..cors, allow_origin: allow_origin)
50
+
}
51
+
52
+
pub fn allow_origin(cors: Cors, origin: String) {
53
+
let allow_origin = case cors.allow_origin {
54
+
Some(Wildcard) -> Some(Wildcard)
55
+
Some(Origin(content)) -> Some(Origin(set.insert(content, origin)))
56
+
None -> Some(Origin(set.from_list([origin])))
57
+
}
58
+
Cors(..cors, allow_origin: allow_origin)
59
+
}
60
+
61
+
pub fn expose_headers(cors: Cors, header: String) {
62
+
let expose_headers = set.insert(cors.expose_headers, header)
63
+
Cors(..cors, expose_headers: expose_headers)
64
+
}
65
+
66
+
pub fn max_age(cors: Cors, age: Int) {
67
+
let max_age = Some(age)
68
+
Cors(..cors, max_age: max_age)
69
+
}
70
+
71
+
pub fn allow_credentials(cors: Cors) {
72
+
let allow_credentials = Some(True)
73
+
Cors(..cors, allow_credentials: allow_credentials)
74
+
}
75
+
76
+
pub fn allow_method(cors: Cors, method: Method) {
77
+
let allow_methods = set.insert(cors.allow_methods, method)
78
+
Cors(..cors, allow_methods: allow_methods)
79
+
}
80
+
81
+
pub fn allow_header(cors: Cors, header: String) {
82
+
let allow_headers = set.insert(cors.allow_headers, header)
83
+
Cors(..cors, allow_headers: allow_headers)
84
+
}
85
+
86
+
fn set_allowed_origin(cors: Cors, origin: String) {
87
+
let hd = "Access-Control-Allow-Origin"
88
+
case cors.allow_origin {
89
+
None -> function.identity
90
+
Some(Wildcard) -> set_header(_, hd, "*")
91
+
Some(Origin(origins)) -> {
92
+
let origins = set.to_list(origins)
93
+
case origins {
94
+
[o] -> set_header(_, hd, o)
95
+
_ -> {
96
+
let not_origin = !list.contains(origins, origin)
97
+
use <- bool.guard(when: not_origin, return: function.identity)
98
+
fn(res) {
99
+
res
100
+
|> set_header(hd, origin)
101
+
|> set_header("Vary", "Origin")
102
+
}
103
+
}
104
+
}
105
+
}
106
+
}
107
+
}
108
+
109
+
fn set_expose_headers(res: Response(body), cors: Cors) {
110
+
let hd = "Access-Control-Expose-Headers"
111
+
cors.expose_headers
112
+
|> set.to_list()
113
+
|> string.join(",")
114
+
|> set_header(res, hd, _)
115
+
}
116
+
117
+
fn set_max_age(res: Response(body), cors: Cors) {
118
+
let hd = "Access-Control-Max-Age"
119
+
cors.max_age
120
+
|> option.map(fn(a) { set_header(res, hd, int.to_string(a)) })
121
+
|> option.unwrap(res)
122
+
}
123
+
124
+
fn set_allow_credentials(res: Response(body), cors: Cors) {
125
+
let hd = "Access-Control-Allow-Credentials"
126
+
cors.allow_credentials
127
+
|> option.map(fn(_) { set_header(res, hd, "true") })
128
+
|> option.unwrap(res)
129
+
}
130
+
131
+
fn method_to_string(method: Method) {
132
+
case method {
133
+
http.Get -> "GET"
134
+
http.Post -> "POST"
135
+
http.Head -> "HEAD"
136
+
http.Put -> "PUT"
137
+
http.Delete -> "DELETE"
138
+
http.Trace -> "TRACE"
139
+
http.Connect -> "CONNECT"
140
+
http.Options -> "OPTIONS"
141
+
http.Patch -> "PATCH"
142
+
http.Other(content) -> content
143
+
}
144
+
}
145
+
146
+
fn set_allow_methods(res: Response(body), cors: Cors) {
147
+
let hd = "Access-Control-Allow-Methods"
148
+
let methods = set.to_list(cors.allow_methods)
149
+
case list.is_empty(methods) {
150
+
True -> res
151
+
False ->
152
+
methods
153
+
|> list.map(method_to_string)
154
+
|> string.join(",")
155
+
|> set_header(res, hd, _)
156
+
}
157
+
}
158
+
159
+
fn set_allow_headers(res: Response(body), cors: Cors) {
160
+
let hd = "Access-Control-Allow-Headers"
161
+
let headers = set.to_list(cors.allow_headers)
162
+
case list.is_empty(headers) {
163
+
True -> res
164
+
False ->
165
+
headers
166
+
|> string.join(",")
167
+
|> set_header(res, hd, _)
168
+
}
169
+
}
170
+
171
+
fn set_response(res: Response(body), cors: Cors, origin: Option(String)) {
172
+
res
173
+
|> set_allowed_origin(cors, option.unwrap(origin, ""))
174
+
|> set_expose_headers(cors)
175
+
|> set_max_age(cors)
176
+
|> set_allow_credentials(cors)
177
+
|> set_allow_methods(cors)
178
+
|> set_allow_headers(cors)
179
+
}
180
+
181
+
pub fn set_cors(res: Response(response), cors: Cors) {
182
+
set_response(res, cors, None)
183
+
}
184
+
185
+
pub fn set_cors_origin(res: Response(response), cors: Cors, origin: String) {
186
+
set_response(res, cors, Some(origin))
187
+
}
188
+
189
+
fn find_origin(req: Request(connection)) {
190
+
req.headers
191
+
|> list.find(fn(h) { pair.first(h) == "Origin" })
192
+
|> result.map(pair.second)
193
+
|> result.unwrap("")
194
+
}
195
+
196
+
fn middleware(
197
+
empty: resdata,
198
+
req: Request(connection),
199
+
cors: Cors,
200
+
handler: fn(Request(connection)) -> Response(resdata),
201
+
) {
202
+
let res = case req.method {
203
+
http.Options -> response.set_body(response.new(204), empty)
204
+
_ -> handler(req)
205
+
}
206
+
req
207
+
|> find_origin()
208
+
|> set_cors_origin(res, cors, _)
209
+
}
210
+
211
+
pub fn mist_handle(
212
+
req: Request(mist.Connection),
213
+
cors: Cors,
214
+
handler: fn(Request(mist.Connection)) -> Response(mist.ResponseData),
215
+
) {
216
+
bytes_builder.new()
217
+
|> mist.Bytes()
218
+
|> middleware(req, cors, handler)
219
+
}
220
+
221
+
pub fn wisp_handle(
222
+
req: wisp.Request,
223
+
cors: Cors,
224
+
handler: fn(wisp.Request) -> wisp.Response,
225
+
) {
226
+
middleware(wisp.Empty, req, cors, handler)
227
+
}