aboutsummaryrefslogtreecommitdiff
path: root/server/src/jchat_http_static.erl
blob: e11a9c05d0bb2a694207941060d5fb25ee19219e (plain)
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.