erlangyaws

Is it possible to add YAWS appmods config at runtime?


I embedded YAWS in my application at production environment, and I use the function yaws:start_embedded/4 to start YAWS.

Below is my code:

Id = "my_server",
GconfList = [{logdir, "./log"}, {id, Id}],  
SconfList = [{docroot, Docroot},
  {port, Port},
  {listen, Listen},
  {appmods, [      
    {"/rest", mod_rest, []},
    {"/file", mod_file, []}      
  ]}
],
yaws:start_embedded(Docroot, SconfList, GconfList, Id).

I'd like to add another appmod, e.g: {"/upload", mod_upload, []}

Is it possible to add appmods at runtime without restarting YAWS?


Solution

  • You can add appmods at runtime by first retrieving the current configuration, using it to create a new configuration containing your new appmods, and then setting the new configuration.

    1. Call yaws_api:getconf/0 to get a 3-tuple {ok, GlobalConf, ServerConfs} where GlobalConf is the global Yaws configuration and ServerConfs is a list of lists of Yaws server configurations. The global conf is a record type named gconf, and the server conf is a record type named sconf; both of these record types are defined in the yaws.hrl header file.
    2. Work through the server configurations to find the one containing the appmods you want to change. This is slightly tricky because you're dealing with a list of lists, and you need to keep the shape of the overall data structure unchanged.
    3. Once you find the sconf, create a new sconf instance from it by adding your new appmod to its current list of appmods. Each element of the appmod list is a tuple consisting of a URL path for the appmod and the name of the appmod module. An appmod tuple can also optionally contain a third field consisting of a list of paths under the first path to be excluded; see the description of exclude_paths in the Yaws appmod documentation for more details.
    4. Replace the existing sconf value in ServerConfs with your new value.
    5. Call yaws_api:setconf/2 to set the new configuration, passing the existing GlobalConf as the first argument and the new ServerConfs containing your new sconf as the second argument.

    The am_extend module below shows how to do this. It exports an add/1 function that takes a function that can identify and augment the appmods in the particular server you care about.

    -module(am_extend).
    -export([add/1]).
    
    add(AppmodAdder) ->
        {ok, GlobalConf, ServerConfs} = yaws_api:getconf(),
        NewServerConfs = add_appmod(ServerConfs, AppmodAdder),
        yaws_api:setconf(GlobalConf, NewServerConfs).
    
    add_appmod(ServerConfs, AppmodAdder) ->
        lists:foldl(fun(Val, Acc) ->
                            Acc ++ [AppmodAdder(A) || A <- Val]
                    end, [], ServerConfs).
    

    An example of using this code is to pass the function below as the AppmodAdder argument for am_extend:add/1. For this example, we're looking for a server that has an appmod path "/sse" so we can add another appmod to that server for the path "/sse2". Any server conf we don't care about is just returned unchanged.

    -include_lib("yaws/include/yaws.hrl").
    
    add_sse2(#sconf{appmods=AM}=SC) ->
        case lists:keyfind("/sse", 1, AM) of
            false ->
                SC;
            _ ->
                SC#sconf{appmods=[{"/sse2", my_sse_module}|AM]}
        end.
    

    Note that our add_sse2/1 function must be compiled with yaws.hrl included so it has the definition for the sconf record available.