Skip to content

Commit

Permalink
not use actors anymore, handle chain of requests, more compatilibity …
Browse files Browse the repository at this point in the history
…with gleam_http
  • Loading branch information
massivefermion committed Nov 16, 2023
1 parent 4e0e162 commit ddd0dfd
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 357 deletions.
6 changes: 3 additions & 3 deletions gleam.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name = "dove"
version = "0.1.0"
version = "0.2.0"

description = "Fast and flexible http client for Gleam"
gleam = ">= 0.32.0"
Expand All @@ -12,13 +12,13 @@ internal_modules = [
"dove/tcp",
"dove/client",
"dove/request",
"dove/response/*",
"dove/response",
]

[dependencies]
gleam_erlang = "~> 0.23"
gleam_http = "~> 3.5"
gleam_json = "~> 0.7"
gleam_otp = "~> 0.8"
gleam_stdlib = "~> 0.32"
mug = "~> 0.2"

Expand Down
6 changes: 3 additions & 3 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
# You typically do not need to edit this file

packages = [
{ name = "gleam_erlang", version = "0.23.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "DA7A8E5540948DE10EB01B530869F8FF2FF6CAD8CFDA87626CE6EF63EBBF87CB" },
{ name = "gleam_erlang", version = "0.23.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "C21CFB816C114784E669FFF4BBF433535EEA9960FA2F216209B8691E87156B96" },
{ name = "gleam_http", version = "3.5.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "AECDA43AFD523D07A8F09068598A6E271C505278A0CB6F9C7A2E4365EAE8D11E" },
{ name = "gleam_json", version = "0.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "CB405BD93A8828BCD870463DE29375E7B2D252D9D124C109E5B618AAC00B86FC" },
{ name = "gleam_otp", version = "0.8.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "18EF8242A5E54BA92F717C7222F03B3228AEE00D1F286D4C56C3E8C18AA2588E" },
{ name = "gleam_stdlib", version = "0.32.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "ABF00CDCCB66FABBCE351A50060964C4ACE798F95A0D78622C8A7DC838792577" },
{ name = "gleeunit", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D3682ED8C5F9CAE1C928F2506DE91625588CC752495988CBE0F5653A42A6F334" },
{ name = "mug", version = "0.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "mug", source = "hex", outer_checksum = "35064CE144C23AD60842F44C989D70DD7A8923B8A74885F06E09B73DD3CC5283" },
Expand All @@ -13,8 +13,8 @@ packages = [

[requirements]
gleam_erlang = { version = "~> 0.23" }
gleam_http = { version = "~> 3.5" }
gleam_json = { version = "~> 0.7" }
gleam_otp = { version = "~> 0.8" }
gleam_stdlib = { version = "~> 0.32" }
gleeunit = { version = "~> 1.0" }
mug = { version = "~> 0.2" }
223 changes: 191 additions & 32 deletions src/dove.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,77 @@ import gleam/option
import gleam/result
import gleam/string
import gleam/dynamic
import gleam/bit_array
import gleam/json
import gleam/erlang
import gleam/erlang/process
import dove/client
import gleam/http
import gleam/http/response as gleam_http_response
import dove/tcp
import dove/error
import dove/request

pub type Method {
GET
PUT
POST
HEAD
PATCH
TRACE
DELETE
CONNECT
OPTIONS
}
import dove/response
import mug

pub type RequestOption(a) {
Body(Body)
Body(RequestBody)
Headers(List(#(String, String)))
QueryParams(List(#(String, String)))
ResponseDecoder(fn(dynamic.Dynamic) -> Result(a, List(dynamic.DecodeError)))
}

pub type Body {
pub type RequestBody {
JSON(String)
PlainText(String)
}

pub type ResponseBody(a) {
Empty
Decoded(a)
Raw(String)
}

pub opaque type Connection(a) {
Connection(
host: String,
socket: mug.Socket,
buffer: String,
requests: List(
#(
erlang.Reference,
option.Option(
fn(dynamic.Dynamic) -> Result(a, List(dynamic.DecodeError)),
),
),
),
responses: List(
#(
erlang.Reference,
Result(gleam_http_response.Response(ResponseBody(a)), error.Error),
),
),
)
}

pub fn connect(host: String, port: Int, timeout: Int) {
client.connect(host, port, timeout)
use socket <- result.then(tcp.connect(host, port, timeout))
Ok(Connection(host <> ":" <> int.to_string(port), socket, "", [], []))
}

pub fn request(
conn: client.Connection(a),
method: Method,
conn: Connection(a),
method: http.Method,
path: String,
options: List(RequestOption(a)),
timeout: Int,
) {
let assert Ok(method) = list.key_find(method_mapping, method)
let method = case method {
http.Other(method) -> method
_ -> {
let assert Ok(method) = list.key_find(method_mapping, method)
method
}
}

let query_params = get_query_params(options)
let headers = get_headers(options)
let body = get_body(options)
Expand Down Expand Up @@ -94,21 +125,110 @@ pub fn request(
body,
))

let subject = process.new_subject()
process.send(conn.client, client.Request(request, subject, decoder, timeout))
Ok(subject)
use _ <- result.then(
tcp.send(conn.socket, request)
|> result.replace_error(error.UnableToSendRequest),
)

let ref = erlang.make_reference()
Ok(#(
Connection(
conn.host,
conn.socket,
conn.buffer,
list.append(conn.requests, [#(ref, decoder)]),
conn.responses,
),
ref,
))
}

pub fn receive(conn: Connection(a), timeout) {
let selector = tcp.new_selector()
receive_internal(conn, selector, timeout)
}

fn receive_internal(conn: Connection(a), selector, timeout) {
case conn.requests {
[#(ref, decoder), ..rest] -> {
let data = case string.length(conn.buffer) {
0 -> receive_packet(conn.socket, selector, "", now(), timeout)
_ -> response.decode(conn.buffer)
}

let conn =
Connection(
conn.host,
conn.socket,
case data {
Ok(#(_, rest)) -> string.append(conn.buffer, rest)
_ -> conn.buffer
},
rest,
list.append(
conn.responses,
[
#(
ref,
data
|> result.map(fn(response) {
case response.0 {
#(status, headers, option.Some(body)) ->
case decoder {
option.Some(decoder) ->
case json.decode(body, decoder) {
Ok(value) ->
Ok(gleam_http_response.Response(
status,
headers,
Decoded(value),
))
Error(decode_error) ->
Error(error.DecodeError(decode_error))
}
option.None ->
Ok(gleam_http_response.Response(
status,
headers,
Raw(body),
))
}

#(status, headers, option.None) ->
Ok(gleam_http_response.Response(status, headers, Empty))
}
})
|> result.flatten,
),
],
),
)

case rest {
[] -> conn
_ -> receive_internal(conn, selector, timeout)
}
}
}
}

pub fn get_response(conn: Connection(a), ref) {
result.flatten(
list.key_find(conn.responses, ref)
|> result.replace_error(error.TCPError(mug.Timeout)),
)
}

const method_mapping = [
#(GET, "GET"),
#(PUT, "PUT"),
#(POST, "POST"),
#(HEAD, "HEAD"),
#(PATCH, "PATCH"),
#(TRACE, "TRACE"),
#(DELETE, "DELETE"),
#(CONNECT, "CONNECT"),
#(OPTIONS, "OPTIONS"),
#(http.Get, "GET"),
#(http.Put, "PUT"),
#(http.Post, "POST"),
#(http.Head, "HEAD"),
#(http.Patch, "PATCH"),
#(http.Trace, "TRACE"),
#(http.Delete, "DELETE"),
#(http.Connect, "CONNECT"),
#(http.Options, "OPTIONS"),
]

fn get_query_params(options: List(RequestOption(a))) {
Expand Down Expand Up @@ -178,3 +298,42 @@ fn get_body(options: List(RequestOption(a))) {
Error(Nil) -> option.None
}
}

fn receive_packet(
socket: mug.Socket,
selector: process.Selector(Result(BitArray, mug.Error)),
storage: String,
start_time: Int,
timeout: Int,
) {
case response.decode(storage) {
Ok(value) -> Ok(value)
Error(error.MoreNeeded) -> {
case now() - start_time >= timeout * 1_000_000 {
True -> Error(error.TCPError(mug.Timeout))
False ->
case tcp.receive(socket, selector, timeout) {
Error(tcp_error) -> Error(error.TCPError(tcp_error))
Ok(packet) ->
case bit_array.to_string(packet) {
Ok(packet) ->
receive_packet(
socket,
selector,
string.append(storage, packet),
start_time,
timeout,
)

Error(Nil) -> Error(error.IsNotString)
}
}
}
}

Error(error) -> Error(error)
}
}

@external(erlang, "erlang", "monotonic_time")
fn now() -> Int
Loading

0 comments on commit ddd0dfd

Please sign in to comment.