Current section

Files

Jump to
mist src mist.gleam
Raw

src/mist.gleam

import gleam/bit_string
import gleam/string_builder.{StringBuilder}
import gleam/bit_string
import gleam/erlang/atom.{Atom}
import gleam/erlang/charlist.{Charlist}
import gleam/http
import gleam/http/request.{Request}
import gleam/http/response.{Response}
import gleam/int
import gleam/io
import gleam/list
import gleam/option.{Option}
import gleam/otp/actor
import gleam/otp/process
import gleam/result
import gleam/string
import glisten/tcp.{
HandlerMessage, LoopFn, ReceiveMessage, Socket, Tcp, TcpClosed, send,
}
pub type PacketType {
Http
HttphBin
}
pub type HttpUri {
AbsPath(Charlist)
}
pub type HttpPacket {
HttpRequest(Atom, HttpUri, #(Int, Int))
HttpHeader(Int, Atom, BitString, BitString)
}
pub type DecodedPacket {
BinaryData(HttpPacket, BitString)
EndOfHeaders(BitString)
MoreData(Option(Int))
}
pub type DecodeError {
InvalidMethod
UnknownHeader
}
external fn decode_packet(
packet_type: PacketType,
packet: BitString,
options: List(a),
) -> Result(DecodedPacket, DecodeError) =
"glisten_ffi" "decode_packet"
pub fn from_header(value: BitString) -> String {
assert Ok(value) = bit_string.to_string(value)
string.lowercase(value)
}
pub fn parse_headers(
bs: BitString,
headers: List(http.Header),
) -> Result(#(List(http.Header), BitString), DecodeError) {
case decode_packet(HttphBin, bs, []) {
Ok(BinaryData(HttpHeader(_, _field, field, value), rest)) -> {
let field = from_header(field)
let value = from_header(value)
parse_headers(rest, [#(field, value), ..headers])
}
Ok(EndOfHeaders(rest)) -> Ok(#(headers, rest))
_ -> Error(UnknownHeader)
}
}
/// Turns the TCP message into an HTTP request
pub fn parse_request(bs: BitString) -> Result(Request(BitString), DecodeError) {
try BinaryData(req, rest) = decode_packet(Http, bs, [])
assert HttpRequest(method, AbsPath(path), _version) = req
try method =
method
|> atom.to_string
|> http.parse_method
|> result.replace_error(InvalidMethod)
try #(headers, rest) = parse_headers(rest, [])
let req =
request.new()
|> request.set_body(rest)
|> request.set_method(method)
|> request.set_path(charlist.to_string(path))
Ok(Request(..req, headers: headers))
}
pub fn code_to_string(code: Int) -> String {
case code {
200 -> "Ok"
_ -> "Unknown"
}
}
pub fn headers(resp: Response(BitString)) -> StringBuilder {
list.fold(
resp.headers,
string_builder.from_string(""),
fn(builder, tup) {
let #(header, value) = tup
string_builder.from_strings([header, ": ", value, "\r\n"])
|> string_builder.append_builder(builder, _)
},
)
}
/// Turns an HTTP response into a TCP message
pub fn to_string(resp: Response(BitString)) -> BitString {
let body_builder = case bit_string.byte_size(resp.body) {
0 -> string_builder.from_string("")
_size ->
resp.body
|> bit_string.to_string
|> result.unwrap("")
|> string_builder.from_string
|> string_builder.append("\r\n")
}
"HTTP/1.1 "
|> string_builder.from_string
|> string_builder.append(int.to_string(resp.status))
|> string_builder.append("\r\n")
|> string_builder.append_builder(headers(resp))
|> string_builder.append("\r\n\r\n")
|> string_builder.append_builder(body_builder)
|> string_builder.to_string
|> bit_string.from_string
}
pub fn http_response(status: Int, body: BitString) -> BitString {
response.new(status)
|> response.set_body(body)
|> response.prepend_header("Content-Type", "text/plain")
|> response.prepend_header(
"Content-Length",
body
|> bit_string.byte_size
|> fn(size) { size + 1 }
|> int.to_string,
)
|> to_string
}
/// Just your standard `hello_world` handler
pub fn hello_world(_msg: HandlerMessage, sock: Socket) -> actor.Next(Socket) {
assert Ok(resp) =
"hello, world!"
|> bit_string.from_string
|> http_response(200, _)
|> bit_string.to_string
resp
|> charlist.from_string
|> send(sock, _)
actor.Stop(process.Normal)
}
pub type HttpHandler =
fn(Request(BitString)) -> Response(BitString)
/// Convert your classic `HTTP handler` into a TCP message handler.
/// You probably want to use this
pub fn make_handler(handler: HttpHandler) -> LoopFn {
fn(msg, sock) {
case msg {
Tcp(_, _) -> {
io.print("this should not happen")
actor.Continue(sock)
}
TcpClosed(_msg) -> actor.Stop(process.Normal)
ReceiveMessage(data) -> {
case parse_request(
data
|> charlist.to_string
|> bit_string.from_string,
) {
Ok(req) -> {
assert Ok(resp) =
req
|> handler
|> to_string
|> bit_string.to_string
assert Ok(Nil) = send(sock, charlist.from_string(resp))
}
Error(_) -> {
assert Ok(error) =
400
|> response.new
|> response.set_body(bit_string.from_string(""))
|> to_string
|> bit_string.to_string
assert Ok(Nil) = send(sock, charlist.from_string(error))
}
}
actor.Stop(process.Normal)
}
}
}
}
/// Re-exported from `glisten`
pub fn listen(
port: Int,
options: List(tcp.TcpOption),
) -> Result(tcp.ListenSocket, tcp.SocketReason) {
tcp.listen(port, options)
}
/// Re-exported from `glisten`
pub fn start_acceptor_pool(
listener_socket: tcp.ListenSocket,
handler: LoopFn,
pool_count: Int,
) -> Result(Nil, Nil) {
tcp.start_acceptor_pool(listener_socket, handler, pool_count)
}