Compare commits

...

4 Commits

Author SHA1 Message Date
Peter Harpending
3a8c2cf4b0 add qhl header file 2025-09-23 16:31:38 -07:00
Peter Harpending
e6fa8481e8 add qhl as a dep 2025-09-23 16:29:11 -07:00
Peter Harpending
f0725700f0 deleting relay logic 2025-09-23 16:26:15 -07:00
Peter Harpending
2037444f54 auto-listen to port 8000 2025-09-23 16:23:35 -07:00
6 changed files with 296 additions and 60 deletions

242
README.md
View File

@ -563,3 +563,245 @@ start_acceptor(ListenSocket) ->
- Everything else is boilerplate
- So our task is to remove the relay-messages logic, and replace it with http
parse/respond logic.
### Auto-listening to port `8000`
- Start commit: `9c5b332e009b88a9400a4e7008f760ce872b810a`
```diff
diff --git a/gex_httpd/src/gex_httpd.erl b/gex_httpd/src/gex_httpd.erl
index 242c82f..3a724b0 100644
--- a/gex_httpd/src/gex_httpd.erl
+++ b/gex_httpd/src/gex_httpd.erl
@@ -9,8 +9,11 @@
-copyright("Peter Harpending <peterharpending@qpq.swiss>").
+%% for our edification
-export([listen/1, ignore/0]).
--export([start/0, start/1]).
+-export([start/0]).
+
+%% erlang expects us to export these functions
-export([start/2, stop/1]).
@@ -45,17 +48,17 @@ start() ->
io:format("Starting...").
--spec start(PortNum) -> ok
- when PortNum :: inet:port_number().
-%% @doc
-%% Start the server and begin listening immediately. Slightly more convenient when
-%% playing around in the shell.
-
-start(PortNum) ->
- ok = start(),
- ok = gh_client_man:listen(PortNum),
- io:format("Startup complete, listening on ~w~n", [PortNum]).
-
+%-spec start(PortNum) -> ok
+% when PortNum :: inet:port_number().
+%%% @doc
+%%% Start the server and begin listening immediately. Slightly more convenient when
+%%% playing around in the shell.
+%
+%start(PortNum) ->
+% ok = start(),
+% ok = gh_client_man:listen(PortNum),
+% io:format("Startup complete, listening on ~w~n", [PortNum]).
+%
-spec start(normal, term()) -> {ok, pid()}.
%% @private
@@ -64,7 +67,10 @@ start(PortNum) ->
%% See: http://erlang.org/doc/apps/kernel/application.html
start(normal, _Args) ->
- gh_sup:start_link().
+ Result = gh_sup:start_link(),
+ % auto-listen to port 8000
+ ok = listen(8000),
+ Result.
-spec stop(term()) -> ok.
```
### Deleting the relay logic
- Start commit: `2037444f54d8f95a959c7e67673d55782fad5ad3`
```diff
diff --git a/gex_httpd/src/gh_client.erl b/gex_httpd/src/gh_client.erl
index ea5b3ef..44d206d 100644
--- a/gex_httpd/src/gh_client.erl
+++ b/gex_httpd/src/gh_client.erl
@@ -127,22 +127,10 @@ 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 = gh_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);
{tcp_closed, Socket} ->
ok = io:format("~p Socket closed, retiring.~n", [self()]),
exit(normal);
diff --git a/gex_httpd/src/gh_client_man.erl b/gex_httpd/src/gh_client_man.erl
index 9c78921..c913dcc 100644
--- a/gex_httpd/src/gh_client_man.erl
+++ b/gex_httpd/src/gh_client_man.erl
@@ -17,7 +17,7 @@
-export([listen/1, ignore/0]).
--export([enroll/0, echo/1]).
+-export([enroll/0]).
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
code_change/3, terminate/2]).
@@ -74,17 +74,6 @@ enroll() ->
gen_server:cast(?MODULE, {enroll, self()}).
--spec echo(Message) -> ok
- when Message :: string().
-%% @doc
-%% The function that tells the manager to broadcast a message to all clients.
-%% This can broadcast arbitrary strings to clients from non-clients as well.
-
-echo(Message) ->
- gen_server:cast(?MODULE, {echo, Message, self()}).
-
-
-
%%% Startup Functions
@@ -145,9 +134,6 @@ handle_call(Unexpected, From, State) ->
handle_cast({enroll, Pid}, State) ->
NewState = do_enroll(Pid, State),
{noreply, NewState};
-handle_cast({echo, Message, Sender}, State) ->
- ok = do_echo(Message, Sender, State),
- {noreply, State};
handle_cast(ignore, State) ->
NewState = do_ignore(State),
{noreply, NewState};
@@ -262,17 +248,6 @@ do_enroll(Pid, State = #s{clients = Clients}) ->
end.
--spec do_echo(Message, Sender, State) -> ok
- when Message :: string(),
- Sender :: pid(),
- State :: state().
-%% @private
-%% The "doer" procedure called when an "echo" message is received.
-
-do_echo(Message, Sender, #s{clients = Clients}) ->
- Send = fun(Client) -> Client ! {relay, Sender, Message} end,
- lists:foreach(Send, Clients).
-
-spec handle_down(Mon, Pid, Reason, State) -> NewState
when Mon :: reference(),
```
### Adding QHL as a dependency
[QHL](https://git.qpq.swiss/qPQ-AG/qhl) is our own internal parsing library for
HTTP/1.1 requests
```bash
zx set dep otpr-qhl-0.1.0
```
```diff
diff --git a/gex_httpd/zomp.meta b/gex_httpd/zomp.meta
index 7111f2d..09d84c2 100644
--- a/gex_httpd/zomp.meta
+++ b/gex_httpd/zomp.meta
@@ -1,17 +1,17 @@
-{a_email,"peterharpending@qpq.swiss"}.
+{name,"Gajumaru DEX HTTP Daemon"}.
+{type,app}.
+{modules,[]}.
+{prefix,"gh"}.
{author,"Peter Harpending"}.
+{desc,"Gajumaru Exchange HTTP Daemon"}.
+{package_id,{"otpr","gex_httpd",{0,1,0}}}.
+{deps,[{"otpr","qhl",{0,1,0}}]}.
+{key_name,none}.
+{a_email,"peterharpending@qpq.swiss"}.
{c_email,"peterharpending@qpq.swiss"}.
{copyright,"Peter Harpending"}.
-{deps,[]}.
-{desc,"Gajumaru Exchange HTTP Daemon"}.
{file_exts,[]}.
-{key_name,none}.
{license,skip}.
-{modules,[]}.
-{name,"Gajumaru DEX HTTP Daemon"}.
-{package_id,{"otpr","gex_httpd",{0,1,0}}}.
-{prefix,"gh"}.
{repo_url,"https://git.qpq.swiss/QPQ-AG/gex"}.
{tags,[]}.
-{type,app}.
{ws_url,"https://git.qpq.swiss/QPQ-AG/gex"}.
```
### Adding the QHL header file
```bash
wget https://git.qpq.swiss/QPQ-AG/QHL/raw/branch/master/include/http.hrl -O gex_httpd/include/http.hrl
```
```diff
diff --git a/gex_httpd/include/http.hrl b/gex_httpd/include/http.hrl
new file mode 100644
index 0000000..27d24d2
--- /dev/null
+++ b/gex_httpd/include/http.hrl
@@ -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()}.
```

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()}.

View File

@ -9,8 +9,11 @@
-copyright("Peter Harpending <peterharpending@qpq.swiss>").
%% for our edification
-export([listen/1, ignore/0]).
-export([start/0, start/1]).
-export([start/0]).
%% erlang expects us to export these functions
-export([start/2, stop/1]).
@ -45,17 +48,17 @@ start() ->
io:format("Starting...").
-spec start(PortNum) -> ok
when PortNum :: inet:port_number().
%% @doc
%% Start the server and begin listening immediately. Slightly more convenient when
%% playing around in the shell.
start(PortNum) ->
ok = start(),
ok = gh_client_man:listen(PortNum),
io:format("Startup complete, listening on ~w~n", [PortNum]).
%-spec start(PortNum) -> ok
% when PortNum :: inet:port_number().
%%% @doc
%%% Start the server and begin listening immediately. Slightly more convenient when
%%% playing around in the shell.
%
%start(PortNum) ->
% ok = start(),
% ok = gh_client_man:listen(PortNum),
% io:format("Startup complete, listening on ~w~n", [PortNum]).
%
-spec start(normal, term()) -> {ok, pid()}.
%% @private
@ -64,7 +67,10 @@ start(PortNum) ->
%% See: http://erlang.org/doc/apps/kernel/application.html
start(normal, _Args) ->
gh_sup:start_link().
Result = gh_sup:start_link(),
% auto-listen to port 8000
ok = listen(8000),
Result.
-spec stop(term()) -> ok.

View File

@ -127,22 +127,10 @@ 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 = gh_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);
{tcp_closed, Socket} ->
ok = io:format("~p Socket closed, retiring.~n", [self()]),
exit(normal);

View File

@ -17,7 +17,7 @@
-export([listen/1, ignore/0]).
-export([enroll/0, echo/1]).
-export([enroll/0]).
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
code_change/3, terminate/2]).
@ -74,17 +74,6 @@ enroll() ->
gen_server:cast(?MODULE, {enroll, self()}).
-spec echo(Message) -> ok
when Message :: string().
%% @doc
%% The function that tells the manager to broadcast a message to all clients.
%% This can broadcast arbitrary strings to clients from non-clients as well.
echo(Message) ->
gen_server:cast(?MODULE, {echo, Message, self()}).
%%% Startup Functions
@ -145,9 +134,6 @@ handle_call(Unexpected, From, State) ->
handle_cast({enroll, Pid}, State) ->
NewState = do_enroll(Pid, State),
{noreply, NewState};
handle_cast({echo, Message, Sender}, State) ->
ok = do_echo(Message, Sender, State),
{noreply, State};
handle_cast(ignore, State) ->
NewState = do_ignore(State),
{noreply, NewState};
@ -262,17 +248,6 @@ do_enroll(Pid, State = #s{clients = Clients}) ->
end.
-spec do_echo(Message, Sender, State) -> ok
when Message :: string(),
Sender :: pid(),
State :: state().
%% @private
%% The "doer" procedure called when an "echo" message is received.
do_echo(Message, Sender, #s{clients = Clients}) ->
Send = fun(Client) -> Client ! {relay, Sender, Message} end,
lists:foreach(Send, Clients).
-spec handle_down(Mon, Pid, Reason, State) -> NewState
when Mon :: reference(),

View File

@ -1,17 +1,17 @@
{a_email,"peterharpending@qpq.swiss"}.
{name,"Gajumaru DEX HTTP Daemon"}.
{type,app}.
{modules,[]}.
{prefix,"gh"}.
{author,"Peter Harpending"}.
{desc,"Gajumaru Exchange HTTP Daemon"}.
{package_id,{"otpr","gex_httpd",{0,1,0}}}.
{deps,[{"otpr","qhl",{0,1,0}}]}.
{key_name,none}.
{a_email,"peterharpending@qpq.swiss"}.
{c_email,"peterharpending@qpq.swiss"}.
{copyright,"Peter Harpending"}.
{deps,[]}.
{desc,"Gajumaru Exchange HTTP Daemon"}.
{file_exts,[]}.
{key_name,none}.
{license,skip}.
{modules,[]}.
{name,"Gajumaru DEX HTTP Daemon"}.
{package_id,{"otpr","gex_httpd",{0,1,0}}}.
{prefix,"gh"}.
{repo_url,"https://git.qpq.swiss/QPQ-AG/gex"}.
{tags,[]}.
{type,app}.
{ws_url,"https://git.qpq.swiss/QPQ-AG/gex"}.