Last minute changes

This commit is contained in:
Daniel Flanagan 2022-03-02 10:24:47 -06:00
parent 0d1068226a
commit ab0ede644a
Signed by: lytedev
GPG key ID: 5B2020A0F9921EF4
4 changed files with 222 additions and 54 deletions

26
Dockerfile Normal file
View file

@ -0,0 +1,26 @@
FROM elixir:1.13.2-slim
RUN apt-get update && \
apt-get -y --no-install-recommends install git curl ca-certificates && \
apt-get autoremove -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists
# install livebook
RUN mix local.hex --force && mix local.rebar --force
RUN mix escript.install --force github livebook-dev/livebook
ENV LIVEBOOK_IP 0.0.0.0
ENV LIVEBOOK_HOME /app
ENV LIVEBOOK_PORT 5588
ENV LIVEBOOK_IFRAME_PORT 5589
EXPOSE 5588/tcp
EXPOSE 5589/tcp
WORKDIR /app
ADD . /app
RUN mix do deps.get, compile
CMD ["/root/.mix/escripts/livebook", "server", "--default-runtime", "mix", "--no-token"]

View file

@ -0,0 +1,101 @@
defmodule RanchTalk.SocketMessageDisplayer do
use Kino.JS
use Kino.JS.Live
@spec new() :: Kino.JS.Live.t()
def new() do
Kino.JS.Live.new(__MODULE__, [])
end
@impl true
def init(messages, ctx) do
{:ok, assign(ctx, messages: messages)}
end
@impl true
def handle_info({:form_message, %{data: data}}, ctx) do
data = Map.put(data, :date, DateTime.utc_now())
broadcast_event(ctx, "add_message", data)
new_messages = [data | ctx.assigns.messages]
{:noreply, assign(ctx, messages: new_messages)}
end
@impl true
def handle_cast({:subscribe_to_form, form}, ctx) do
subscribe_to_form(form)
{:noreply, ctx}
end
@impl true
def handle_connect(ctx) do
IO.inspect(ctx)
# subscribe_to_form(ctx.assigns.form)
{:ok, ctx.assigns, ctx}
end
defp subscribe_to_form(form) do
Kino.Control.subscribe(form, :form_message)
end
asset "main.js" do
"""
var cache = [
'',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' '
]
function leftPad(str, len, ch) {
str = str + ''
len = len - str.length
if (len <= 0) return str
if (!ch && ch !== 0) ch = ' '
ch = ch + ''
if (ch === ' ' && len < 10) return cache[len] + str
var pad = ''
while (true) {
if (len & 1) pad += ch
len >>= 1
if (len) ch += ch
else break
}
return pad + str
}
function dateStr(date) {
console.log("before str", date)
const hours = leftPad(date.getHours().toString(), 2, '0')
const minutes = leftPad(date.getMinutes().toString(), 2, '0')
const seconds = leftPad(date.getSeconds().toString(), 2, '0')
return `${hours}:${minutes}:${seconds}`
}
export function init(ctx, state) {
const header = document.createElement("h1")
header.textContent = "Received Messages:"
ctx.root.appendChild(header)
const messages = document.createElement("ul")
messages.style.fontFamily = 'IosevkaLyte, monospace'
messages.style.fontSize = '16px'
ctx.root.appendChild(messages)
ctx.handleEvent("add_message", ({name, message, date}) => {
console.log("Raw Date", date)
const messageDate = new Date(date)
console.log("Message Date", messageDate)
const messageEl = document.createElement("li")
messageEl.textContent = `${dateStr(messageDate)} ${name}: ${message}`
messages.appendChild(messageEl)
})
}
"""
end
end

View file

@ -27,7 +27,7 @@ alias RanchTalk.TalkTimer
## Ranch Introduction ## Ranch Introduction
> Special thanks to Cody Poll for the excuse to waste a ton of time playing > Special thanks to Cody Poll for the excuse to waste a ton of time playing
> around with Livebook! > around with Livebook! Source for this talk is available [here](https://git.lyte.dev/lytedev/ranch-talk)
From https://ninenines.eu/docs/en/ranch/2.1/guide/introduction/: From https://ninenines.eu/docs/en/ranch/2.1/guide/introduction/:
@ -71,7 +71,7 @@ loop look _very loosely_ like this now:
1. Open a socket 1. Open a socket
2. Check if any pending connections exist on the socket 2. Check if any pending connections exist on the socket
3. If there are any pending connections, add them to our list of connections 3. If there are any pending connections, add them to our list of connections
- This is that "acceptor" part that Ranch takes care of for us * This is that "acceptor" part that Ranch takes care of for us
4. For each active connection, check if the connection has any messages 4. For each active connection, check if the connection has any messages
5. If the connection has any messages, handle them 5. If the connection has any messages, handle them
6. For each active connection, check if the connection is still active 6. For each active connection, check if the connection is still active
@ -85,6 +85,8 @@ synchronously reaching out to a cache, then querying a database, or calling
another service. It would be stuck waiting for all of these operations while another service. It would be stuck waiting for all of these operations while
new messages pour into your socket! new messages pour into your socket!
Let's look at a visual example... in Factorio!
### How does Ranch solve this? ### How does Ranch solve this?
Well, let's imagine _you_ are the poor, single-threaded program taking care of Well, let's imagine _you_ are the poor, single-threaded program taking care of
@ -92,7 +94,7 @@ all this stuff. You're running around like mad from the OS socket, to the
connection list, shuffling messages all over the place, **and** you're connection list, shuffling messages all over the place, **and** you're
responsible for reading every single one, processing it, and responding. responsible for reading every single one, processing it, and responding.
Obviously, so modern web framework or socket library works this way for obvious Obviously, no modern web framework or socket library works this way for obvious
reasons. You (or your machine) would be completely overwhelmed! reasons. You (or your machine) would be completely overwhelmed!
But this is where Ranch (and Erlang/OTP and Elixir) really shine. But this is where Ranch (and Erlang/OTP and Elixir) really shine.
@ -140,35 +142,39 @@ documentation to see how it works so we know better what to look for. Don't
worry, I'm not really going to make you read documentation yourself during worry, I'm not really going to make you read documentation yourself during
a talk, so I've summarized the important stuff we'll look at below: a talk, so I've summarized the important stuff we'll look at below:
- https://ninenines.eu/docs/en/ranch/2.1/guide/introduction/ <!-- livebook:{"break_markdown":true} -->
- Just the stuff we've already talked about (minus all the boring socket
* https://ninenines.eu/docs/en/ranch/2.1/guide/introduction/
* Just the stuff we've already talked about (minus all the boring socket
detail stuff) detail stuff)
- https://ninenines.eu/docs/en/ranch/2.1/guide/listeners/ * https://ninenines.eu/docs/en/ranch/2.1/guide/listeners/
- We start Ranch by adding the dependency and running * We start Ranch by adding the dependency and running
[`:application.ensure_all_started(:ranch)`](https://ninenines.eu/docs/en/ranch/2.1/manual/) [`:application.ensure_all_started(:ranch)`](https://ninenines.eu/docs/en/ranch/2.1/manual/)
- We can start a listener with * We can start a listener with
[`:ranch.start_listener/5`](https://ninenines.eu/docs/en/ranch/2.1/manual/ranch.start_listener/) [`:ranch.start_listener/5`](https://ninenines.eu/docs/en/ranch/2.1/manual/ranch.start_listener/)
- https://ninenines.eu/docs/en/ranch/2.1/guide/internals/ * https://ninenines.eu/docs/en/ranch/2.1/guide/internals/
- Ranch is an OTP `Application` (named `:ranch`) * Ranch is an OTP `Application` (named `:ranch`)
- It has a "top `Supervisor`" which supervises the `:ranch_server` process * It has a "top `Supervisor`" which supervises the `:ranch_server` process
_and_ any listeners _and_ any listeners
- Ranch uses a "custom `Supervisor`" for managing connections * Ranch uses a "custom `Supervisor`" for managing connections
- Listeners are grouped into the `:ranch_listener_sup` `Supervisor` * Listeners are grouped into the `:ranch_listener_sup` `Supervisor`
- Listeners consist of three kinds of processes: * Listeners consist of three kinds of processes:
- The listener `GenServer` * The listener `GenServer`
- A `Supervisor` that watches the acceptor processes * A `Supervisor` that watches the acceptor processes
- The second argument to `:ranch/start_listener/5` indicates the number * The second argument to `:ranch/start_listener/5` indicates the number
of processes that will be accepting new connections and we should be of processes that will be accepting new connections and we should be
careful choosing this number careful choosing this number
- It defaults to `100` * It defaults to `100`
- A `Supervisor` that watches the connection processes * A `Supervisor` that watches the connection processes
- Each listener is registered with the `:ranch_server` `GenServer` * Each listener is registered with the `:ranch_server` `GenServer`
- All socket operations go through "transport handlers" * All socket operations go through "transport handlers"
- These are simple callback modules (`@behaviour`s) for performing * These are simple callback modules (`@behaviour`s) for performing
operations on sockets operations on sockets
- Accepted connections are given to "the protocol handler" (just TCP for our * Accepted connections are given to "the protocol handler" (just TCP for our
use case) use case)
<!-- livebook:{"break_markdown":true} -->
Sweet! Armed with this knowledge, we should be able to find evidence of these Sweet! Armed with this knowledge, we should be able to find evidence of these
facts in our system _right now_. Let's do it! facts in our system _right now_. Let's do it!
@ -198,10 +204,10 @@ Now, if you selected the Livebook node and NOT the empty shell-of-a-node that
is the attached Mix project (you should switch to the correct one now!), you is the attached Mix project (you should switch to the correct one now!), you
will see that `:ranch_server` monitors a couple of `Supervisor`s: will see that `:ranch_server` monitors a couple of `Supervisor`s:
- `:ranch_conns_sup` * `:ranch_conns_sup`
- `:ranch_listener_sup` * `:ranch_listener_sup`
- `:ranch_conns_sup` * `:ranch_conns_sup`
- `:ranch_listener_sup` * `:ranch_listener_sup`
Awesome! We can see the connection `Supervisor` and listener `Supervisor` for Awesome! We can see the connection `Supervisor` and listener `Supervisor` for
port `5588` and likewise for port `5589`. The former for serving the page port `5588` and likewise for port `5589`. The former for serving the page
@ -216,14 +222,11 @@ processes under `Monitors` hanging out waiting for connections. What gives?
Yeah, I dunno. Maybe somebody in the audience knows why they aren't monitored Yeah, I dunno. Maybe somebody in the audience knows why they aren't monitored
(or at least why they don't show up here). (or at least why they don't show up here).
But if you go to [the Processes page](/dashboard/processes) and `Ctrl-F ":ranch_acceptor.loop"` you will see exactly 200 results. But if you go to [the Processes page](/dashboard/processes) and search for `:ranch_acceptor.loop`, you will see exactly 200 results. Proof!
Ok, this is cool and all, and if we had time, we could look at this in the Let's see how this plays out visually, yes, again, in Factorio! Then I'll show you how to use Ranch in the code below and we'll see if we can explore what it does.
Observer from pretty much any `iex` session like so:
```elixir <!-- livebook:{"break_markdown":true} -->
:observer.start()
```
But we're all getting impatient to build our own Ranch. It won't have horses on But we're all getting impatient to build our own Ranch. It won't have horses on
it, but it'll have something even better. TCP sockets! it, but it'll have something even better. TCP sockets!
@ -231,7 +234,7 @@ it, but it'll have something even better. TCP sockets!
## Building Your Own Ranch in 30 Seconds ## Building Your Own Ranch in 30 Seconds
My apologies to all the folks that built real ranches over much longer periods My apologies to all the folks that built real ranches over much longer periods
of time and with far fewer TCP sockets to show for it. of time and with far fewer TCP sockets to show for it. I hope this isn't too upsetting!
```elixir ```elixir
Application.ensure_all_started(:ranch) Application.ensure_all_started(:ranch)
@ -241,23 +244,30 @@ Man, being able to take advantage of Open Source contributors' work is really
hard work. That was so easy! Now let's start accepting some TCP connections! hard work. That was so easy! Now let's start accepting some TCP connections!
```elixir ```elixir
socket_message_displayer = RanchTalk.SocketMessageDisplayer.new()
defmodule EchoHandler do defmodule EchoHandler do
def start_link(ref, transport, opts) do def start_link(ref, transport, opts) do
pid = spawn_link(__MODULE__, :init, [ref, transport, opts]) pid = spawn_link(__MODULE__, :init, [ref, transport, opts])
{:ok, pid} {:ok, pid}
end end
def init(ref, transport, _opts \\ []) do def init(ref, transport, opts) do
{:ok, socket} = :ranch.handshake(ref) {:ok, socket} = :ranch.handshake(ref)
loop(socket, transport) loop(socket, transport, ref, opts)
end end
defp loop(socket, transport) do defp loop(socket, transport, ref, opts) do
case transport.recv(socket, 0, 5000) do case transport.recv(socket, 0, 5000) do
{:ok, data} -> {:ok, data} ->
IO.inspect(data) displayer = Keyword.get(opts, :displayer)
transport.send(socket, data) transport.send(socket, data)
loop(socket, transport)
if displayer do
RanchTalk.SocketMessageDisplayer.add_message(displayer, data)
end
loop(socket, transport, ref, opts)
_ -> _ ->
:ok = transport.close(socket) :ok = transport.close(socket)
@ -265,9 +275,11 @@ defmodule EchoHandler do
end end
end end
:ranch.start_listener(:tcp_echo, :ranch_tcp, %{socket_opts: [port: 5555]}, EchoHandler, []) :ranch.start_listener(:tcp_echo, :ranch_tcp, %{socket_opts: [port: 5555]}, EchoHandler,
displayer: socket_message_displayer
)
# Ranch Complete socket_message_displayer
``` ```
Ooh, _now_ if we look in our dashboard (at the non-Livebook node) we can see Ooh, _now_ if we look in our dashboard (at the non-Livebook node) we can see
@ -279,17 +291,35 @@ to it and see if it really does echo back to us! You can use `nc` (netcat),
`telnet`, or we can use Erlang's `:gen_tcp` like so: `telnet`, or we can use Erlang's `:gen_tcp` like so:
```elixir ```elixir
{:ok, socket} = :gen_tcp.connect({127, 0, 0, 1}, 5555, [:binary, active: true]) defmodule MyRanchClient do
``` def send_message(message) do
{:ok, socket} = :gen_tcp.connect({127, 0, 0, 1}, 5555, [:binary, active: true])
See how `:gen_tcp` returns `{:ok, #Port<...>}`? A `Port` is a special :gen_tcp.send(socket, [
Erlang/OTP-ism we can learn about another time. For now, should have got us to_string(DateTime.utc_now()),
a connection! Let's send something. " ",
inspect(socket),
": ",
message
])
**NOTE**: If you don't hurry and send the message, the TCP socket will be closed due to your inactivity. Better act fast! :gen_tcp.close(socket)
end
end
```elixir form =
:gen_tcp.send(socket, "Hello, socket! " <> to_string(DateTime.utc_now())) Kino.Control.form(
[
name: Kino.Input.text("Name", default: "Anonymous"),
message: Kino.Input.text("Message", default: "")
],
submit: "Send Message",
reset_on_submit: [:message]
)
Kino.JS.Live.cast(socket_message_displayer, {:subscribe_to_form, form})
form
``` ```
And if we got `:ok`, this `Process` should have a message in its And if we got `:ok`, this `Process` should have a message in its

View file

@ -11,18 +11,29 @@ Thanks to [Divvy][divvy] for inviting me to give this talk.
# Usage # Usage
Install and run a local Livebook in `attached` mode and automatically grab my
code:
```bash ```bash
asdf install asdf install
mix escript.install github livebook-dev/livebook mix escript.install github livebook-dev/livebook
git clone https://git.lyte.dev/lytedev/ranch-talk.git git clone https://git.lyte.dev/lytedev/ranch-talk.git
cd ranch-talk cd ranch-talk
mix do deps.get, compile mix do deps.get, compile
```
Install and run a local Livebook in `attached` mode and automatically grab my
code:
```fish
env LIVEBOOK_PORT=5588 LIVEBOOK_IFRAME_PORT=5589 \ env LIVEBOOK_PORT=5588 LIVEBOOK_IFRAME_PORT=5589 \
livebook server --default-runtime mix \ LIVEBOOK_HOME=(pwd) LIVEBOOK_IP=0.0.0.0 \
"$(pwd)/ranch-talk.livemd" livebook server --default-runtime mix --no-token
```
Or if you're gonna share this with everybody, it's probably safer to run it
inside of Docker:
```bash
docker build . --tag ranch-talk
docker run -it --rm -p 5588:5588 -p 5589:5589 ranch-talk
``` ```
Enjoy! Enjoy!