PageRenderTime 51ms CodeModel.GetById 16ms app.highlight 31ms RepoModel.GetById 1ms app.codeStats 1ms

/src/support/z_webmachine_error_handler.erl

http://github.com/zotonic/zotonic
Erlang | 173 lines | 130 code | 24 blank | 19 comment | 1 complexity | db2cd41fbcee5361caf0dc96995f3706 MD5 | raw file
  1%% @author Marc Worrell <marc@worrell.nl>
  2%% @copyright 2009-2012  Marc Worrell
  3%%
  4%% @doc Error handler for webmachine HTTP errors. The result varies depending on the content type being served.
  5%% @todo Mail the error to the webadmin
  6
  7%% Copyright 2009-2012 Marc Worrell, Arjan Scherpenisse
  8%%
  9%% Licensed under the Apache License, Version 2.0 (the "License");
 10%% you may not use this file except in compliance with the License.
 11%% You may obtain a copy of the License at
 12%% 
 13%%     http://www.apache.org/licenses/LICENSE-2.0
 14%% 
 15%% Unless required by applicable law or agreed to in writing, software
 16%% distributed under the License is distributed on an "AS IS" BASIS,
 17%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 18%% See the License for the specific language governing permissions and
 19%% limitations under the License.
 20
 21-module(z_webmachine_error_handler).
 22-author("Marc Worrell <marc@worrell.nl>").
 23-author("Arjan Scherpenisse <arjan@scherpenisse.net>").
 24
 25-compile([{parse_transform, lager_transform}]).
 26
 27-export([render_error/3]).
 28
 29-include_lib("zotonic.hrl").
 30
 31
 32render_error(Code = 500, ReqData, Reason) ->
 33    lager:error("webmachine error: path=~p: ~p", [wrq:path(ReqData), Reason]),
 34    ErrorDump = mochiweb_html:escape(lists:flatten(io_lib:format("~p", [Reason]))),
 35    Type = webmachine_request:get_metadata('content-type', ReqData),
 36    error_handler(Type, ReqData, Code, ErrorDump, Reason);
 37
 38render_error(Code = 404, ReqData, _Reason) ->
 39    ErrorDump = mochiweb_html:escape(lists:flatten(io_lib:format("Resource not found: ~p", [wrq:raw_path(ReqData)]))),
 40    Type = webmachine_request:get_metadata('content-type', ReqData),
 41    error_handler(Type, ReqData, Code, ErrorDump, undefined);
 42
 43render_error(Code, ReqData, _Reason) ->
 44    Type = webmachine_request:get_metadata('content-type', ReqData),
 45    error_handler(Type, ReqData, Code, undefined, undefined).
 46
 47
 48error_handler("application/json", ReqData, Code, ErrorDump, _Reason) ->
 49    RD1 = wrq:set_resp_header("Content-Type", "application/json; charset=utf-8", ReqData),
 50    RD2 = wrq:set_resp_header("Content-Encoding", "identity", RD1),
 51    JS = {struct, [{error_code, Code}, {error_dump, ErrorDump}]},
 52    {mochijson:encode(JS), RD2};
 53
 54% Check the extension of the path to see what the resource could have been.
 55error_handler(_, ReqData, Code, ErrorDump, Reason) ->
 56    case z_media_identify:guess_mime(wrq:raw_path(ReqData)) of
 57        "application/octet-stream" -> show_template(ReqData, Code, ErrorDump, Reason);
 58        "application/xhtml+xml" -> show_template(ReqData, Code, ErrorDump, Reason);
 59        "application/" ++ _ -> {<<>>, ReqData};
 60        "audio/" ++ _ -> {<<>>, ReqData};
 61        "video/" ++ _ -> {<<>>, ReqData};
 62        "image/" ++ _ -> {<<>>, ReqData};
 63        "text/css" -> {<<>>, ReqData};
 64        _ -> show_template(ReqData, Code, ErrorDump, Reason)
 65    end.
 66
 67
 68show_template(ReqData, Code, ErrorDump, Reason) ->
 69    RD1 = wrq:set_resp_header("Content-Type", "text/html; charset=utf-8", ReqData),
 70    RD2 = wrq:set_resp_header("Content-Encoding", "identity", RD1),
 71    RD3 = wrq:set_resp_header("X-Robots", "noindex,nofollow", RD2),
 72    Host = webmachine_request:get_metadata('zotonic_host', RD3),
 73    try 
 74        Context = z_context:new(Host),
 75        Vars = case bt_simplify(Reason) of
 76                    {reason, ErlangError, Tab} ->
 77                        [
 78                            {error_code, Code},
 79                            {error_dump, ErrorDump},
 80                            {error_erlang, ErlangError},
 81                            {error_table, Tab}
 82                        ];
 83                    {raw, X} ->
 84                        [
 85                            {error_code, Code}, 
 86                            {error_dump, X}
 87                        ]
 88               end,
 89        Html = z_template:render("error.tpl", Vars, Context),
 90        {Output, _} = z_context:output(Html, Context),
 91        {Output, RD3}
 92    catch
 93        _:_Reason -> {<<>>,RD3}
 94    end.
 95
 96
 97bt_simplify({_E1, {_E2, Reason, BT}}) when is_list(BT) ->
 98    {reason, Reason, bt_table(BT)};
 99bt_simplify({_E, {Reason, BT}}) when is_list(BT) ->
100    {reason, Reason, bt_table(BT)};
101bt_simplify(X) ->
102    {raw, X}.
103
104    bt_table(BT) ->
105        bt_table(BT, []).
106    
107    bt_table([], Acc) ->
108        lists:reverse(Acc);
109    bt_table([{Module, Fun, ArityArgs} | T], Acc) ->
110        bt_table(T, [bt_row(Module, Fun, ArityArgs, undefined, undefined) | Acc]);
111    bt_table([{Module, Fun, ArityArgs, Loc} | T], Acc) ->
112        bt_table(T, [bt_row(Module, Fun, ArityArgs, proplists:get_value(file, Loc), proplists:get_value(line, Loc)) | Acc]).
113
114    bt_row(Module, Fun, ArityArgs, File, Line) ->
115        case is_template(atom_to_list(Module)) of
116            {true, Mod, Template} ->
117                [true, Mod, Template, simplify_args(ArityArgs), loc(File, Line)];
118            false ->
119                SFun = case is_integer(ArityArgs) of
120                            true -> z_convert:to_list(Fun) ++ "/" ++ integer_to_list(ArityArgs);
121                            false -> z_convert:to_list(Fun) ++ "/" ++ integer_to_list(length(ArityArgs))
122                       end,
123                [false, Module, SFun, simplify_args(ArityArgs), loc(File,Line)]
124        end.
125    
126    loc(undefined,undefined) -> undefined;
127    loc(File,undefined) -> z_convert:to_list(File);
128    loc(File,Line) -> z_convert:to_list(File)++":"++z_convert:to_list(Line).
129    
130    simplify_args(N) when is_integer(N) -> undefined;
131    simplify_args(L) ->
132        As = [ simplify_arg(A) || A <- L ],
133        iolist_to_binary([$[, z_utils:combine(", ", As), $]]).
134    
135        simplify_arg(N) when is_integer(N) -> integer_to_list(N);
136        simplify_arg(A) when is_atom(A) -> atom_to_list(A);
137        simplify_arg(B) when is_binary(B) -> B;
138        simplify_arg([]) -> "[]";
139        simplify_arg({}) -> "{}";
140        simplify_arg(L) when is_list(L) -> "[...]";
141        simplify_arg({A,B}) when is_atom(A), is_atom(B) -> [${,atom_to_list(A),$,,atom_to_list(B),$}];
142        simplify_arg(T) when is_tuple(T) ->
143            case is_atom(element(1, T)) of
144                true -> [$#, atom_to_list(element(1,T)), "{}"];
145                false -> io_lib:format("~p", [T])
146            end;
147        simplify_arg(X) -> io_lib:format("~p", [X]).
148    
149    
150    is_template("template_"++R) ->
151        case re:split(R, "_", [{return,list}]) of
152            [_Site,""|Rest] ->
153                case lists:last(Rest) of
154                    "tpl" ->
155                        case re:run(R, "modules_(mod_.*)_templates_(.*)_tpl$", [{capture, all, list}]) of
156                            {match, [_, Module, Path]} ->
157                                {true, Module, Path++".tpl"};
158                            nomatch ->
159                                case re:run(R, "priv_sites_(.*)_templates_(.*)_tpl$", [{capture, all, list}]) of
160                                    {match, [_, Module, Path]} ->
161                                        {true, Module, Path++".tpl"};
162                                    nomatch ->
163                                        false
164                                end
165                        end;
166                    _ -> 
167                        false
168                end;
169            _ ->
170                false
171        end;
172    is_template(_) ->
173        false.