Don't wanna be here? Send us removal request.
Text
Installing Cowboy and Nitrogen from Scratch

Installing Cowboy and Nitrogen from Scratch
In this post I’d like to share how I have installed nitrogen with cowboy and done so “from scratch.” Despite the fact that nitrogen comes with install scripts to do this work for us, it’s important to me to be able to understand the way that the entire system operates. I want to make sure that when something breaks I’ll be able to figure out how to fix it.
Installation steps.
Create an empty rebar3 project.
The first step is to create an empty project where we will install everything.
rebar3 new app basic_nitrogen_cowboy
Before we’re done with this step we can fill in the README.md and the names in the rebar3.config and scaffolded files.
Install the dependencies.
At this point it’s time to install cowboy and nitrogen.
% in rebar.config [ {deps, [ {cowboy, {git, "https://github.com/ninenines/cowboy.git", {branch, "master"}}}, {nitrogen_core, {git, "https://github.com/nitrogen/nitrogen_core.git", {branch, "master"}}} ]} ]
rebar3 deps
These are really the only two dependencies we’ll need to get started. While some related depdencies exist for nitrogen, such as qdate and sync, these are enhancements and we can skip them until we need them.
Configure cowboy.
If we already had cowboy running we could skip paste this work, but for a new project there’s very little we need to do to get running.
Configure a route for serving static files.
Configure a route for handoff to nitrogen.
% in basic_nitrogen_cowboy_app.erl start(_StartType, _StartArgs) -> Dispatch = cowboy_router:compile([ {'_', [ {"/static/[...]", cowboy_static, {priv_dir, basic_cowboy_nitrogen, "web_static"}}, {"/[...]", cowboy_simple_bridge_anchor, []} ]} ]),
The static files are likely common to any setup, but they are specifically required to hand out the nitrogen JavaScript and CSS files which run the interactive bits. The other route needs no complicated setup because simple_bridge does the heavy lifting.
Since we’re not running any custom handlers nitrogen is going to use the default mapping between path segments and Erlang modules. In practice I always prefer to write my own routing handler so I can have explicit control over what modules are called when serving requests.
Configure nitrogen.
There isn’t much to configure for nitrogen either, thankfully. We need to create a main handler to load any custom handlers, so that nitrogen will know to load them. This needs to be called nitrogen_main_handler and it needs to export run/0 where we have the chance to add our handlers with nitrogen:handler/2. We may also export ws_init/0 to add the same or a different set of handlers for new WebSocket connections.
-module(nitrogen_main_handler). -export([run/0]). run() -> % nitrogen:handler(...) calls here wf_core:run().
It’s possible to completely customize how nitrogen initializes each request, but the default behavior is sufficient for most cases. Either way, we need to specify what we want, and unlike nitrogen_main_handler, whatever module we specify needs to implement the simple_bridge_handler behavior. The default is nitrogen’s own nitrogen.erl. I’d normally add this to my sys.config, but to keep things simple in this demonstration I’ll set the built-in default manually before initializing the web server.
% in basic_nitrogen_cowboy_app.erl start(_StartType, _StartArgs) -> + application:set_env(simple_bridge, handler, nitrogen), Dispatch = cowboy_router:compile([ {'_', [ {"/static/[...]", cowboy_static, {priv_dir, basic_cowboy_nitrogen, "web_static"}} {"/[...]", cowboy_simple_bridge_anchor, []} ]} ]),
Notice how we leave off the _main_handler part of the module name. nitrogen will find it with just our custom prefix.
Add a static page.
Technically we should be done at this point. We have a working web server, even though it doesn’t have any pages yet. Let’s add one by creating index.erl.
-module(index). -export([main/0]). main() -> "hello, world!".
Nothing we’ve built so far is special; cowboy can already render static pages. But we want nitrogen specifically for the server-side client interactivity. If we try to wire up events, set postbacks, or add continuations now though they won’t work; we’re missing the JavaScript modules that make that all happen.
Add client-side interactivity scripts.
To add the scripts we need to copy them from the nitrogen_core dependency and into our static web root; then we need to render a <script> tag that instructs the browser to load them. In this guide we’re going to rely on the rebar3 post_hooks to copy these over when compiling our project.
% in rebar.config {post_hooks, [ {compile, mkdir -p ./priv/web_static/nitrogen}, {compile, cp -R ./_build/default/lib/nitrogen_core/www ./priv/web_static/nitrogen} ]}
With this script in there our nitogen static files will stay in sync. You can, of course, pick and choose wherever you want to save these files. I chose to use one called nitrogen for my own organizational purposes, but this isn’t essential.
What is essential is adding two kinds of scripts to the rendered pages.
The static nitrogen scripts.
The dynamic inline script that nitrogen writes to initialize the client-side interactivity.
-module(index). -export([main/0]). main() -> - "hello, world!". + "hello, world!", + #template {text=[ + "<script src=/static/nitrogen/jquery.js></script>", + "<script src=/static/nitrogen/jquery-ui.min.js></script>", + "<script src=/static/nitrogen/nitrogen.min.js></script>", + "<script src=/static/nitrogen/livevalidation.min.js></script>", + "<script src=/static/nitrogen/bert.min.js></script>", + ]}, + #template {text="[[[script]]]"}.
Finally, after all this work, we can make our page interactive and nitrogen is fully working and initialized. Obviously there’s more configuration and more work to do before everything is done, but this is all one needs to boot up a site.
Post-setup enhancements.
For each project I build where I incorporate nitrogen I try to install it ‟from scratch” like this so that I have a familiarity with how it works and so I can feel like I truly understand it. When I read ‟to install, run this script,” I come away with an impression that the setup is complicated and tedious, but it’s much easier than that, and so much of the complication is taking care of conviences that don’t matter too much to me. I hope you can be enriched by exploring this fresh-start approach.
For the most part there are only two more things I do before I feel like my nitrogen setup is ready to go:
Add the sync dependency to make interactive development more convenient. This automatically recompiles the Erlang modules I’m writing as I write them - ‟hot reloading.”
Create custom router, security, and other handlers to further customize how nitrogenworks. By default, nitrogen transforms a URL path into a lookup for an Erlang module, given a customizable prefix. This seems risky to me and I like knowing that people will only be able to view pages that I intended for them to see. Custom routing can be tedious, but that’s balanced by providing a pease of mind about security as well as providing a central dispatch-style map of the site, not to mention giving an opportunity to build more complicated routing rules for unique sections of the site.
Changes in upcoming nitrogen release.
The next version of nitrogen only seems to require two changes to what I wrote in this post:
The static assets are found at lib/nitrogen_core/priv/www instead of at lib/nitrogen_core/www
It’s no-longer required to manually start nitro_cache before starting nitrogen_corein the application configuration.
0 notes
Text
Spy on remote error messages
I have a server running on an old Android device. It recently started crashing on one of its web views and I wanted to take the opportunity to practice remote debugging. The server logs aren’t easily accessible to me because I don’t have an SSH server running on the phone, but I knew I should be able to see the crash logs through OTP’s built-in behaviors.
The caveat to what I’m about to share is that it’s dangerous and something you should only consider using in a small contained environment - not in production systems. Why? You could blow up your server.
Goal: redirect system logger output from running system to local development shell.
Like most systems, an Erlang system prints error logs to what is essentially stdout. That’s actually an oversimplification, but not wrong enough to derail this post. The problem is that when I connect my laptop to my old phone via starting an erl shell in distributed mode, I only see the error messages originating from my laptop. The web server crashes are still logging on my phone and unavailable to me.
This task turned out to be a little harder than I expected it to be because I didn’t know what I should be looking for and there are sparse docs for it.
In short, IO flows through a process’ group_leader, and that group leader is changeable, so if you swap group leaders for a remote process you can redirect its logs to anywhere you want.
Now given that I didn’t know what process was crashing on my phone I couldn’t efficiently do this by picking random posts off my system and inspecting their logs. I also wasn’t sure how to pick out my web server and watch its logs.
The solution is to connect a distributed Erlang node and then tell the remote node to swap the group leader for its logger_sup process.
-module(remote_logger).
-export([ activate/1, deactivate/2, remote_loader/1 ]).
activate(Node) -> {group_leader, LocalGL} = process_info(whereis(logger_sup), group_leader), {ok, OriginalGL} = rpc:call(Node, erlang, apply, [fun remote_loader/1, [LocalGL]]), {ok, OriginalGL}.
deactivate(Node, OriginalGL) -> {ok, PrevGL} = rpc:call(Node, erlang, apply, [fun remote_loader/1, [OriginalGL]]), {ok, PrevGL}.
remote_loader(RemoteGL) -> Sup = whereis(logger_sup), {group_leader, OriginalGL} = process_info(Sup, group_leader), group_leader(RemoteGL, Sup), logger:reconfigure(), {ok, OriginalGL}.
0 notes