1%%% File : yapp.erl 2%%% @author Mikael Karlsson <mikael@creado.se> 3%%% @since 11 Dec 2005 by Mikael Karlsson <mikael@creado.se> 4%%% @see yapp_handler 5%%% @doc Yaws applications handler. 6%%% <p>An easy way to deploy Yaws applications independently of 7%%% each other.</p> 8%%% <p>This module implements Yaws runmod and arg_rewrite_mod interfaces. 9%%% It has functions to insert and remove Yapps to the Yaws sconfs records. 10%%% It is used by the yapp_handler implementation. The yapp module will make Yaws 11%%% temporarily switch the docroot 12%%% to the applications priv/docroot directory when it encounters the registered 13%%% URL path of the application. One can set another docroot than the default 14%%% (priv/docroot) by setting the environment variable yapp_docroot in the 15%%% .app file for the application. 16%%% The application may also have own appmods which are put in the 17%%% application environment variable yapp_appmods like: </p> 18%%% <code> {env, [ 19%%% {yapp_appmods,[{"ctrl",enityme_controller}]} 20%%% ]}, </code> 21%%% <p> 22%%% In order to include the yapp module, runmod shall be set to yapp. 23%%% The arg_rewrite_mod shall also be set to yapp and the opaque variable 24%%% yapp_server_id shall be set to an unique string for every virtual server 25%%% using yapp, like: </p> 26%%% <pre> 27%%% . 28%%% ebin_dir = /usr/local/yaws/lib/yapp/ebin 29%%% . 30%%% runmod = yapp 31%%% . 32%%% <server aspen4> 33%%% port = 8001 34%%% listen = 0.0.0.0 35%%% dir_listings = true 36%%% arg_rewrite_mod = yapp 37%%% docroot = /home/yaws/scripts/../www 38%%% <opaque> 39%%% yapp_server_id = aspeninternal 40%%% bootstrap_yapps = yapp 41%%% </opaque> 42%%% </server> 43%%% </pre> 44%%% 45%%% <p><em>Note:</em> The yapp application is in itself a "Yapp" with a small admin console. 46%%% So by adding the line <code>bootstrap_yapps = yapp</code> as above you will get access 47%%% to the admin console on http://yourservername/yapp/index.html</p> 48%%% <p><em>Note2:</em> The current available registry implementation uses Mnesia so you need 49%%% to create a mnesia schema (if not already done) for the node(s) you are running Yaws on. 50%%% <code>mnesia:create_schema([node()]).</code></p> 51%%% 52%%% @type yawsArg() = term(). This is the #arg record as defined 53%%% by yaws_api.hrl. 54%%% 55%%% @type yawsSconf() = term(). This is the #sconf record as defined 56%%% by yaws.hrl. 57%%% 58%%% @type yawsGconf() = term(). This is the #gconf record as defined 59%%% by yaws.hrl. 60 61-module(yapp). 62-author('mikael@creado.se'). 63 64-include("yaws_api.hrl"). 65-export([arg_rewrite/1, start/0, prepath/1, insert/1, insert/2, remove/2, 66 log/3, 67 reset_yaws_conf/0, srv_id/1, 68 get_bootstrap_yapps/0, get_yapps/0, get_server_ids/0]). 69 %% reload_yaws/0, 70 71-define(prepath, yapp_prepath). 72-define(srv_id, "yapp_server_id"). 73-define(bootstrap_yapps, "bootstrap_yapps"). %% Opaque key for "bootstrap yapps" 74-define(erlyweb_appname,"appname"). 75-define(yapp_list, yapp_list). 76 77-define(priv_docroot, "priv/docroot"). 78 79%% This record is stored in the sconf opaque property yapp_list 80-record(yapp, { 81 urlpath, 82 docroot = "", 83 appname = "", 84 appmods = [], 85 opaque = [] 86 }). 87 88%% The yapp_reg is stored in Mnesia in the y_registry 89%% and contains a list of {yapp_server_id, yapps} tuples, where 90%% yapps is a list of {UrlPath, AppName} tuples 91%% UrlPath = string() 92%% Interface arg_rewrite_mod 93%% @spec arg_rewrite(Arg::yawsArg()) -> yawsArg() 94%% @doc Interface function for a Yaws arg_rewrite_mod. If 95%% it finds a registered Yapp it will strip of the 96%% path up to the Yapp and redirect the docroot. 97arg_rewrite(Arg) -> 98 case find_registered_yapps(Arg) of 99 undefined -> 100 Arg; 101 {#yapp{urlpath = YappPath, docroot = Docroot, 102 appmods = YappMods, opaque = YOpaque }, _Rest} -> 103 104 DocMount = 105 case string:right(YappPath,1) of 106 "/" -> YappPath; 107 _ -> YappPath ++ "/" 108 end, 109 110 VDir = {"vdir", DocMount ++ " " ++ Docroot}, 111 112 AddOpaque = [ VDir | YOpaque], 113 114 %% Add Yapp appmods, Yaws uses process dictionary. 115 SC = get(sc), 116 AppMods = yaws:sconf_appmods(SC), 117 Opaque = yaws:sconf_opaque(SC), 118 NYappPath = case YappPath of 119 [$/|YPTail] -> 120 YPTail; 121 _ -> 122 YappPath 123 end, 124 RemappedYappMods = lists:map(fun({PE, Mod, Ex}) -> 125 {PE, Mod, [[NYappPath] ++ X || X <- Ex]}; 126 (AM) -> 127 AM 128 end, YappMods), 129 SC2 = yaws:setup_sconf([{docroot, Docroot}, 130 {appmods, AppMods ++ RemappedYappMods}, 131 {opaque, AddOpaque ++ Opaque}], SC), 132 put(sc, SC2), 133 134 Opaque2 = Arg#arg.opaque, 135 Arg#arg{docroot=Docroot, docroot_mount=DocMount, 136 opaque = AddOpaque ++ Opaque2} 137 end. 138 139 140%% Interface run_mod 141%% @spec start() -> void() 142%% @doc Interface function for Yaws run_mod. 143%% This fun is spawned from Yaws so no return val is expected. 144%% All Yapps can expect mnesia to be started, so mnesia is ensured 145%% to be started. 146%% For every server id that has Yapps the yapp module is registered 147%% as an arg_rewrite_mod in #sconf. 148%% Configuration data for mapping Yapp paths to applications is looked 149%% up in a mnesia registry table and stored in Yaws #sconf.opaque record 150%% for the each server id. 151start() -> 152 case wait_for_yaws() of 153 ok -> 154 log(info, "Starting yapp~n",[]), 155 application:start(yapp); 156 Error -> 157 log(error, "Failed waiting for Yaws to start when starting Yapp: ~p~n",[Error]) 158 end. 159 160wait_for_yaws() -> wait_for_yaws(20). 161 162wait_for_yaws(0) -> {error, timeout}; 163wait_for_yaws(N) -> 164 WA = application:which_applications(), 165 case lists:keysearch(yaws, 1, WA) of 166 false -> 167 log(info, "Yapp starting but Yaws not ready - waiting 500 ms",[]), 168 receive after 500 -> ok end, %% Give Yaws some time to settle own things 169 wait_for_yaws(N-1); 170 _ -> 171 ok 172 end. 173 174 175%% @spec prepath(Arg::yawsArg()) -> Path::string() 176%% @doc Get the Yapp root-path. Can be called from a Yapp erl/1 177%% fun or an erl section in a .yaws file to get the Yapp root 178%% path. 179prepath(Arg) -> 180 Arg#arg.docroot_mount. 181 182 183%% @spec log(Level, FormatStr::string(), Args) -> void() 184%% Level = error | warning | info | debug 185%% Args = [term()] 186%% @doc Yapp interface to the error_logger. 187log(debug, FormatStr, Args) -> 188 gen_event:notify(error_logger, {debug_msg, group_leader(), {self(), FormatStr, Args}}); 189log(info, FormatStr, Args) -> 190 error_logger:info_msg(FormatStr, Args); 191log(warning, FormatStr, Args) -> 192 error_logger:warning_msg(FormatStr, Args); 193log(error, FormatStr, Args) -> 194 error_logger:error_msg(FormatStr, Args); 195log(Level, FormatStr, Args) -> 196 error_logger:error_msg("Unknown logging level ~p ," ++ FormatStr,[Level|Args]). 197 198%% Utility function 199 200find_registered_yapps(Arg) -> 201 Req = Arg#arg.req, 202 {abs_path, Path} = Req#http_request.path, 203 A = proplists:get_value(?yapp_list, Arg#arg.opaque,[]), 204 find_registered(A, Path). 205 206find_registered([],_Path) -> 207 undefined; 208find_registered([ #yapp{urlpath = RegPath} = Y | T ], Path) -> 209 case string:str(Path, RegPath) of 210 1 -> 211 case string:substr(Path,1+length(RegPath)) of 212 [] -> {Y,[]}; 213 [$/ |_] = Rest -> {Y,Rest}; 214 _ -> find_registered(T,Path) 215 end; 216 _ -> 217 find_registered(T, Path) 218 end. 219 220%% @hidden 221insert([]) -> 222 ok; 223insert(Yapps) when is_list(Yapps) -> 224 {ok, Gconf, Sconfs} = get_conf(), 225 NewSconfs = (catch insert_yapps_in_sconfs(Yapps, Sconfs)), 226 yaws_api:setconf(Gconf, NewSconfs). 227%% @hidden 228insert(SrvId, Yapp)-> 229 {ok, Gconf, SconfGroups} = get_conf(), 230 NewSconfGroups = insert_yapp_in_sconfgroups(SrvId, Yapp, SconfGroups), 231 yaws_api:setconf(Gconf, NewSconfGroups). 232 233 234insert_yapps_in_sconfs([], SconfGroups) -> 235 SconfGroups; 236insert_yapps_in_sconfs([{_SrvId,[]}|T], SconfGroups) -> 237 insert_yapps_in_sconfs(T, SconfGroups); 238insert_yapps_in_sconfs([{SrvId,[Y|YS]}|T], SconfGroups) -> 239 NewSconfGroups = insert_yapp_in_sconfgroups(SrvId,Y,SconfGroups), 240 insert_yapps_in_sconfs([{SrvId,YS}|T], NewSconfGroups). 241 242insert_yapp_in_sconfgroups(_SrvId, _Yapp,[]) -> 243 []; 244insert_yapp_in_sconfgroups(SrvId, Yapp, [SCG|T]) -> 245 [insert_yapp_in_sconfgroup(SrvId,Yapp, SCG) | insert_yapp_in_sconfgroups(SrvId,Yapp,T)]. 246 247insert_yapp_in_sconfgroup(_SrvId, _Yapp, []) -> 248 []; 249insert_yapp_in_sconfgroup(SrvId, Yapp, [SC|SCG]) -> 250 case srv_id(SC) of 251 SrvId -> 252 case insert_yapp_in_sconf(Yapp,SC) of 253 no_app -> 254 [SC | SCG]; 255 NewSC -> 256 [NewSC | SCG] 257 end; 258 _ -> 259 [ SC | insert_yapp_in_sconfgroup(SrvId,Yapp,SCG)] 260 end. 261 262%% the yapp application itself maybe a yapp, treated as special case 263insert_yapp_in_sconf({UrlPath, yapp}, SC) -> 264 insert_yapp_in_sconf0({UrlPath, yapp}, SC); 265insert_yapp_in_sconf({UrlPath, AppName}, SC) -> 266 case start_app([AppName]) of 267 ok -> 268 insert_yapp_in_sconf0({UrlPath, AppName}, SC); 269 Error -> 270 log(error, "yapp:insert_yapp_in_sconf - Error loading Yapp ~p, ~p", 271 [AppName, Error]), 272 no_app 273 end. 274 275start_app([AppName|T]) -> 276 log(info, "Starting app ~p" , [AppName]), 277 case application:start(AppName) of 278 {error,{not_started,RequiredApp}} -> 279 start_app([RequiredApp,AppName|T]); 280 {error,{already_started,AppName}} -> 281 start_app(T); 282 ok -> 283 start_app(T); 284 Error -> Error 285 end; 286start_app([]) -> ok; 287start_app(Error) -> Error. 288 289insert_yapp_in_sconf0({UrlPath, AppName}, SC) -> 290 log(info,"Inserting App ~p in Url ~p~n", [AppName, UrlPath]), 291 OP = yaws:sconf_opaque(SC), 292 AppEnv = application:get_all_env(AppName), 293 DocSubRoot = proplists:get_value(yapp_docroot, AppEnv, ?priv_docroot), 294 YAppMods = proplists:get_value(yapp_appmods, AppEnv, []), 295 YOpaque = proplists:get_value(yapp_opaque, AppEnv, []), 296 Y = #yapp{urlpath= UrlPath, 297 docroot = code:lib_dir(AppName) ++ "/" ++ DocSubRoot, 298 appname = AppName, 299 appmods = YAppMods, 300 opaque = YOpaque }, 301 302 OP2 = case proplists:get_value(?yapp_list, OP) of 303 undefined -> 304 [{?yapp_list,[Y]} | OP]; 305 YR -> 306 YR2 = insert_yapp_in_yapp_list(Y, YR), 307 lists:keyreplace(?yapp_list, 1, OP, {?yapp_list, YR2}) 308 end, 309 yaws:setup_sconf([{opaque, OP2}], SC). 310 311insert_yapp_in_yapp_list(#yapp{} = Y, []) -> 312 [Y]; 313insert_yapp_in_yapp_list(#yapp{urlpath= UP} = Y, [#yapp{urlpath=UP} | T]) -> 314 [Y|T]; 315insert_yapp_in_yapp_list(Y, [H | T]) -> 316 [ H | insert_yapp_in_yapp_list(Y,T) ]. 317 318%% @hidden 319remove(SrvId, RegPath) -> 320 {ok, Gconf, Sconfs} = yaws_api:getconf(), 321 NewSconfs = remove_yapp_from_sconfgroups(SrvId, RegPath, Sconfs), 322 yaws_api:setconf(Gconf, NewSconfs). 323 324remove_yapp_from_sconfgroups(_SrvId, _RegPath, []) -> 325 []; 326remove_yapp_from_sconfgroups(SrvId, RegPath, [SCG|T]) -> 327 [remove_yapp_from_sconfgroup(SrvId, RegPath,SCG) | 328 remove_yapp_from_sconfgroups(SrvId, RegPath, T)]. 329 330remove_yapp_from_sconfgroup(_SrvId, _RegPath, []) -> 331 []; 332remove_yapp_from_sconfgroup(SrvId, RegPath, [ H | T ]) -> 333 case srv_id(H) of 334 SrvId -> 335 [ remove_yapp_from_sconf(RegPath, H) | T ]; 336 _ -> 337 [ H | remove_yapp_from_sconfgroup(SrvId, RegPath, T ) ] 338 end. 339 340remove_yapp_from_sconf(RegPath, SC) -> 341 OP = yaws:sconf_opaque(SC), 342 OP2 = case proplists:get_value(?yapp_list, OP) of 343 undefined -> 344 OP; 345 YR -> 346 YR2 = remove_yapp_from_yapp_list(RegPath, YR), 347 lists:keyreplace(?yapp_list, 1, OP, {?yapp_list, YR2}) 348 end, 349 yaws:setup_sconf([{opaque, OP2}], SC). 350 351remove_yapp_from_yapp_list(_, [] ) -> 352 []; 353remove_yapp_from_yapp_list(RegPath, [ #yapp{urlpath = RegPath} | T ] ) -> 354 T; 355remove_yapp_from_yapp_list(RegPath, [H | T]) -> 356 [H | remove_yapp_from_yapp_list(RegPath, T)]. 357 358%% by tobbe@tornkvist.org 359reset_yaws_conf() -> 360 case catch yaws_config:load(yaws_sup:get_app_args()) of 361 {ok, Gconf, Sconfs} -> 362 yaws_api:setconf(Gconf, Sconfs); 363 Err -> 364 Err 365 end. 366 367%% @spec get_conf() -> {ok,yawsGconf(), Sconfs} 368%% Sconfs = [ yawsSconf() ] 369get_conf() -> 370 yaws_api:getconf(). 371% yaws_config:load(yaws_sup:get_app_args()). 372 373%% @spec srv_id(Sconf) -> string() | undefined 374%% Sconf = yawsSconf() 375%% @doc Get the server id from an Sconf if available 376srv_id(SC) -> 377 OP = yaws:sconf_opaque(SC), 378 proplists:get_value(?srv_id, OP). 379 380%% @spec get_bootstrap_yapps() -> [{ ServerId, [ {Path, ApplicationName}]}] 381%% ServerId = string() 382%% Path = string() 383%% Applicationame = atom() 384%% @doc Gets the Yapps defined in each opaque 385%% "bootstrap_yapps = appname1, appname2" for every server id. (If available). 386%% Bootstrap yapps will get the same pathname as their application name 387%% and are "static" meaning that they can not be removed from the server 388%% unless yaws.conf is changed (or if embedded yaws - yaws:setconf/2 is used). 389get_bootstrap_yapps() -> 390 391 {ok, _Gconf, Sconfs} = get_conf(), 392 393 YL = [begin 394 OP = yaws:sconf_opaque(SC), 395 {proplists:get_value(?srv_id, OP), 396 make_yapp_tuples(proplists:get_value(?bootstrap_yapps, OP))} 397 end || SC <- lists:flatten(Sconfs) ], 398 399 [{SrvId, Yapps} || 400 {SrvId, Yapps} <- YL, SrvId =/= undefined, Yapps =/= []]. 401 402 403 404make_yapp_tuples(undefined) -> 405 []; 406make_yapp_tuples(BootStrapYapps) -> 407 [ make_yapp_tuple(A) || A <- string:tokens(BootStrapYapps,",")]. 408 409make_yapp_tuple(A) -> 410 B = string:strip(A), 411 {"/" ++ B, list_to_atom(B)}. 412 413%% @spec get_yapps() -> [{ServId,[{yapp, Urlpath, Docroot, Appname , Appmods}]}] 414%% Urlpath = string() 415%% Docroot = string() 416%% Appname = atom() 417%% Appmods = [atom()] 418%% @doc Gets all Yapps that are configured for the Yaws server. 419get_yapps() -> 420 {ok, _Gconf, Sconfs} = yaws_api:getconf(), 421 Yapps1 = [begin 422 OP = yaws:sconf_opaque(SC), 423 {proplists:get_value("yapp_server_id", OP), 424 proplists:get_value(yapp_list, OP)} 425 end || SC <- lists:flatten(Sconfs)], 426 [{S,Y} || {S,Y} <- Yapps1, Y =/= undefined, S =/= undefined]. 427 428%% @spec get_server_ids() -> [string()] 429%% @doc Lists all server ids. 430get_server_ids() -> 431 {ok, _Gconf, Sconfs} = get_conf(), 432 SrvIds1 = [begin 433 OP = yaws:sconf_opaque(SC), 434 proplists:get_value("yapp_server_id", OP) 435 end || SC <- lists:flatten(Sconfs)], 436 [S|| S <- SrvIds1, S =/= undefined]. 437 438