have hello world running

This commit is contained in:
Peter Harpending 2025-09-16 15:50:31 -07:00
parent 71909e79b0
commit c63436e380
7 changed files with 1205 additions and 16 deletions

6
NOTES.txt Normal file
View 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
View 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
View 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
View 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>

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,8 @@
{type,app}.
{modules,[]}.
{prefix,"fd"}.
{author,"Peter Harpending"}.
{desc,"Front End Web Dev in Erlang stuff"}.
{author,"Peter Harpending"}.
{package_id,{"otpr","fewd",{0,1,0}}}.
{deps,[]}.
{key_name,none}.