N2O   


 Erlang Web Stack

Maxim Sokhatsky

Synrc Research Center

Why N2O?





Micro REST



Python Flask

Ruby Sinatra

PHP Silex

  Scala Scalatra  

Concurrency in Mind



Ruby Celluloid

PHP React

PHP phpDaemon

  Java+Scala Play Akka  

SPA



Angular Meteor Ember

Chaplin Brunch

 Knockout    React    D3

Backbone   jQuery

Functional DSL



Scala Lift

   Erlang N2O   

Haskell BlazeHtml

OCaml Ocsigen

F# WebSharper

  Clojure  



Laser Enlive

Compojure Ring

Hiccup ClojureScript Om

http-kit aleph noir

JVM


Elixir  Weber 



              def action(_, conn) do
                     {:render, [project: "simpleTodo"], []}
              end
 
    def add([body: body], _conn) do
        {:json, [response: "ok"], [{"Content-Type", "application/json"}]}
    end
 
                  def wrong(_, _) do
                         {:redirect, "/"}
                  end

Erlang  ChicagoBoss 




DTL Engine

Database Connectivity

PubSub

Ruby on Rails like


 Nitrogen 

 N2O  ~2000 LOC 


One Process per Connection

Binary Page Construction 

Zero Bridge 

GProc Pub/Sub 

WebSockets, KVS DB, Shen

Tuned Layers



static and dynamic Routing Path
cleaned Query Parser
Session Cookies stored in ETS
cached DTL Templates
optimized Nitrogen DSL rendering
HTML Elements binaries
JavaScript Actions deferred

Layers Performance


                 components                TCP conn (K)
     PHP5 FCGI   Simple Script <?php ?>          5     
      Nitrogen   No session, No DSL, DTL         1   
           N2O   Sessions, DSL, DTL              7
           N2O   Sessions, no DSL, DTL           10
           N2O   No Sessions, no DSL, DTL        13 

                               On same machine 
             raw webserver performance
                              measured with wrk:   NGINX -- 60K 
                                                
                        Cowboy -- 30K

Measuring Tools


           requests     pages/sec    latency (ms) 
     wrk       15K      13628.86      18.88   
      ab       10K       5464.63     190  
 httperf       10K       3623.50     200 
   siege        1K        884.51     430 


                               On same machine 
             raw webserver performance
                              measured with wrk:   NGINX -- 60K 
                                               
                   Cowboy -- 30K

Decreasing Latency

From 2x to 


Deliver HTML ASAP

 Deferred JavaScript delivery

after  WebSocket

 connection established

<script> TransitionProcess = '<0.7780.5>' </script> socket.send(["N2O",TransitionProcess]).

Dynamic HTML  cow 


Static HTML  nginx 


 SPA  and  DTL 


Render both HTML and JavaScript using DSL :

#button postback loginsource userpass ] }

Or render only JavaScript ..

#button render = script,
                    id = ElementId,
                        postback login
                                  source userpass ] }

.. when you develop SPA

Render


<input value="Login" id= "temp563149" type="button"/>

$( '#temp563149' ).bind( 'click' ,
     function anonymous(event)
         ws.send(Bert.encodebuf({
              source: Bert.binary( 'temp563149' ),
              pickle: Bert.binary('g2gCaINoAmgEZAA...'),
                linked: [ Bert.tuple(Bert.atom( 'user' ),
                                       utf8.toByteArray($( '#user' ).val())),
                               Bert.tuple(Bert.atom( 'pass' ),
                                       utf8.toByteArray($( '#pass' ).val())) ]
      })); });

Elements


Plug  your JavaScript controls


                  
           
body() -> [ 
#textboxlist { },
                                                  #grid { },
                  
                  
              #diagram { },
                  
                  
              
#histogram { },

                  
                  
              #monitor { } ].

and talk with them using BERT

 through WebSockets

Actions

Client Updates are sent as Actions

which are evaluated as JavaScript strings


render_action(Record) -> 
        wf:f("alert(\"~s\");",  [wf:js_escape(Record#alert.text)]). 

wf:wire(#alert{text="Hello, World!"}).


Element render could create Actions 

Action could include rendered Elements

Triggered Action may send Events

JS Compiler  200 LOC 

Preserves Semantics


-compile({parse_transform,shen}).

M = lists:foldl(fun(X, Acc) -> Acc + X end, 0, [1,2,3,4]),


$ erlc sample.erl


  m = [1,2,3,4].reduce( function(x,acc) { return acc + x; }, 0);

1. Human Readable Output 

2. No Runtimes 

3. Strong Dynamic Typing 

4. Compact Language, Sane Syntax


   Synrc Shen, JScala                      [1,2,3,4] 
   CoffeeScript, LiveScript                [1,2,4]
   TypeScript                              [1,2,3] 
   Ur, Fay, Roy, Elm, Haxe, F*, WebSharper [3,4]
   ClojureScript                           [4]
   OberonJS                                [1,3,4]
   Wisp                                    [1,2,4]

Why  Erlang to JS  ?



Validation Rules 

Business Rules

Prototyping

Static Content Generation 

Single Environment



SPA  React  ERL


    User = #react{
        render = fun(This) -> #h1{body=value(email,This)} end
            },

    CommentList = #react{
        props = [{data,[]}],
        render = fun(This) -> 
            Users = lists:map(fun(Item) ->
                User#react{props=Item} end, value(data,This)),
            Users
        end },

SPA  React  JS


var user = React.createClass({
        render: function() {
                return React.DOM.h1(null,value('email',this));
       }});

var commentlist = React.createClass({props: {data:[]},
       render: function() {
       var users = value('data',this).map(function(item) {
               return user({props: item});
       });
                return users;
       }});

 Erlang AST  CMPTBL



Brace (Joxa /w Erlang AST)

ElixirErlang


Synrc Shen JavaScript Compiler

can handle each Erlang language that

compiles to Erlang AST*

 DTL  Sample


main () ->
    Body = wf : render ( body () ) ,
    [ #dtl { file  "login"
                 bindings = [ { body , Body } ] } ].

body () ->
    [  #span { text  = "Login: " },
       #textbox  { id  = user },
       #span  text  "Pass: "  },
       #password  id  = pass },
       #button 
text  = "Login" postback  login , source  [ user , pass ] } ].

event ( login ) ->  wf : user ( wf : q ( user ) ), wf : redirect ( ).

 Idiomatic  Chat  


-module ( chat ).
event (chat) -> wf:send (chat, wf:q (message) ),

body () -> 
    wf:async ( fun() -> loop() end, chat ),
   [ #panel { id=history }, #textbox { id=message }, 
                 #button { postback=chat } ].

loop () -> 
    receive Message -> 
         wf:insert_bottom history, #span { text=Message } ),
          wf:flush () end, loop ().

N2O in  Elixir 

defmodule Sample.Index do
    def event(:init) do :wf.reg(:room) end
    def event({:chat,pid}) do pid <- {:message, :wf.q(:message)} end
    def event(e) do :wf.info('Unknown Event ~p',[e]) end
    def main() do :dtl.new(file: "index", bindings: [title: "Elixir N2O", body: body()]) end
    def body() do { :ok, pid } = :wf.async(fn() -> loop() end)
                  [ :div.new(id: :history),  
                    :textbox.new(id: :message), 
                    :button.new(id: :send, body: "Chat", postback: {:chat,pid}, source: [:message]) ] end

    def loop() do receive do
            {:message, message} ->   :wf.insert_bottom(:history, [ :span.new(body: message), :br.new() ])
                :wf.flush(:room)
            unknown -> :wf.info('Unknown Looper Message ~p',[unknown]) end
        loop() end  end

N2O in  Joxa 


(ns lispchat (require erlang joxa-core n2o wf))
(defn+ body() [(n2o/panel   {:id :history})
               (n2o/textbox {:id :message})
               (n2o/button  {:id :send}
                            {:body "Chat"}
                            {:postback :chat}
                            {:source [:message]}) ])
(defn+ main() [(n2o/dtl {:app :n2o_sample}
                        {:file "index"}
                        {:bindings [{:title "Title"} {:body (body)}]})])
(defn+ speak(m) (wf/insert_bottom :history (n2o/span {:body m})))
(defn+ event(body)
    (case body
        (:init (speak ["Hello" (n2o/br)]))
        (:chat (speak [(wf/q :message) (n2o/br)]))))
(defn+ test() (do 
                (event :init)
                (wf/info "hello ~p~n" [(main)])))

 JSON 



proplists <-> JSON 243x


   Library                   Type    Enc    Dec
   -------------             ------- ------ -------
   jsonx                     C NIF   1048   998
   jiffy                     C NIF   2652   2234
   yaws2                     Erlang  14208  12653  Xen 
   jsonerl                   Erlang  14549  14899
   mochijson2                Erlang  16787  16512
   jsx                       Erlang  27188  18333

 JSON PT 


proplists <=> #rec{}

        

-module(users).
-behaviour(n2o_rest).
-compile({parse_transform, n2o_rest}).
-include("users.hrl").
-rest_record(user).

from_json(Proplist, #user{}) -> #user{}.
to_json(Record) -> proplist().

 REST  Sample


-compile({parse_transform, n2o_rest}).
-include("users.hrl").
-rest_record(user).

init() -> ets:new(users, [public, named_table, {keypos, #user.id}]).
populate(Users) -> ets:insert(users, Users).
exists(Id) -> ets:member(users, wf:to_list(Id)).
get() -> ets:tab2list(users).
get(Id) -> [User] = ets:lookup(users, wf:to_list(Id)), User. 
delete(Id) -> ets:delete(users, wf:to_list(Id)).
post(#user{} = User) -> ets:insert(users, User);
post(Data) -> post(from_json(Data, #user{})).

 KVS  REST API


   POST/PUT kvs:add(#user{}).

         DELETE kvs:remove(user,2).

PUT/PATCH kvs:put(#user{id=2}).

                GET kvs:entries(Feed,user).

                GET kvs:get(user,2).

                GET kvs:all(user).

Events


From JavaScript 

Everything is sent as an Event


               Postback event/1

                    Control control_event/2

                            API api_event/3

Handle events in Erlang...

Events Nature


Events are enveloped by BERT

as #ev{}

-record ( ev,
               { name :: api_event | control_event | event | atom () ,
                  payload ,
                  trigger ,
                  module :: atom () } ).


You can define your own entry points

Postback Events


Create Page Logic with

Postback Events event/1


main() -> [ #button  {  postback =  login } ] .

event(login) -> wf:info("Logged User: ~p", [wf:user()]).


Control Events


Handle Data from Controls with

Control Events control_event/2


render_element( E = #myelement { } ) -> do_render( E ).

control_event(Trigger, Data) -> 
      wf:info("Received Data ~p from JavaScript Control ~p",
             [ Data, Trigger ] ).

... when you cteate your own Elements

API Events


Wire Erlang and JavaScript with

API Events api_event/3


main() -> wf:wire( #api { name = notify, tag = api3  } ), [ ].

api_event(Event, Args, Req) ->
        wf:info( "Received from JavaScript: ~p", [ Args ] ).

                                         document.notify('Hello from JavaScript').

  Community  



Sources

  https://github.com/5HT/n2o  


HTML and TeX Handbook


IRC FreeNode

  #N2O  

Erlang Web Stack 2014

By Maxim Sokhatsky

Erlang Web Stack 2014

N2O is optimized version of Nitrogen, well known Erlang Web Framework. But instead of Nitrogen, N2O is very fast, based on WebSockets protocol, supports REST handlers and works tightly coupled with Cowboy -- the fastest Erlang Web Server. Here is brief info about N2O and causes of its speed.

  • 38,692
Loading comments...

More from Maxim Sokhatsky