memory leak problems with tetris poop
This commit is contained in:
parent
4bd279798c
commit
882a416831
@ -23,3 +23,10 @@
|
||||
-type body() :: {partial, binary()} | {multipart, [body_part()]} | zj:value() | binary().
|
||||
-type body_part() :: {Field :: binary(), Data :: binary()}
|
||||
| {Field :: binary(), Name :: binary(), Data :: binary()}.
|
||||
|
||||
|
||||
-type request() :: #request{}.
|
||||
-type response() :: #response{}.
|
||||
-type tcp_error() :: closed
|
||||
| {timeout, RestData :: binary() | erlang:iovec()}
|
||||
| inet:posix().
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Chat with Websockets</title>
|
||||
<link rel="stylesheet" href="./default.css">
|
||||
<link rel="stylesheet" href="/css/default.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
|
||||
@ -3,15 +3,28 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>WF Compiler Demo</title>
|
||||
<link rel="stylesheet" href="./default.css">
|
||||
<link rel="stylesheet" href="/css/default.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1 class="content-title">WFC Demo</h1>
|
||||
|
||||
<ul>
|
||||
<li><a href="/chat.html">Websocket Chatroom</a></li>
|
||||
<li><a href="/ws-test-echo.html">Websocket Echo Test</a></li>
|
||||
<li>
|
||||
Websocket demos that work
|
||||
|
||||
<ul>
|
||||
<li><a href="/ws-test-echo.html">Echo</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Don't work
|
||||
|
||||
<ul>
|
||||
<li><a href="/chat.html">Chatroom</a></li>
|
||||
<li><a href="/tetris.html">Tetris</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="content-body">
|
||||
|
||||
4
priv/static/js/dist/libfewd.d.ts
vendored
4
priv/static/js/dist/libfewd.d.ts
vendored
@ -3,4 +3,6 @@
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
export {};
|
||||
export { auto_resize, auto_scroll_to_bottom };
|
||||
declare function auto_resize(checkbox_element: HTMLInputElement, target_element: HTMLTextAreaElement, max_height: number): void;
|
||||
declare function auto_scroll_to_bottom(checkbox_element: HTMLInputElement, target_element: HTMLTextAreaElement): void;
|
||||
|
||||
19
priv/static/js/dist/libfewd.js
vendored
19
priv/static/js/dist/libfewd.js
vendored
@ -3,5 +3,22 @@
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
export {};
|
||||
export { auto_resize, auto_scroll_to_bottom };
|
||||
function auto_resize(checkbox_element, target_element, max_height) {
|
||||
// if the user has manually resized their output, we do nothing
|
||||
if (checkbox_element.checked) {
|
||||
let target_height = target_element.scrollHeight;
|
||||
// resize it automagically up to 500px
|
||||
if (target_height < max_height)
|
||||
target_element.style.height = String(target_height) + 'px';
|
||||
else
|
||||
target_element.style.height = String(max_height) + 'px';
|
||||
}
|
||||
}
|
||||
function auto_scroll_to_bottom(checkbox_element, target_element) {
|
||||
if (checkbox_element.checked) {
|
||||
// scroll to bottom
|
||||
target_element.scrollTop = target_element.scrollHeight;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=libfewd.js.map
|
||||
2
priv/static/js/dist/libfewd.js.map
vendored
2
priv/static/js/dist/libfewd.js.map
vendored
@ -1 +1 @@
|
||||
{"version":3,"file":"libfewd.js","sourceRoot":"","sources":["../ts/libfewd.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
||||
{"version":3,"file":"libfewd.js","sourceRoot":"","sources":["../ts/libfewd.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACH,WAAW,EACX,qBAAqB,EACxB,CAAC;AAGF,SACA,WAAW,CACN,gBAAmC,EACnC,cAAsC,EACtC,UAAyB;IAG1B,+DAA+D;IAC/D,IAAI,gBAAgB,CAAC,OAAO,EAAE,CAAC;QAC3B,IAAI,aAAa,GAAW,cAAc,CAAC,YAAY,CAAC;QACxD,sCAAsC;QACtC,IAAI,aAAa,GAAG,UAAU;YAC1B,cAAc,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;;YAE3D,cAAc,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;IAChE,CAAC;AACL,CAAC;AAGD,SACA,qBAAqB,CAChB,gBAAmC,EACnC,cAAsC;IAGvC,IAAI,gBAAgB,CAAC,OAAO,EAAE,CAAC;QAC3B,mBAAmB;QACnB,cAAc,CAAC,SAAS,GAAG,cAAc,CAAC,YAAY,CAAC;IAC3D,CAAC;AACL,CAAC"}
|
||||
6
priv/static/js/dist/tetris.d.ts
vendored
Normal file
6
priv/static/js/dist/tetris.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Tetris
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
export {};
|
||||
30
priv/static/js/dist/tetris.js
vendored
Normal file
30
priv/static/js/dist/tetris.js
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Tetris
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
main();
|
||||
async function main() {
|
||||
let ws = new WebSocket("/ws/tetris");
|
||||
let elt_tetris_state = document.getElementById('tetris-state');
|
||||
ws.onmessage =
|
||||
(e) => {
|
||||
handle_evt(e, elt_tetris_state);
|
||||
};
|
||||
}
|
||||
//-----------------------------------------------------
|
||||
// Tetris
|
||||
//-----------------------------------------------------
|
||||
/**
|
||||
* take the entire tetris state, render the html elements
|
||||
*
|
||||
* then fish out the element in the document, and replace it
|
||||
*
|
||||
* blitting basically
|
||||
*/
|
||||
async function handle_evt(e, oelt) {
|
||||
let state_str = e.data;
|
||||
oelt.value = state_str;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=tetris.js.map
|
||||
1
priv/static/js/dist/tetris.js.map
vendored
Normal file
1
priv/static/js/dist/tetris.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"tetris.js","sourceRoot":"","sources":["../ts/tetris.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,IAAI,EAAE,CAAC;AAEP,KAAK,UACL,IAAI;IAIA,IAAI,EAAE,GAAuC,IAAI,SAAS,CAAC,YAAY,CAAC,CAAqC;IAC7G,IAAI,gBAAgB,GAAyB,QAAQ,CAAC,cAAc,CAAC,cAAc,CAAwB,CAAE;IAE7G,EAAE,CAAC,SAAS;QACR,CAAC,CAAe,EAAE,EAAE;YAChB,UAAU,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;QACpC,CAAC,CAAA;AACT,CAAC;AAED,uDAAuD;AACvD,SAAS;AACT,uDAAuD;AAGvD;;;;;;GAMG;AACH,KAAK,UACL,UAAU,CACL,CAAmB,EACnB,IAA0B;IAG3B,IAAI,SAAS,GAAY,CAAC,CAAC,IAAc,CAAC;IAC1C,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;AAC3B,CAAC"}
|
||||
@ -5,6 +5,39 @@
|
||||
*/
|
||||
|
||||
export {
|
||||
auto_resize,
|
||||
auto_scroll_to_bottom
|
||||
};
|
||||
|
||||
|
||||
function
|
||||
auto_resize
|
||||
(checkbox_element : HTMLInputElement,
|
||||
target_element : HTMLTextAreaElement,
|
||||
max_height : number)
|
||||
: void
|
||||
{
|
||||
// if the user has manually resized their output, we do nothing
|
||||
if (checkbox_element.checked) {
|
||||
let target_height: number = target_element.scrollHeight;
|
||||
// resize it automagically up to 500px
|
||||
if (target_height < max_height)
|
||||
target_element.style.height = String(target_height) + 'px';
|
||||
else
|
||||
target_element.style.height = String(max_height) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function
|
||||
auto_scroll_to_bottom
|
||||
(checkbox_element : HTMLInputElement,
|
||||
target_element : HTMLTextAreaElement)
|
||||
: void
|
||||
{
|
||||
if (checkbox_element.checked) {
|
||||
// scroll to bottom
|
||||
target_element.scrollTop = target_element.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
49
priv/static/js/ts/tetris.ts
Normal file
49
priv/static/js/ts/tetris.ts
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Tetris
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
export {
|
||||
|
||||
};
|
||||
|
||||
import * as libfewd from './libfewd.js';
|
||||
|
||||
main();
|
||||
|
||||
async function
|
||||
main
|
||||
()
|
||||
: Promise<void>
|
||||
{
|
||||
let ws : WebSocket = new WebSocket("/ws/tetris") ;
|
||||
let elt_tetris_state : HTMLTextAreaElement = document.getElementById('tetris-state') as HTMLTextAreaElement ;
|
||||
|
||||
ws.onmessage =
|
||||
(e: MessageEvent) => {
|
||||
handle_evt(e, elt_tetris_state);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------
|
||||
// Tetris
|
||||
//-----------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* take the entire tetris state, render the html elements
|
||||
*
|
||||
* then fish out the element in the document, and replace it
|
||||
*
|
||||
* blitting basically
|
||||
*/
|
||||
async function
|
||||
handle_evt
|
||||
(e : MessageEvent,
|
||||
oelt : HTMLTextAreaElement)
|
||||
: Promise<void>
|
||||
{
|
||||
let state_str : string = e.data as string;
|
||||
oelt.value = state_str;
|
||||
}
|
||||
18
priv/static/tetris.html
Normal file
18
priv/static/tetris.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Tetris with Websockets</title>
|
||||
<link rel="stylesheet" href="/css/default.css">
|
||||
<link rel="stylesheet" href="/css/tetris.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1 class="content-title">Tetris</h1>
|
||||
|
||||
<textarea id="tetris-state"></textarea>
|
||||
</div>
|
||||
|
||||
<script type="module" src="./js/dist/tetris.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Websockets echo test</title>
|
||||
<link rel="stylesheet" href="./default.css">
|
||||
<link rel="stylesheet" href="/css/default.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
|
||||
|
||||
-record(s, {socket = none :: none | gen_tcp:socket(),
|
||||
next = none :: none | binary()}).
|
||||
next = <<>> :: binary()}).
|
||||
|
||||
|
||||
%% An alias for the state record above. Aliasing state can smooth out annoyances
|
||||
@ -128,27 +128,20 @@ listen(Parent, Debug, ListenSocket) ->
|
||||
%% The service loop itself. This is the service state. The process blocks on receive
|
||||
%% of Erlang messages, TCP segments being received themselves as Erlang messages.
|
||||
|
||||
loop(Parent, Debug, State = #s{socket = Socket, next = Next}) ->
|
||||
loop(Parent, Debug, State = #s{socket = Socket, next = Next0}) ->
|
||||
ok = inet:setopts(Socket, [{active, once}]),
|
||||
receive
|
||||
{tcp, Socket, Message} ->
|
||||
tell("~p Next = ~p", [?LINE, Next]),
|
||||
Received =
|
||||
case Next of
|
||||
none -> Message;
|
||||
_ -> <<Next/binary, Message/binary>>
|
||||
end,
|
||||
tell("qhl_parse(Socket, ~tp)", [Received]),
|
||||
Received = <<Next0/binary, Message/binary>>,
|
||||
case qhl:parse(Socket, Received) of
|
||||
{ok, Req, NewNext} ->
|
||||
tell("qhl return: {ok, ~p, ~p}", [Req, NewNext]),
|
||||
handle_request(Socket, Req),
|
||||
NewState = State#s{next = NewNext},
|
||||
{ok, Req, Next1} ->
|
||||
Next2 = handle_request(Socket, Req, Next1),
|
||||
NewState = State#s{next = Next2},
|
||||
loop(Parent, Debug, NewState);
|
||||
Error ->
|
||||
%% should trigger bad request
|
||||
io:format("~p QHL parse error: ~tp", [?LINE, Error]),
|
||||
io:format("~p bad request:~n~ts", [?LINE, Received]),
|
||||
tell(error, "~p QHL parse error: ~tp", [?LINE, Error]),
|
||||
tell(error, "~p bad request:~n~ts", [?LINE, Received]),
|
||||
http_err(Socket, 400),
|
||||
gen_tcp:shutdown(Socket, read_write),
|
||||
exit(normal)
|
||||
@ -217,29 +210,57 @@ system_replace_state(StateFun, State) ->
|
||||
|
||||
%%% http request handling
|
||||
|
||||
handle_request(Sock, R = #request{method = M, path = P}) when M =/= undefined, P =/= undefined ->
|
||||
tell("~p ~ts", [M, P]),
|
||||
route(Sock, M, P, R).
|
||||
-spec handle_request(Sock, Request, Received) -> NewReceived
|
||||
when Sock :: gen_tcp:socket(),
|
||||
Request :: request(),
|
||||
Received :: binary(),
|
||||
NewReceived :: binary().
|
||||
|
||||
handle_request(Sock, R = #request{method = M, path = P}, Received) when M =/= undefined, P =/= undefined ->
|
||||
tell("~tp ~tp ~ts", [self(), M, P]),
|
||||
route(Sock, M, P, R, Received).
|
||||
|
||||
|
||||
route(Sock, get, Route, Request) ->
|
||||
|
||||
-spec route(Sock, Method, Route, Request, Received) -> NewReceived
|
||||
when Sock :: gen_tcp:socket(),
|
||||
Method :: get | post,
|
||||
Route :: binary(),
|
||||
Request :: request(),
|
||||
Received :: binary(),
|
||||
NewReceived :: binary().
|
||||
|
||||
route(Sock, get, Route, Request, Received) ->
|
||||
case Route of
|
||||
<<"/ws/echo">> -> ws_echo(Sock, Request);
|
||||
<<"/">> -> route_static(Sock, <<"/index.html">>);
|
||||
_ -> route_static(Sock, Route)
|
||||
<<"/ws/tetris">> -> ws_tetris(Sock, Request, Received);
|
||||
<<"/ws/echo">> -> ws_echo(Sock, Request) , Received;
|
||||
<<"/">> -> route_static(Sock, <<"/index.html">>) , Received;
|
||||
_ -> route_static(Sock, Route) , Received
|
||||
end;
|
||||
route(Sock, post, Route, Request) ->
|
||||
route(Sock, post, Route, Request, Received) ->
|
||||
case Route of
|
||||
<<"/wfcin">> -> wfcin(Sock, Request);
|
||||
_ -> http_err(Sock, 404)
|
||||
<<"/wfcin">> -> wfcin(Sock, Request) , Received;
|
||||
_ -> http_err(Sock, 404) , Received
|
||||
end;
|
||||
route(Sock, _, _, _) ->
|
||||
http_err(Sock, 404).
|
||||
route(Sock, _, _, _, Received) ->
|
||||
http_err(Sock, 404),
|
||||
Received.
|
||||
|
||||
|
||||
|
||||
-spec route_static(Socket, Route) -> ok
|
||||
when Socket :: gen_tcp:socket(),
|
||||
Route :: binary().
|
||||
|
||||
route_static(Sock, Route) ->
|
||||
respond_static(Sock, fd_sfc:query(Route)).
|
||||
|
||||
|
||||
|
||||
-spec respond_static(Sock, MaybeEty) -> ok
|
||||
when Sock :: gen_tcp:socket(),
|
||||
MaybeEty :: fd_sfc:maybe_entry().
|
||||
|
||||
respond_static(Sock, {found, Entry}) ->
|
||||
% -record(e, {fs_path :: file:filename(),
|
||||
% last_modified :: file:date_time(),
|
||||
@ -259,6 +280,91 @@ respond_static(Sock, not_found) ->
|
||||
http_err(Sock, 404).
|
||||
|
||||
|
||||
%% ------------------------------
|
||||
%% tetris
|
||||
%% ------------------------------
|
||||
|
||||
-spec ws_tetris(Sock, Request, Received) -> NewReceived
|
||||
when Sock :: gen_tcp:socket(),
|
||||
Request :: request(),
|
||||
Received :: binary(),
|
||||
NewReceived :: binary().
|
||||
|
||||
ws_tetris(Sock, Request, Received) ->
|
||||
try
|
||||
ws_tetris2(Sock, Request, Received)
|
||||
catch
|
||||
X:Y:Z ->
|
||||
tell(error, "CRASH ws_tetris: ~tp:~tp:~tp", [X, Y, Z]),
|
||||
http_err(Sock, 500)
|
||||
end.
|
||||
|
||||
|
||||
|
||||
-spec ws_tetris2(Sock, Request, Received) -> NewReceived
|
||||
when Sock :: gen_tcp:socket(),
|
||||
Request :: request(),
|
||||
Received :: binary(),
|
||||
NewReceived :: binary().
|
||||
|
||||
ws_tetris2(Sock, Request, Received) ->
|
||||
%tell("~p: ws_tetris request: ~tp", [?LINE, Request]),
|
||||
case fd_ws:handshake(Request) of
|
||||
{ok, Response} ->
|
||||
respond(Sock, Response),
|
||||
tetris_init(Sock),
|
||||
ws_tetris_loop(Sock, [], Received);
|
||||
Error ->
|
||||
tell("ws_tetris: error: ~tp", [Error]),
|
||||
http_err(Sock, 400)
|
||||
end.
|
||||
|
||||
|
||||
-spec ws_tetris_loop(Sock, Frames, Received) -> NewReceived
|
||||
when Sock :: gen_tcp:socket(),
|
||||
Frames :: [fd_ws:frame()],
|
||||
Received :: binary(),
|
||||
NewReceived :: binary().
|
||||
|
||||
ws_tetris_loop(Sock, Frames, Received) ->
|
||||
tell("~p:ws_tetris_loop(Sock, ~p, ~p)", [?MODULE, Frames, Received]),
|
||||
%% create tetris state
|
||||
case inet:setopts(Sock, [{active, once}]) of
|
||||
ok ->
|
||||
receive
|
||||
{tcp, Sock, Bin} ->
|
||||
Rcv1 = <<Received/binary, Bin/binary>>,
|
||||
tell("~p:~p rcv1: ~tp", [?MODULE, ?LINE, Rcv1]),
|
||||
ws_tetris_loop(Sock, Frames, <<>>);
|
||||
{tetris, State} ->
|
||||
tell("tetris: ~p", [State]),
|
||||
fd_ws:send(Sock, {text, State}),
|
||||
ws_tetris_loop(Sock, Frames, Received);
|
||||
{tcp_closed, Sock} -> {error, tcp_closed};
|
||||
{tcp_error, Sock, Reason} -> {error, {tcp_error, Reason}}
|
||||
after 30_000 ->
|
||||
{error, timeout}
|
||||
end;
|
||||
{error, Reason} ->
|
||||
{error, {inet, Reason}}
|
||||
end.
|
||||
|
||||
tetris_init(_Sock) ->
|
||||
tell("~p tetris_init", [self()]),
|
||||
Parent = self(),
|
||||
_Child = spawn_link(fun() -> poop(Parent) end),
|
||||
ok.
|
||||
|
||||
poop(Parent) ->
|
||||
tell("~p poop(~p)", [self(), Parent]),
|
||||
Parent ! {tetris, "poop\n"},
|
||||
timer:sleep(3_000),
|
||||
poop(Parent).
|
||||
|
||||
%% ------------------------------
|
||||
%% echo
|
||||
%% ------------------------------
|
||||
|
||||
ws_echo(Sock, Request) ->
|
||||
try
|
||||
ws_echo2(Sock, Request)
|
||||
@ -269,12 +375,9 @@ ws_echo(Sock, Request) ->
|
||||
end.
|
||||
|
||||
ws_echo2(Sock, Request) ->
|
||||
tell("~p: ws_echo request: ~tp", [?LINE, Request]),
|
||||
case fd_ws:handshake(Request) of
|
||||
{ok, Response} ->
|
||||
tell("~p: ws_echo response: ~tp", [?LINE, Response]),
|
||||
respond(Sock, Response),
|
||||
tell("~p: ws_echo: entering loop", [?LINE]),
|
||||
ws_echo_loop(Sock);
|
||||
Error ->
|
||||
tell("ws_echo: error: ~tp", [Error]),
|
||||
@ -285,15 +388,15 @@ ws_echo_loop(Sock) ->
|
||||
ws_echo_loop(Sock, [], <<>>).
|
||||
|
||||
ws_echo_loop(Sock, Frames, Received) ->
|
||||
tell("~p: ws_echo_loop: entering loop", [?LINE]),
|
||||
tell("~p ws_echo_loop(Sock, ~tp, ~tp)", [self(), Frames, Received]),
|
||||
case fd_ws:recv(Sock, Received, 5*fd_ws:min(), Frames) of
|
||||
Result = {ok, Message, NewFrames, NewReceived} ->
|
||||
tell("~p: ws_echo_loop ok: ~tp", [?LINE, Result]),
|
||||
tell("~p echo message: ~tp", [self(), Message]),
|
||||
% send the same message back
|
||||
ok = fd_ws:send(Sock, Message),
|
||||
ws_echo_loop(Sock, NewFrames, NewReceived);
|
||||
Error ->
|
||||
tell("ws_echo_loop: error: ~tp", [Error]),
|
||||
tell(error, "ws_echo_loop: error: ~tp", [Error]),
|
||||
fd_ws:send(Sock, {close, <<>>}),
|
||||
error(Error)
|
||||
end.
|
||||
@ -333,6 +436,12 @@ wfcin(Sock, Request) ->
|
||||
http_err(Sock, 400).
|
||||
|
||||
|
||||
|
||||
-spec ctx(Cookies) -> {Cookie, Context}
|
||||
when Cookies :: #{binary() := Cookie},
|
||||
Cookie :: binary(),
|
||||
Context :: wfc_eval_context:context().
|
||||
|
||||
ctx(#{<<"wfc">> := Cookie}) ->
|
||||
case fd_cache:query(Cookie) of
|
||||
{ok, Context} -> {Cookie, Context};
|
||||
@ -341,17 +450,40 @@ ctx(#{<<"wfc">> := Cookie}) ->
|
||||
ctx(_) ->
|
||||
{new_cookie(), wfc_eval_context:default()}.
|
||||
|
||||
|
||||
|
||||
-spec new_cookie() -> Cookie
|
||||
when Cookie :: binary().
|
||||
|
||||
new_cookie() ->
|
||||
binary:encode_hex(crypto:strong_rand_bytes(8)).
|
||||
|
||||
|
||||
|
||||
-spec jsgud(JSON) -> Encodable
|
||||
when JSON :: zj:value(),
|
||||
Encodable :: JSON.
|
||||
|
||||
jsgud(X) ->
|
||||
#{"ok" => true,
|
||||
"result" => X}.
|
||||
|
||||
|
||||
|
||||
-spec jsbad(JSON) -> JSBad
|
||||
when JSON :: zj:value(),
|
||||
JSBad :: zj:value().
|
||||
|
||||
jsbad(X) ->
|
||||
#{"ok" => false,
|
||||
"error" => X}.
|
||||
|
||||
|
||||
|
||||
-spec http_err(Socket, ErrorCode) -> ok
|
||||
when Socket :: gen_tcp:socket(),
|
||||
ErrorCode :: integer().
|
||||
|
||||
http_err(Sock, N) ->
|
||||
Slogan = qhl:slogan(N),
|
||||
Body = ["<!doctype html>"
|
||||
@ -372,10 +504,21 @@ http_err(Sock, N) ->
|
||||
respond(Sock, Resp).
|
||||
|
||||
|
||||
respond(Sock, Response) ->
|
||||
|
||||
-spec respond(Sock, Response) -> ok
|
||||
when Sock :: gen_tcp:socket(),
|
||||
Response :: response().
|
||||
|
||||
respond(Sock, Response = #response{code = Code}) ->
|
||||
tell("~tp ~tp ~ts", [self(), Code, qhl:slogan(Code)]),
|
||||
gen_tcp:send(Sock, fmtresp(Response)).
|
||||
|
||||
|
||||
|
||||
-spec fmtresp(Response) -> Formatted
|
||||
when Response :: response(),
|
||||
Formatted :: iolist().
|
||||
|
||||
fmtresp(#response{type = page, %% no idea what {data, String} is
|
||||
version = http11,
|
||||
code = Code,
|
||||
@ -389,6 +532,12 @@ fmtresp(#response{type = page, %% no idea what {data, String} is
|
||||
Body].
|
||||
|
||||
|
||||
|
||||
-spec add_headers(Existing, Body) -> Hdrs
|
||||
when Existing :: [{iolist(), iolist()}],
|
||||
Body :: iolist(),
|
||||
Hdrs :: [{iolist(), iolist()}].
|
||||
|
||||
%% body needed just for size
|
||||
add_headers(Hs, Body) ->
|
||||
Defaults = default_headers(Body),
|
||||
@ -396,6 +545,11 @@ add_headers(Hs, Body) ->
|
||||
proplists:from_map(maps:merge(Defaults, Hs2)).
|
||||
|
||||
|
||||
|
||||
-spec default_headers(Body) -> HdrsMap
|
||||
when Body :: iolist(),
|
||||
HdrsMap :: #{iolist() := iolist()}.
|
||||
|
||||
default_headers(Body) ->
|
||||
BodySize = byte_size(iolist_to_binary(Body)),
|
||||
#{"Server" => "fewd 0.1.0",
|
||||
|
||||
@ -3,6 +3,11 @@
|
||||
|
||||
-behavior(gen_server).
|
||||
|
||||
-export_type([
|
||||
entry/0,
|
||||
maybe_entry/0
|
||||
]).
|
||||
|
||||
-export([
|
||||
%% caller context
|
||||
base_path/0,
|
||||
@ -16,6 +21,9 @@
|
||||
|
||||
-include("$zx_include/zx_logger.hrl").
|
||||
|
||||
-type entry() :: fd_sfc_entry:entry().
|
||||
-type maybe_entry() :: {found, fd_sfc_entry:entry()} | not_found.
|
||||
|
||||
|
||||
-record(s, {base_path = base_path() :: file:filename(),
|
||||
cache = fd_sfc_cache:new(base_path()) :: fd_sfc_cache:cache(),
|
||||
@ -27,15 +35,22 @@
|
||||
%% caller context
|
||||
%%-----------------------------------------------------------------------------
|
||||
|
||||
-spec base_path() -> file:filename().
|
||||
base_path() ->
|
||||
filename:join([zx:get_home(), "priv", "static"]).
|
||||
|
||||
-spec renew() -> ok.
|
||||
renew() ->
|
||||
gen_server:cast(?MODULE, renew).
|
||||
|
||||
|
||||
-spec query(HttpPath) -> MaybeEntry
|
||||
when HttpPath :: binary(),
|
||||
MaybeEntry :: maybe_entry().
|
||||
query(Path) ->
|
||||
gen_server:call(?MODULE, {query, Path}).
|
||||
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, none, []).
|
||||
|
||||
|
||||
@ -57,18 +57,11 @@ new2(BasePath) ->
|
||||
true -> filename:absname(BasePath);
|
||||
false -> filename:absname(filename:dirname(BasePath))
|
||||
end,
|
||||
%% hacky, fuck you
|
||||
RemovePrefix =
|
||||
fun (Prefix, Size, From) ->
|
||||
<<Prefix:Size/bytes, Rest/bytes>> = From,
|
||||
Rest
|
||||
end,
|
||||
BBaseDir = unicode:characters_to_binary(BaseDir),
|
||||
BBS = byte_size(BBaseDir),
|
||||
HandlePath =
|
||||
fun(AbsPath, AccCache) ->
|
||||
BAbsPath = unicode:characters_to_binary(AbsPath),
|
||||
HttpPath = RemovePrefix(BBaseDir, BBS, BAbsPath),
|
||||
HttpPath = remove_prefix(BBaseDir, BAbsPath),
|
||||
NewCache =
|
||||
case fd_sfc_entry:new(AbsPath) of
|
||||
{found, Entry} -> maps:put(HttpPath, Entry, AccCache);
|
||||
@ -81,3 +74,8 @@ new2(BasePath) ->
|
||||
_recursive = true,
|
||||
_fun = HandlePath,
|
||||
_init_acc = #{}).
|
||||
|
||||
remove_prefix(Prefix, From) ->
|
||||
Size = byte_size(Prefix),
|
||||
<<Prefix:Size/bytes, Rest/bytes>> = From,
|
||||
Rest.
|
||||
|
||||
@ -21,11 +21,6 @@
|
||||
-include("http.hrl").
|
||||
-include("$zx_include/zx_logger.hrl").
|
||||
|
||||
-type request() :: #request{}.
|
||||
-type response() :: #response{}.
|
||||
-type tcp_error() :: closed
|
||||
| {timeout, RestData :: binary() | erlang:iovec()}
|
||||
| inet:posix().
|
||||
|
||||
-define(MAX_PAYLOAD_SIZE, ((1 bsl 63) - 1)).
|
||||
|
||||
|
||||
29
src/qhl.erl
29
src/qhl.erl
@ -49,62 +49,52 @@ parse(Socket, Received) ->
|
||||
%% socket.
|
||||
|
||||
parse(Socket, Received, M = #request{method = undefined}) ->
|
||||
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||
case read_method(Socket, Received) of
|
||||
{ok, Method, Rest} -> parse(Socket, Rest, M#request{method = Method});
|
||||
Error -> Error
|
||||
end;
|
||||
parse(Socket, Received, M = #request{path = undefined}) ->
|
||||
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||
case read_path(Socket, Received) of
|
||||
{ok, Path, Rest} -> parse(Socket, Rest, M#request{path = Path});
|
||||
Error -> Error
|
||||
end;
|
||||
parse(Socket, Received, M = #request{qargs = undefined}) ->
|
||||
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||
case read_qargs(Socket, Received) of
|
||||
{ok, Qargs, Rest} -> parse(Socket, Rest, M#request{qargs = Qargs});
|
||||
Error -> Error
|
||||
end;
|
||||
parse(Socket, Received, M = #request{fragment = undefined}) ->
|
||||
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||
case read_fragment(Socket, Received) of
|
||||
{ok, Fragment, Rest} -> parse(Socket, Rest, M#request{fragment = Fragment});
|
||||
Error -> Error
|
||||
end;
|
||||
parse(Socket, Received, M = #request{version = undefined}) ->
|
||||
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||
case read_version(Socket, Received) of
|
||||
{ok, Version, Rest} -> parse(Socket, Rest, M#request{version = Version});
|
||||
Error -> Error
|
||||
end;
|
||||
parse(Socket, Received, M = #request{headers = undefined}) ->
|
||||
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||
case read_headers(Socket, Received) of
|
||||
{ok, Headers, Rest} -> parse(Socket, Rest, M#request{headers = Headers});
|
||||
Error -> Error
|
||||
end;
|
||||
parse(Socket, Received, M = #request{enctype = undefined}) ->
|
||||
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||
case read_enctype(M) of
|
||||
{ok, Enctype} -> parse(Socket, Received, M#request{enctype = Enctype});
|
||||
Error -> Error
|
||||
end;
|
||||
parse(Socket, Received, M = #request{cookies = undefined}) ->
|
||||
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||
case read_cookies(M) of
|
||||
{ok, Cookies} -> parse(Socket, Received, M#request{cookies = Cookies});
|
||||
Error -> Error
|
||||
end;
|
||||
parse(Socket, Received, M = #request{size = undefined}) ->
|
||||
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||
case read_size(M) of
|
||||
{ok, 0} -> {ok, M#request{size = 0}, none};
|
||||
{ok, Size} -> parse(Socket, Received, M#request{size = Size});
|
||||
Error -> Error
|
||||
end;
|
||||
parse(Socket, Received, M = #request{method = get, body = undefined, size = Size}) ->
|
||||
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||
case read_body(Received, Size) of
|
||||
{ok, Body} -> {ok, M#request{body = Body}, none};
|
||||
{ok, Body, Next} -> {ok, M#request{body = Body}, Next};
|
||||
@ -117,7 +107,6 @@ parse(Socket,
|
||||
method = post,
|
||||
enctype = urlencoded,
|
||||
size = Size}) ->
|
||||
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||
case read_body(Received, Size) of
|
||||
{ok, Body} ->
|
||||
{ok, M#request{body = parts_to_map(posted(Body))}, none};
|
||||
@ -141,7 +130,6 @@ parse(Socket,
|
||||
method = post,
|
||||
enctype = {multipart, Boundary},
|
||||
size = Size}) ->
|
||||
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||
case read_multipart(Socket, Received, Boundary, Size) of
|
||||
{ok, Parts} -> {ok, M#request{body = parts_to_map(Parts)}, none};
|
||||
{ok, Parts, Next} -> {ok, M#request{body = parts_to_map(Parts)}, Next};
|
||||
@ -153,12 +141,10 @@ parse(Socket,
|
||||
method = post,
|
||||
enctype = json,
|
||||
size = Size}) ->
|
||||
io:format("~p parse(~p, ~p, ~p)~n", [?LINE, Socket, Received, M]),
|
||||
case read_body(Received, Size) of
|
||||
{ok, Body} -> read_json(M#request{body = Body}, none);
|
||||
{ok, Body, Next} -> read_json(M#request{body = Body}, Next);
|
||||
{incomplete, Body} ->
|
||||
io:format("~p {incomplete, ~p}~n", [?LINE, Body]),
|
||||
case accumulate(Socket, M#request{body = Body}) of
|
||||
{ok, NewM = #request{body = NewBody}} ->
|
||||
read_json(NewM#request{body = NewBody}, none);
|
||||
@ -528,7 +514,6 @@ read_size(#request{method = options}) ->
|
||||
|
||||
|
||||
read_body(Received, Size) ->
|
||||
io:format("~p read_body(~p, ~p)~n", [?LINE, Received, Size]),
|
||||
case Received of
|
||||
<<Bin:Size/binary>> ->
|
||||
{ok, Bin};
|
||||
@ -826,11 +811,9 @@ accumulate(Socket, M = #request{size = Size, body = Body}) ->
|
||||
end.
|
||||
|
||||
accumulate(Socket, Remaining, Received) when Remaining > 0 ->
|
||||
io:format("~p accumulate(~p, ~p, ~p)~n", [?LINE, Socket, Remaining, Received]),
|
||||
ok = inet:setopts(Socket, [{active, once}]),
|
||||
receive
|
||||
{tcp, Socket, Bin} ->
|
||||
io:format("~p~n", [?LINE]),
|
||||
Size = byte_size(Bin),
|
||||
if
|
||||
Size == Remaining ->
|
||||
@ -845,16 +828,12 @@ accumulate(Socket, Remaining, Received) when Remaining > 0 ->
|
||||
{ok, NewReceived, Next}
|
||||
end;
|
||||
{tcp_closed, Socket} ->
|
||||
io:format("~p~n", [?LINE]),
|
||||
{error, tcp_closed};
|
||||
{tcp_error, Socket, Reason} ->
|
||||
io:format("~p~n", [?LINE]),
|
||||
{error, {tcp_error, Reason}};
|
||||
X ->
|
||||
io:format("~p raseevd: ~p~n", [?LINE, X])
|
||||
after 10_000 ->
|
||||
io:format("~p~n", [?LINE]),
|
||||
{error, timeout}
|
||||
{error, {tcp_error, Reason}}
|
||||
%X ->
|
||||
after 3_000 ->
|
||||
{error, timeout}
|
||||
end;
|
||||
accumulate(_, 0, Received) ->
|
||||
{ok, Received};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user