<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>A tale of 2.0 Cowboy</title>
<meta name="description" content"Cowboy 2 EUC 2017 talk">
<meta name="author" content="Loïc Hoguin">
<link rel="stylesheet" href="css/reveal.css">
<link rel="stylesheet" href="css/theme/black.css">
</head>
<body>
<div class="reveal">
<div class="slides">
<section>
<h1>A tale of 2.0 Cowboy</h1>
<p>I'm a poor lonesome Cowboy,<br/>and a long way from home...</p>
<p><small>Loïc Hoguin (<a href="https://twitter.com/lhoguin">@lhoguin</a>), <a href="https://ninenines.eu">Nine Nines</a></small></p>
</section>
<section>
<h1>Confessions</h1>
</section>
<section>
<section>
<h2>Slow and steady</h2>
<img src="img/turtle-racing.jpg"/>
</section>
<section>
<h2>Life happened</h2>
<p>Literally</p>
</section>
<section>
<ul>
<li>Modifying an existing project is hard</li>
<li>Starting from scratch is easy (at first)</li>
</ul>
</section>
<section>
<h2>Many changes</h2>
<ul>
<li>Project core changed the most</li>
<li>Interface received numerous tweaks</li>
<li>New testing methodology</li>
<li>More detailed documentation</li>
</ul>
</section>
<section>
<h2>HTTP is complicated</h2>
<p>HTTP/2 even more so</p>
</section>
</section>
<section>
<h1>Lessons learned</h1>
</section>
<section>
<section>
<h2>Active supervisors</h2>
<img src="img/river-delta.jpg"/>
</section>
<section>
<ul>
<li>One process... <ul>
<li>Tells another process... <ul>
<li>To start a third process</li>
</ul></li>
</ul></li>
</ul>
</section>
<section>
<h3>Solutions</h3>
<ul>
<li>Let the supervisor act</li>
<li>Make worker processes supervise</li>
</ul>
</section>
<section>
<h3>Example: acceptor supervisor</h3>
<ul>
<li>Basically a supervisor with an <code>info/3</code> callback <ul>
<li>Accept connection</li>
<li>Spawn a process for that connection</li>
<li>Supervise the process</li>
</ul></li>
<li>Coming in Ranch 2.0</li>
</ul>
</section>
<section>
<h3>Example: connection workers</h3>
<ul>
<li>Process manages connection and related workers</li>
<li>Connection and request processes in Cowboy 2.0</li>
<li>Many implications</li>
</ul>
</section>
</section>
<section>
<section>
<h2>List of commands</h2>
<img src="img/to-do-list.png"/>
</section>
<section>
<pre><code data-trim data-noescape>
{[
{headers, fin, 100, Headers},
{flow, 64000},
{spawn, Pid, 5000}
], State}.
</code></pre>
</section>
<section>
<h3>Examples</h3>
<ul>
<li><code>gen_statem</code></li>
<li>Cowboy 2.0 stream handlers</li>
<li>Cowboy 2.1+ Websocket handlers</li>
</ul>
</section>
<section>
<pre><code data-trim data-noescape>
init(StreamID, Req, Opts) → {Commands, State}
data(StreamID, IsFin, Data, State) → {Commands, State}
info(StreamID, Msg, State) → {Commands, State}
terminate(StreamID, Reason, State) → _
early_error(StreamID, Reason, PartialReq, Resp, Opts) → Resp
</code></pre>
</section>
</section>
<section>
<section>
<h2>Callback chains</h2>
<img src="img/chain.jpg"/>
</section>
<section>
<pre><code data-trim data-noescape>
#{
middlewares ⇒
[cowboy_router, cowboy_handler],
stream_handlers ⇒
[cowboy_compress_h, cowboy_stream_h]
}
</code></pre>
</section>
<section>
<h3>Middleware</h3>
<pre><code data-trim data-noescape>
execute(Req0, Env0) →
case do_something(Req0, Env0) of
{ok, Req, Env} →
{ok, Req, Env};
error →
{stop, Req0}
end.
</code></pre>
</section>
<section>
<h3>Stream handler 1/2</h3>
<pre><code data-trim data-noescape>
data(StreamID, IsFin, Data, State=#state{next=Next0}) →
{Commands0, Next} = cowboy_stream:data(
StreamID, IsFin, Data, Next0),
fold(Commands0, State#state{next=Next}).
</code></pre>
</section>
<section>
<h3>Stream handler 2/2</h3>
<pre><code data-trim data-noescape>
data(StreamID, IsFin, Data0, State=#state{next=Next0}) →
Data = do_something(Data0),
{Commands0, Next} = cowboy_stream:data(
StreamID, IsFin, Data, Next0),
{Commands0, State#state{next=Next}}.
</code></pre>
</section>
</section>
<section>
<section>
<h2>Receive loop</h2>
<img src="img/mailboxes.jpg"/>
</section>
<section>
<pre><code data-trim data-noescape>
loop() →
receive
Msg →
do_something(Msg),
loop()
end.
</code></pre>
</section>
<section>
<h3>Loop handlers</h3>
<ul>
<li>Native support for server-sent events</li>
<li>Switch to loop handlers from <code>cowboy_rest</code></li>
<li>After Cowboy 2.0</li>
</ul>
</section>
</section>
<section>
<section>
<h2>Error 3-tuples</h2>
<img src="img/error-message.jpg"/>
</section>
<section>
<pre><code data-trim data-noescape>
{error, Reason}
Reason =
{connection_error,
protocol_error,
'The preface sequence must be followed
by a SETTINGS frame. (RFC7540 3.5)'}
</code></pre>
</section>
</section>
<section>
<section>
<h2>Options map</h2>
<img src="img/treasure-map.jpg"/>
</section>
<section>
<pre><code data-trim data-noescape>
uri(Req :: cowboy_req:req()) → uri(Req, #{})
uri(Req :: cowboy_req:req(), Opts) → URI :: iodata()
Opts :: #{
scheme ⇒ iodata() | undefined,
host ⇒ iodata() | undefined,
port ⇒ inet:port_number() | undefined,
path ⇒ iodata() | undefined,
qs ⇒ iodata() | undefined,
fragment ⇒ iodata() | undefined
}
</code></pre>
</section>
<section>
<pre><code data-trim data-noescape>
read_body(Req :: cowboy_req:req())
→ read_body(Req, #{})
read_body(Req :: cowboy_req:req(), Opts)
→ {ok, Data :: binary(), Req}
| {more, Data :: binary(), Req}
Opts :: #{
length ⇒ non_neg_integer(),
period ⇒ non_neg_integer(),
timeout ⇒ timeout()
}
</code></pre>
</section>
<section>
<h3>Required values</h3>
<p>Use <code>:=</code> instead of <code>=></code></p>
</section>
</section>
<section>
<section>
<h2>Better tests</h2>
<img src="img/bridge-collapsing.jpg"/>
</section>
<section>
<h3>Standards compliance</h3>
<p>For RFCs, at least one test per relevant MUST/SHOULD.</p>
<p>Document in the test suite what couldn't be tested.</p>
</section>
<section>
<h3>Features</h3>
<p>Every function, argument, option must have a test.</p>
</section>
<section>
<h3>Some tips</h3>
<ul>
<li>Unit tests are not interesting</li>
<li>Always run test cases in parallel</li>
<li>Short, successful tests must not output errors</li>
<li>Test cases must be documented</li>
</ul>
</section>
<section>
<img src="img/documented-tests.png"/>
</section>
</section>
<section>
<section>
<h2>Better docs</h2>
<img src="img/huge-library.jpg"/>
</section>
<section>
<p>Taking lessons from PHP</p>
<p><a href="https://ninenines.eu/docs/en/cowboy/2.0/manual/cowboy_req.read_body/">Example</a></p>
</section>
<section>
<h3>Highlights</h3>
<ul>
<li>One (man) page per module</li>
<li>One (man) page per function</li>
<li>Description, arguments, return value, changelog, examples...</li>
</ul>
</section>
</section>
<section>
<h1>Cowboy 2.0+</h1>
</section>
<section>
<section>
<h2>When?</h2>
<ul>
<li>Cowboy 2.0-rc.1: Summer 2017 <ul>
<li>After OTP 20 is released</li>
</ul></li>
<li>Cowboy 2.0: 2017</li>
<li>Requires Erlang/OTP 19+ (sorry!)</li>
</ul>
</section>
<section>
<h3>What's left?</h3>
<ul>
<li>Return multipart headers as a map</li>
<li>Constraints 2.0</li>
</ul>
</section>
</section>
<section>
<section>
<h2>New users</h2>
<ul>
<li>RabbitMQ (1.1, for now)</li>
<li>Zotonic</li>
<li>Barrel</li>
</ul>
</section>
</section>
<section>
<section>
<h2>Related projects</h2>
<ul>
<li>Ranch 1.4: Out now!</li>
<li>Gun 1.0: 2017</li>
<li>Cowlib 2.0+: Adding documentation</li>
<li>Erlang.mk: Rolling release, keep on rollin'</li>
<li>New project! Looking Glass</li>
</ul>
</section>
<section>
<h2>Looking Glass</h2>
<ul>
<li>Next-gen profiler for RabbitMQ</li>
<li><code>erl_tracer</code> NIF</li>
<li>LZ4 compression of trace files</li>
<li>Cachegrind output</li>
<li>Really, really fast</li>
<li><a href="https://github.com/rabbitmq/looking-glass">https://github.com/rabbitmq/looking-glass</a></li>
</ul>
</section>
</section>
<section>
<section>
<h2>Cowboy 2.1+</h2>
<ul>
<li>Features cut from 2.0 added back</li>
<li>More features (related to standards)</li>
<li>Improve interface with backward compatibility</li>
<li>Fix bugs, add tests, become a legend</li>
</ul>
</section>
<section>
<p>And eventually release Cowboy 3.0</p>
</section>
</section>
<section>
<h2>After Cowboy 2.0</h2>
<p>Finally build that REST framework</p>
<p>Make (the true) REST easily accessible</p>
</section>
<section>
<h2>Thank you in advance</h2>
<p><a href="https://ninenines.eu">ninenines.eu</a></p>
</section>
</div>
</div>
<script src="js/reveal.js"></script>
<script>
Reveal.initialize({
// controls: false,
progress: false,
history: true
});
</script>
</body>
</html>