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

Add support for gleam v1.0.0 #20

Merged
merged 4 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- uses: erlef/setup-beam@v1
with:
otp-version: "26.1"
gleam-version: "0.32.0"
gleam-version: "1.0.0"
rebar3-version: "3"
- run: gleam format --check src test
- run: gleam test
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ gleam add gleam_cowboy
```gleam
import gleam/erlang/process
import gleam/http/cowboy
import gleam/http/response.{Response}
import gleam/http/request.{Request}
import gleam/bit_builder.{BitBuilder}
import gleam/http/response.{type Response}
import gleam/http/request.{type Request}
import gleam/bytes_builder.{type BytesBuilder}

// Define a HTTP service
//
pub fn my_service(request: Request(t)) -> Response(BitBuilder) {
let body = bit_builder.from_string("Hello, world!")
pub fn my_service(request: Request(t)) -> Response(BytesBuilder) {
let body = bytes_builder.from_string("Hello, world!")

response.new(200)
|> response.prepend_header("made-with", "Gleam")
Expand Down
6 changes: 3 additions & 3 deletions gleam.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name = "gleam_cowboy"
version = "0.6.0"
version = "0.7.0"
licences = ["Apache-2.0"]
description = "Run Gleam HTTP services with the Cowboy web server"
gleam = ">= 0.32.0"
gleam = ">= 1.0.0"

repository = { type = "github", user = "gleam-lang", repo = "cowboy" }
links = [
Expand All @@ -14,7 +14,7 @@ links = [
gleam_stdlib = "~> 0.31"
gleam_http = "~> 3.0"
gleam_otp = "~> 0.7"
cowboy = "~> 2.0"
cowboy = "~> 2.5"
gleam_erlang = "~> 0.22"

[dev-dependencies]
Expand Down
18 changes: 9 additions & 9 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

packages = [
{ name = "certifi", version = "2.12.0", build_tools = ["rebar3"], requirements = [], otp_app = "certifi", source = "hex", outer_checksum = "EE68D85DF22E554040CDB4BE100F33873AC6051387BAF6A8F6CE82272340FF1C" },
{ name = "cowboy", version = "2.10.0", build_tools = ["make", "rebar3"], requirements = ["ranch", "cowlib"], otp_app = "cowboy", source = "hex", outer_checksum = "3AFDCCB7183CC6F143CB14D3CF51FA00E53DB9EC80CDCD525482F5E99BC41D6B" },
{ name = "cowlib", version = "2.12.1", build_tools = ["make", "rebar3"], requirements = [], otp_app = "cowlib", source = "hex", outer_checksum = "163B73F6367A7341B33C794C4E88E7DBFE6498AC42DCD69EF44C5BC5507C8DB0" },
{ name = "gleam_erlang", version = "0.23.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "DA7A8E5540948DE10EB01B530869F8FF2FF6CAD8CFDA87626CE6EF63EBBF87CB" },
{ name = "gleam_hackney", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "hackney", "gleam_http"], otp_app = "gleam_hackney", source = "hex", outer_checksum = "CA69AD9061C4A8775A7BD445DE33ECEFD87379AF8E5B028F3DD0216BECA5DD0B" },
{ name = "gleam_http", version = "3.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "0B09AAE8EB547C4F2F2D3F8917A0A4D2EF75DFF0232389332BAFE19DBBFDB92B" },
{ name = "gleam_otp", version = "0.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_erlang"], otp_app = "gleam_otp", source = "hex", outer_checksum = "ED7381E90636E18F5697FD7956EECCA635A3B65538DC2BE2D91A38E61DCE8903" },
{ name = "gleam_stdlib", version = "0.32.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "07D64C26D014CF570F8ACADCE602761EA2E74C842D26F2FD49B0D61973D9966F" },
{ name = "gleeunit", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D3682ED8C5F9CAE1C928F2506DE91625588CC752495988CBE0F5653A42A6F334" },
{ name = "hackney", version = "1.20.1", build_tools = ["rebar3"], requirements = ["mimerl", "certifi", "metrics", "idna", "ssl_verify_fun", "unicode_util_compat", "parse_trans"], otp_app = "hackney", source = "hex", outer_checksum = "FE9094E5F1A2A2C0A7D10918FEE36BFEC0EC2A979994CFF8CFE8058CD9AF38E3" },
{ name = "cowboy", version = "2.12.0", build_tools = ["make", "rebar3"], requirements = ["cowlib", "ranch"], otp_app = "cowboy", source = "hex", outer_checksum = "8A7ABE6D183372CEB21CAA2709BEC928AB2B72E18A3911AA1771639BEF82651E" },
{ name = "cowlib", version = "2.13.0", build_tools = ["make", "rebar3"], requirements = [], otp_app = "cowlib", source = "hex", outer_checksum = "E1E1284DC3FC030A64B1AD0D8382AE7E99DA46C3246B815318A4B848873800A4" },
{ name = "gleam_erlang", version = "0.24.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "26BDB52E61889F56A291CB34167315780EE4AA20961917314446542C90D1C1A0" },
{ name = "gleam_hackney", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib", "hackney"], otp_app = "gleam_hackney", source = "hex", outer_checksum = "066B1A55D37DBD61CC72A1C4EDE43C6015B1797FAF3818C16FE476534C7B6505" },
{ name = "gleam_http", version = "3.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "8C07DF9DF8CC7F054C650839A51C30A7D3C26482AC241C899C1CEA86B22DBE51" },
{ name = "gleam_otp", version = "0.10.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "0B04FE915ACECE539B317F9652CAADBBC0F000184D586AAAF2D94C100945D72B" },
{ name = "gleam_stdlib", version = "0.36.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "C0D14D807FEC6F8A08A7C9EF8DFDE6AE5C10E40E21325B2B29365965D82EB3D4" },
{ name = "gleeunit", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D364C87AFEB26BDB4FB8A5ABDE67D635DC9FA52D6AB68416044C35B096C6882D" },
{ name = "hackney", version = "1.20.1", build_tools = ["rebar3"], requirements = ["certifi", "idna", "metrics", "mimerl", "parse_trans", "ssl_verify_fun", "unicode_util_compat"], otp_app = "hackney", source = "hex", outer_checksum = "FE9094E5F1A2A2C0A7D10918FEE36BFEC0EC2A979994CFF8CFE8058CD9AF38E3" },
{ name = "idna", version = "6.1.1", build_tools = ["rebar3"], requirements = ["unicode_util_compat"], otp_app = "idna", source = "hex", outer_checksum = "92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA" },
{ name = "metrics", version = "1.0.1", build_tools = ["rebar3"], requirements = [], otp_app = "metrics", source = "hex", outer_checksum = "69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16" },
{ name = "mimerl", version = "1.2.0", build_tools = ["rebar3"], requirements = [], otp_app = "mimerl", source = "hex", outer_checksum = "F278585650AA581986264638EBF698F8BB19DF297F66AD91B18910DFC6E19323" },
Expand Down
37 changes: 24 additions & 13 deletions src/gleam/http/cowboy.gleam
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
import gleam/list
import gleam/pair
import gleam/map.{type Map}
import gleam/dict.{type Dict}
import gleam/option.{type Option, None, Some}
import gleam/result
import gleam/http.{type Header}
import gleam/http/service.{type Service}
import gleam/http/request.{Request}
import gleam/bit_builder.{type BitBuilder}
import gleam/bytes_builder.{type BytesBuilder}
import gleam/dynamic.{type Dynamic}
import gleam/erlang/process.{type Pid}

type CowboyRequest
type CowboyRequest

@external(erlang, "gleam_cowboy_native", "start_link")
fn erlang_start_link(
handler: fn(CowboyRequest) -> CowboyRequest,
port: Int,
) -> Result(Pid, Dynamic)

@external(erlang, "gleam_cowboy_native", "set_headers")
fn erlang_set_headers(headers: Dict(String, Dynamic), request: CowboyRequest) -> CowboyRequest

fn set_headers(headers: Dict(String, Dynamic), request: CowboyRequest) -> CowboyRequest {
erlang_set_headers(headers, request)
}

@external(erlang, "cowboy_req", "reply")
fn cowboy_reply(
status: Int,
headers: Map(String, Dynamic),
body: BitBuilder,
headers: Dict(String, Dynamic),
body: BytesBuilder,
request: CowboyRequest,
) -> CowboyRequest

Expand All @@ -37,12 +44,12 @@ fn get_method(request) -> http.Method {
}

@external(erlang, "cowboy_req", "headers")
fn erlang_get_headers(request: CowboyRequest) -> Map(String, String)
fn erlang_get_headers(request: CowboyRequest) -> Dict(String, String)

fn get_headers(request) -> List(http.Header) {
request
|> erlang_get_headers
|> map.to_list
|> dict.to_list
}

@external(erlang, "gleam_cowboy_native", "read_entire_body")
Expand Down Expand Up @@ -93,16 +100,16 @@ fn proplist_get_all(input: List(#(a, b)), key: a) -> List(b) {
// list. This list has a special-case in Cowboy so we need to set it
// correctly.
// https://github.com/gleam-lang/cowboy/issues/3
fn cowboy_format_headers(headers: List(Header)) -> Map(String, Dynamic) {
fn cowboy_format_headers(headers: List(Header)) -> Dict(String, Dynamic) {
let set_cookie_headers = proplist_get_all(headers, "set-cookie")
headers
|> list.map(pair.map_second(_, dynamic.from))
|> map.from_list
|> map.insert("set-cookie", dynamic.from(set_cookie_headers))
|> dict.from_list
|> dict.insert("set-cookie", dynamic.from(set_cookie_headers))
}

fn service_to_handler(
service: Service(BitArray, BitBuilder),
service: Service(BitArray, BytesBuilder),
) -> fn(CowboyRequest) -> CowboyRequest {
fn(request) {
let #(body, request) = get_body(request)
Expand All @@ -120,15 +127,19 @@ fn service_to_handler(
let status = response.status

let headers = cowboy_format_headers(response.headers)
// We set headers directly on the CowboyRequest as we cannot set cookie headers
// using cowboy_req:set_resp_headers as of 2.11.0.
// https://github.com/ninenines/cowboy/pull/1624#issuecomment-1915324578
let request = set_headers(headers, request)
let body = response.body
cowboy_reply(status, headers, body, request)
cowboy_reply(status, dict.new(), body, request)
}
}

// TODO: document
// TODO: test
pub fn start(
service: Service(BitArray, BitBuilder),
service: Service(BitArray, BytesBuilder),
on_port number: Int,
) -> Result(Pid, Dynamic) {
service
Expand Down
5 changes: 4 additions & 1 deletion src/gleam_cowboy_native.erl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-module(gleam_cowboy_native).

-export([init/2, start_link/2, read_entire_body/1]).
-export([init/2, start_link/2, read_entire_body/1, set_headers/2]).

start_link(Handler, Port) ->
RanchOptions = #{
Expand Down Expand Up @@ -29,3 +29,6 @@ read_entire_body(Body, Req0) ->
{ok, Chunk, Req1} -> {list_to_binary([Body, Chunk]), Req1};
{more, Chunk, Req1} -> read_entire_body([Body, Chunk], Req1)
end.

set_headers(Headers, Req) ->
Req#{resp_headers => Headers}.
24 changes: 23 additions & 1 deletion test/gleam/http/cowboy_test.gleam
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import gleam/http/cowboy
import gleam/bytes_builder.{type BytesBuilder}
import gleam/http.{Get, Head, Post}
import gleam/http.{Get, Head, Post, Http}
import gleam/http/request.{type Request}
import gleam/http/response.{type Response}
import gleam/http/cookie
import gleam/hackney
import gleam/list

pub fn echo_service(request: Request(BitArray)) -> Response(BytesBuilder) {
let body = case request.body {
Expand All @@ -12,6 +14,7 @@ pub fn echo_service(request: Request(BitArray)) -> Response(BytesBuilder) {
}
response.new(200)
|> response.prepend_header("made-with", "Gleam")
|> response.set_cookie("cookie_name", "cookie_value", cookie.defaults(Http))
|> response.set_body(body)
}

Expand Down Expand Up @@ -87,3 +90,22 @@ pub fn body_is_echoed_on_post_test() {
let assert Ok("Gleam") = response.get_header(resp, "made-with")
let assert "Ping" = resp.body
}

pub fn cookie_headers_are_handled_correctly_test() {
let port = 3082
let assert Ok(_) = cowboy.start(echo_service, on_port: port)

let req =
request.new()
|> request.set_method(Get)
|> request.set_host("0.0.0.0")
|> request.set_scheme(http.Http)
|> request.set_port(port)
|> request.set_header("name", "value")

let assert Ok(resp) = hackney.send(req)
let assert 200 = resp.status
let assert Ok("Gleam") = response.get_header(resp, "made-with")
let cookies = response.get_cookies(resp)
let assert True = list.contains(cookies, #("cookie_name", "cookie_value"))
}
Loading