1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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.
|