Packages
mist
0.1.3
6.0.3
6.0.2
6.0.1
6.0.0
5.0.4
5.0.3
5.0.2
5.0.1
5.0.0
5.0.0-rc1
4.0.7
4.0.6
4.0.5
4.0.4
4.0.3
4.0.2
4.0.1
4.0.0
3.0.0
2.0.0
1.2.0
1.1.0
1.0.0
1.0.0-rc3
1.0.0-rc2
1.0.0-rc1
0.17.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.3
a misty Gleam web server
Current section
Files
Jump to
Current section
Files
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)
}