aboutsummaryrefslogtreecommitdiff
path: root/server/src/jchat_http_static.erl
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/jchat_http_static.erl')
-rw-r--r--server/src/jchat_http_static.erl110
1 files changed, 110 insertions, 0 deletions
diff --git a/server/src/jchat_http_static.erl b/server/src/jchat_http_static.erl
new file mode 100644
index 0000000..e11a9c0
--- /dev/null
+++ b/server/src/jchat_http_static.erl
@@ -0,0 +1,110 @@
+-module(jchat_http_static).
+
+-export([init/2]).
+
+init(Req0, State) ->
+ Method = cowboy_req:method(Req0),
+ handle_request(Method, Req0, State).
+
+handle_request(<<"GET">>, Req0, State) ->
+ Path = cowboy_req:path(Req0),
+ serve_static_file(Path, Req0, State);
+
+handle_request(<<"HEAD">>, Req0, State) ->
+ Path = cowboy_req:path(Req0),
+ serve_static_file(Path, Req0, State);
+
+handle_request(<<"OPTIONS">>, Req0, State) ->
+ Req1 = cowboy_req:reply(200, #{
+ <<"access-control-allow-origin">> => <<"*">>,
+ <<"access-control-allow-methods">> => <<"GET, HEAD, OPTIONS">>,
+ <<"access-control-allow-headers">> => <<"content-type">>
+ }, <<>>, Req0),
+ {ok, Req1, State};
+
+handle_request(_Method, Req0, State) ->
+ Req1 = cowboy_req:reply(405, #{
+ <<"content-type">> => <<"text/plain">>
+ }, <<"Method Not Allowed">>, Req0),
+ {ok, Req1, State}.
+
+serve_static_file(Path, Req0, State) ->
+ StaticDir = jchat_config:static_files_dir(),
+
+ % Convert path to filename
+ Filename = case Path of
+ <<"/">> -> "index.html";
+ <<"/", Rest/binary>> -> binary_to_list(Rest);
+ _ -> binary_to_list(Path)
+ end,
+
+ FilePath = filename:join(StaticDir, Filename),
+
+ case file:read_file(FilePath) of
+ {ok, Content} ->
+ ContentType = get_content_type(Filename),
+ Req1 = cowboy_req:reply(200, #{
+ <<"content-type">> => ContentType,
+ <<"access-control-allow-origin">> => <<"*">>,
+ <<"cache-control">> => <<"public, max-age=3600">>
+ }, Content, Req0),
+ {ok, Req1, State};
+ {error, enoent} ->
+ % If file not found and it's not an API request, serve index.html for SPA routing
+ case is_spa_route(Filename) of
+ true ->
+ IndexPath = filename:join(StaticDir, "index.html"),
+ case file:read_file(IndexPath) of
+ {ok, Content} ->
+ Req1 = cowboy_req:reply(200, #{
+ <<"content-type">> => <<"text/html; charset=utf-8">>,
+ <<"access-control-allow-origin">> => <<"*">>
+ }, Content, Req0),
+ {ok, Req1, State};
+ {error, _} ->
+ serve_404(Req0, State)
+ end;
+ false ->
+ serve_404(Req0, State)
+ end;
+ {error, _} ->
+ serve_500(Req0, State)
+ end.
+
+serve_404(Req0, State) ->
+ Req1 = cowboy_req:reply(404, #{
+ <<"content-type">> => <<"text/plain">>
+ }, <<"Not Found">>, Req0),
+ {ok, Req1, State}.
+
+serve_500(Req0, State) ->
+ Req1 = cowboy_req:reply(500, #{
+ <<"content-type">> => <<"text/plain">>
+ }, <<"Internal Server Error">>, Req0),
+ {ok, Req1, State}.
+
+get_content_type(Filename) ->
+ case filename:extension(Filename) of
+ ".html" -> <<"text/html; charset=utf-8">>;
+ ".css" -> <<"text/css; charset=utf-8">>;
+ ".js" -> <<"application/javascript; charset=utf-8">>;
+ ".json" -> <<"application/json; charset=utf-8">>;
+ ".png" -> <<"image/png">>;
+ ".jpg" -> <<"image/jpeg">>;
+ ".jpeg" -> <<"image/jpeg">>;
+ ".gif" -> <<"image/gif">>;
+ ".svg" -> <<"image/svg+xml">>;
+ ".ico" -> <<"image/x-icon">>;
+ ".woff" -> <<"font/woff">>;
+ ".woff2" -> <<"font/woff2">>;
+ ".ttf" -> <<"font/ttf">>;
+ ".eot" -> <<"application/vnd.ms-fontobject">>;
+ _ -> <<"application/octet-stream">>
+ end.
+
+is_spa_route(Filename) ->
+ % Don't serve index.html for actual file extensions
+ case filename:extension(Filename) of
+ "" -> true; % No extension, likely a SPA route
+ _ -> false % Has extension, likely a real file
+ end.