have hello world running
This commit is contained in:
parent
71909e79b0
commit
c63436e380
6
NOTES.txt
Normal file
6
NOTES.txt
Normal file
@ -0,0 +1,6 @@
|
||||
VIDEO 1 - 2025-09-16
|
||||
TODONE
|
||||
- add qhl as dep
|
||||
TODO (GOAL QUEUE)
|
||||
- listen by default
|
||||
- talk to a web browser
|
||||
4
README.md
Normal file
4
README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# fewd = front end web dev
|
||||
|
||||
this is me (PRH) trying to learn some front end web dev because pixels are
|
||||
important despite my wishes.
|
||||
25
include/http.hrl
Normal file
25
include/http.hrl
Normal file
@ -0,0 +1,25 @@
|
||||
-record(request,
|
||||
{method = undefined :: undefined | method(),
|
||||
path = undefined :: undefined | binary(),
|
||||
qargs = undefined :: undefined | #{Key :: binary() := Value :: binary()},
|
||||
fragment = undefined :: undefined | none | binary(),
|
||||
version = undefined :: undefined | http10 | http11 | http20,
|
||||
headers = undefined :: undefined | [{Key :: binary(), Value :: binary()}],
|
||||
cookies = undefined :: undefined | #{Key :: binary() := Value :: binary()},
|
||||
enctype = undefined :: undefined | none | urlencoded | multipart(),
|
||||
size = undefined :: undefined | none | non_neg_integer(),
|
||||
body = undefined :: undefined | none | body()}).
|
||||
|
||||
-record(response,
|
||||
{type = page :: page | {data, string()},
|
||||
version = http11 :: http11,
|
||||
code = 200 :: pos_integer(),
|
||||
slogan = "" :: string(),
|
||||
headers = [] :: [{Key :: string(), Value :: iolist()}],
|
||||
body = "" :: iolist()}).
|
||||
|
||||
-type method() :: get | post | options.
|
||||
-type multipart() :: {multipart, Boundary :: binary()}.
|
||||
-type body() :: {partial, binary()} | {multipart, [body_part()]} | binary().
|
||||
-type body_part() :: {Field :: binary(), Data :: binary()}
|
||||
| {Field :: binary(), Name :: binary(), Data :: binary()}.
|
||||
10
priv/index.html
Normal file
10
priv/index.html
Normal file
@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>FEWD Hello!</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello world!</h1>
|
||||
</body>
|
||||
</html>
|
||||
@ -27,6 +27,8 @@
|
||||
|
||||
%%% Type and Record Definitions
|
||||
|
||||
-include("http.hrl").
|
||||
|
||||
|
||||
-record(s, {socket = none :: none | gen_tcp:socket()}).
|
||||
|
||||
@ -127,22 +129,17 @@ listen(Parent, Debug, ListenSocket) ->
|
||||
loop(Parent, Debug, State = #s{socket = Socket}) ->
|
||||
ok = inet:setopts(Socket, [{active, once}]),
|
||||
receive
|
||||
{tcp, Socket, <<"bye\r\n">>} ->
|
||||
ok = io:format("~p Client saying goodbye. Bye!~n", [self()]),
|
||||
ok = gen_tcp:send(Socket, "Bye!\r\n"),
|
||||
ok = gen_tcp:shutdown(Socket, read_write),
|
||||
exit(normal);
|
||||
{tcp, Socket, Message} ->
|
||||
ok = io:format("~p received: ~tp~n", [self(), Message]),
|
||||
ok = fd_client_man:echo(Message),
|
||||
loop(Parent, Debug, State);
|
||||
{relay, Sender, Message} when Sender == self() ->
|
||||
ok = gen_tcp:send(Socket, ["Message from YOU: ", Message]),
|
||||
loop(Parent, Debug, State);
|
||||
{relay, Sender, Message} ->
|
||||
From = io_lib:format("Message from ~tp: ", [Sender]),
|
||||
ok = gen_tcp:send(Socket, [From, Message]),
|
||||
loop(Parent, Debug, State);
|
||||
%ok = io:format("~p received: ~tp~n", [self(), Message]),
|
||||
case qhl:parse(Socket, Message) of
|
||||
{ok, Req, none} ->
|
||||
handle_request(Socket, Req),
|
||||
loop(Parent, Debug, State);
|
||||
Error ->
|
||||
io:format("~tp:~tp error: ~tp~n", [self(), ?LINE, Error]),
|
||||
gen_tcp:shutdown(Socket, read_write),
|
||||
exit(normal)
|
||||
end;
|
||||
{tcp_closed, Socket} ->
|
||||
ok = io:format("~p Socket closed, retiring.~n", [self()]),
|
||||
exit(normal);
|
||||
@ -151,6 +148,18 @@ loop(Parent, Debug, State = #s{socket = Socket}) ->
|
||||
Unexpected ->
|
||||
ok = io:format("~p Unexpected message: ~tp", [self(), Unexpected]),
|
||||
loop(Parent, Debug, State)
|
||||
%{tcp, Socket, <<"bye\r\n">>} ->
|
||||
% ok = io:format("~p Client saying goodbye. Bye!~n", [self()]),
|
||||
% ok = gen_tcp:send(Socket, "Bye!\r\n"),
|
||||
% ok = gen_tcp:shutdown(Socket, read_write),
|
||||
% exit(normal);
|
||||
%{relay, Sender, Message} when Sender == self() ->
|
||||
% ok = gen_tcp:send(Socket, ["Message from YOU: ", Message]),
|
||||
% loop(Parent, Debug, State);
|
||||
%{relay, Sender, Message} ->
|
||||
% From = io_lib:format("Message from ~tp: ", [Sender]),
|
||||
% ok = gen_tcp:send(Socket, [From, Message]),
|
||||
% loop(Parent, Debug, State);
|
||||
end.
|
||||
|
||||
|
||||
@ -202,3 +211,62 @@ system_get_state(State) -> {ok, State}.
|
||||
|
||||
system_replace_state(StateFun, State) ->
|
||||
{ok, StateFun(State), State}.
|
||||
|
||||
|
||||
|
||||
%%% http request handling
|
||||
|
||||
handle_request(Sock, R = #request{method = M, path = P}) when M =/= undefined, P =/= undefined ->
|
||||
route(Sock, M, P, R).
|
||||
|
||||
|
||||
route(Sock, get, <<"/">>, _) -> home(Sock);
|
||||
route(Sock, _, _, _) -> http_err(Sock, 404).
|
||||
|
||||
|
||||
home(Sock) ->
|
||||
%% fixme: cache
|
||||
Path_IH = filename:join([zx:get_home(), "priv", "index.html"]),
|
||||
case file:read_file(Path_IH) of
|
||||
{ok, Body} ->
|
||||
Resp = #response{headers = [{"content-type", "text/html"}],
|
||||
body = Body},
|
||||
respond(Sock, Resp);
|
||||
Error ->
|
||||
io:format("~p error: ~p~n", [self(), Error]),
|
||||
http_err(Sock, 500)
|
||||
end.
|
||||
|
||||
http_err(_, _) ->
|
||||
error(todo).
|
||||
|
||||
|
||||
respond(Sock, Response) ->
|
||||
gen_tcp:send(Sock, fmtresp(Response)).
|
||||
|
||||
|
||||
fmtresp(#response{type = page, %% no idea what {data, String} is
|
||||
version = http11,
|
||||
code = Code,
|
||||
headers = Hs,
|
||||
body = Body}) ->
|
||||
%% need byte size for binary
|
||||
Headers = add_headers(Hs, Body),
|
||||
[io_lib:format("HTTP/1.1 ~tp ~ts", [Code, qhl:slogan(Code)]), "\r\n",
|
||||
[io_lib:format("~ts: ~ts\r\n", [K, V]) || {K, V} <- Headers],
|
||||
"\r\n",
|
||||
Body].
|
||||
|
||||
|
||||
%% body needed just for size
|
||||
add_headers(Hs, Body) ->
|
||||
Defaults = default_headers(Body),
|
||||
Hs2 = proplists:to_map(Hs),
|
||||
proplists:from_map(maps:merge(Defaults, Hs2)).
|
||||
|
||||
|
||||
default_headers(Body) ->
|
||||
BodySize = byte_size(iolist_to_binary(Body)),
|
||||
#{"Server" => "fewd 0.1.0",
|
||||
"Date" => qhl:ridiculous_web_date(),
|
||||
"Content-Length" => io_lib:format("~p", [BodySize])}.
|
||||
|
||||
1076
src/qhl.erl
Normal file
1076
src/qhl.erl
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user