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