N2O

INTRO

The n2o defines the way you create, configure and run arbitrary applications and protocols inside some hosts, into which N2O can be injected, such as cowboy and emqttd. Each application can spawn its instance in its way like web pages spawn WebSocket connections, workflow engines spawn business processes, and chat applications spawns roster and chatroom processes. With N2O everything is managed by protocols.

N2O shipped to work in two modes: 1) inside n2o_vnode workers; 2) inside cowboy processes, implemented in n2o_stream. In the first case, the MQTT server used between clients and server workers. In the second case, no more Erlang processes introduced except clients. You can create your configuration of N2O processing loop.

The N2O itself is an embeddable protocol loop in n2o_proto. However, besides that, it handles cache and sessions along with flexible n2o_async processes with no ownership restriction. It also introduces the logging approach, AES/CBC—128 pickling and BERT/JSON encoder.

TYPES

-type formatter() :: binary | json | bert | text | default | atom(). -type response() :: { formatter(), binary() }.
Listing 1. Erlang/OTP records
#ok { data = [] :: term() }. #error { data = [] :: term() }.
Listing 2. N2O Protocol
#reply { resp = [] :: [] | response(), req = [] :: [] | term(), state = [] :: [] | term() }. #unknown { data = [] :: [] | binary(), req = [] :: [] | term(), state = [] :: [] | term() }.
Listing 3. N2O State
#cx { session = [] :: [] | binary(), formatter = bert :: bert | json, actions = [] :: list(tuple()), state = [] :: [] | term(), module = [] :: [] | atom(), lang = [] :: [] | atom(), path = [] :: [] | binary(), node = [] :: [] | atom(), pid = [] :: [] | pid(), vsn = [] :: [] | integer() }).

PROTOCOL

While all application protocols in the system are desired to have single effect environment or same error handling path, n2o defines a single protocol loop for all applications as stack of protocols.

In core bundle n2o is shipped with NITRO and FTP protocols which allows you to create real-time web applications with binary-based protocols, also providing robust and performant upload client and file transfer protocol. For building web-based NITRO applications, you need to include nitro dependency.

info(term(),term(),#cx{}) -> #reply{} | #unknown{}.

The info/3 is an N2O protocol callback that to be called on each incoming request.

RPC MQTT

N2O provides RPC over MQ mechanism for MQTT devices. N2O spawns a set of n2o_vnode workers as n2o_async processes that listen to events topic. The responses send to actions topic, which is subscribed automatically on MQTT session init.

Listing 5. MQTT RPC Topics
actions/:vsn/:module/:client events/:vsn/:node/:module/:client

RPC WebSocket

In pure WebSocket case, N2O implements n2o_stream as cowboy module supporting binary and text messages.

Listing 6. Cowboy stream protocol
#binary { data :: binary() }. #text { data :: binary() }.

EXAMPLE

Here is an example of overriding INIT protocol.

Listing 7. Custom INIT Protocol
-module(custom_init). -include("n2o.hrl"). -export([info/3]). info({text,<<"N2O,",Pickle/binary>>}, Req, State) -> {'Token',Token} = n2o_session:authenticate([],Pickle), Sid = case n2o:depickle(Token) of {{S,_},_} -> S; X -> X end, New = State#cx{session = Sid}, {reply,{bert,{io,<<"console.log('connected')">>, {'Token',Token}}}, Req, New}; info(Message,Req,State) -> {unknown,Message,Req,State}.

CONFIG

Just put protocol implementation module name to protocol option in sys.config.

[{n2o,[{log_modules,[n2o]}, {log_level,info}, {logging,n2o_io}, {cache,n2o}, {erroring,n2o}, {upload,"priv/static"}, {mq,n2o_syn}, {ttl,900}, {timer,{0,1,0}} {tables,[cookies,file,caching,ring,async]}, {hmac,sha256}, {filename,n2o_ftp}, {formatter,n2o_bert}, {session,n2o_session}, {pickler,n2o_secret}, {protocols,[n2o_ftp,n2o_nitro]}, {nitro_prolongate,false}, {filter,{n2o_proto,push}}, {origin,<<"*">>}, {timer,{0,10,0}}]}].

N2O is the facade of the following services: cache, MQ, message formatting, logging, sessions, pickling and protocol loops. The other part of N2O is n2o_async module for spawning supervised application processes to use N2O API. In this simple configuration, you may set any implementation for any service.

The following configurable services are publically available in n2o module:

CACHE

Cache is a fast expirable memory store. Just put values onto keys using these functions and system timer will clear expired entries eventually. You can select caching module implementation by setting cache n2o parameter to the module name. Default n2o cache implementation turns each ets store into expirable.

cache(Tab, Key, Value, Till) -> term().

Sets a Value with a given TTL.

cache(Tab, Key) -> term().

Gets a Value.

MQ

The minimal requirement for any framework is the pub/sub API. N2O provides selectable API through mq environment parameter.

reg(term()) -> term().

Subscribe a current client to a transient topic. In particular implementation, the semantics could differ. In MQTT you can subscribe offline/online clients to any persistent topic. Also in MQTT this function subscribes MQTT client not an Erlang processe.

unreg(term()) -> term().

Unsubscribe a current client from a transient topic. In MQTT we remove the subscription from the persistent database.

send(term(), term()) -> term().

Publish a message to a topic. In MQTT if clients are offline, they will receive offline messages from the in-flight storage once they become online.

FORMAT

You specify the formatter in the protocol return message. E.g:

info({Code}, Req, State) -> {reply,{bert,{io,nitro:jse(Code),<<>>}}, Req, State};

encode(record()) -> binary().

Serializes a record.

decode(binary()) -> record().

Deserializes a record.

Here is an example of n2o_bert formatter implementation.

encode(Erl) -> term_to_binary(Erl). decode(Bin) -> binary_to_term(Bin,[safe]).

LOG

Listing 8. Log Framework
[{n2o,[{log_modules,[n2o]}, {log_level,info}]}]

In your code, you should use the following trace functions which are the same as callback API for n2o logging environment variable.

info(Module, Format, Args) -> ok | skip.

Use this function for info message.

warning(Module, Format, Args) -> ok | skip.

Use this function for warning messages.

error(Module, Format, Args) -> ok | skip.

Use this function for printing errors.

SESSION

Sessions are stored in issued tokens encripted with AES/CBC-128. All session variables are cached in ETS table in the default implementation n2o_session.

session(Key, Value) -> term().

Sets a value to session variable.

Listing 8. Sessions
1> rr(n2o). [bin,client,cx,direct,ev,flush,ftp,ftpack,handler, mqtt_client,mqtt_message,pickle,server] 2> put(context,#cx{}). undefined 3> n2o:session(user,maxim). maxim 4> ets:tab2list(cookies). [{{[],user},{63710014344,"maxim"}}, {{<<"5842b7e749a8cf44c920">>,auth},{63710014069,[]}]

session(Key) -> term().

Gets a value of session variable.

PICKLE

pickle(term()) -> binary().

Custom Erlang term serialization.

depickle(binary()) -> term().

Custom Erlang term deserialization.

This module may refer to: n2o_asynrc, n2o_auth, n2o_stream, n2o_vnode, n2o_proto.