Can't get form submission to work properly
Hey there, I was just trying out gleam for the first time, so this is almost certainly user error, but I'm struggling to understand how to make a simple form-based POST request. It seems like the request gets modified in some interesting ways that's making a client_credentials oauth token request fail (specifically error_description": "invalid_request, Missing grant_type parameter value").
I'm interested in using gleam with bun so I could theoretically create a fairly static CLI driven binary, so that's why I'm not using the erlang target in this scenario.
System context:
- Mac 15.4.1
- gleam 1.10.0
- bun 1.2.9
I'm using httpbin.org for testing to get a response back with what I sent it and it seems like the form components are getting put under name maybe.
Here's my gleam code:
pub fn send_form() {
let id = "test"
let secret = "testsecret"
let assert Ok(token_req) = request.to("https://httpbin.org/post")
let creds =
bit_array.from_string(id <> ":" <> secret)
|> bit_array.base64_encode(True)
let payload_form =
form_data.new()
|> form_data.set("grant_type", "client_credentials")
let token_req =
token_req
|> request.set_method(http.Post)
|> request.set_header("Content-Type", "application/x-www-form-urlencoded")
|> request.set_header("Authorization", "Basic " <> creds)
|> request.set_body(payload_form)
use resp <- promise.try_await(fetch.send_form_data(token_req))
use body <- promise.try_await(fetch.read_json_body(resp))
echo body
promise.resolve(Ok(Nil))
}
Response:
//js({ "args": //js({}), "data": "", "files": //js({}), "form": //js({ "---WebkitFormBoundary8d65f103a3d34404be041b33c84280fa\r\nContent-Disposition: form-data; name": "\"grant_type\"\r\n\r\nclient_credentials\r\n---WebkitFormBoundary8d65f103a3d34404be041b33c84280fa--\r\n" }), "headers": //js({ "Accept": "*/*", "Accept-Encoding": "gzip, deflate, br", "Authorization": "Basic dGVzdDp0ZXN0c2VjcmV0", "Content-Length": "185", "Content-Type": "application/x-www-form-urlencoded", "Host": "httpbin.org", "User-Agent": "Bun/1.2.9", "X-Amzn-Trace-Id": "Root=1-681114b3-1ee85fdb0456e9912b353ec0" }), "json": //js(null), "origin": "198.44.129.120", "url": "https://httpbin.org/post" })
# the important bit seems to be this:
name": "\"grant_type\"\r\n\r\nclient_credentials\r\n---WebkitFormBoundary8d65f103a3d34404be041b33c84280fa--\r\n"
Here's some elixir code that works:
Mix.install([:req])
url = "https://httpbin.org/post"
form = [grant_type: "client_credentials"]
creds_string = "test:testsecret"
Req.post(url, form: form, auth: {:basic, creds_string})
#> {:ok,
%Req.Response{
status: 200,
headers: %{
"access-control-allow-credentials" => ["true"],
"access-control-allow-origin" => ["*"],
"connection" => ["keep-alive"],
"content-type" => ["application/json"],
"date" => ["Tue, 29 Apr 2025 17:59:52 GMT"],
"server" => ["gunicorn/19.9.0"]
},
body: %{
"args" => %{},
"data" => "",
"files" => %{},
"form" => %{"grant_type" => "client_credentials"},
"headers" => %{
"Accept-Encoding" => "gzip",
"Authorization" => "Basic dGVzdDp0ZXN0c2VjcmV0",
"Content-Length" => "29",
"Content-Type" => "application/x-www-form-urlencoded",
"Host" => "httpbin.org",
"User-Agent" => "req/0.5.10",
"X-Amzn-Trace-Id" => "Root=1-68111397-3d5c1061652553126eb83cff"
},
"json" => nil,
"origin" => "198.44.129.120",
"url" => "https://httpbin.org/post"
},
trailers: %{},
private: %{}
}}
The first thing I see is that you've set a formencoded content type, but the body is a multipart form. I think that fetch may overwrite the incorrect content type though.
Could you share the request received for each please. I couldn't guess what the difference is and I don't know the relationship between the JavaScript object you've shared and what request fetch will send for it. Thank you