Skip to content

Instantly share code, notes, and snippets.

@niku

niku/README.md Secret

Last active December 31, 2020 01:46
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save niku/c19da11edf0b97470af27844b44d12fa to your computer and use it in GitHub Desktop.
Save niku/c19da11edf0b97470af27844b44d12fa to your computer and use it in GitHub Desktop.
A tiny chat server built on Ractor

How to run

If you use Ruby3.0 or later

❯ ruby chat_server.rb

If you use before Ruby3.0

For bash or zsh

❯ ls chat_server.rb 
chat_server.rb

❯ docker run -v $(pwd):/app -p 8080:8080 -it wakaba260/ruby-ractor-dev ruby /app/chat_server.rb

For fish

❯ ls chat_server.rb 
chat_server.rb

❯ docker run -v (pwd):/app -p 8080:8080 -it wakaba260/ruby-ractor-dev ruby /app/chat_server.rb
require "socket"
class ChatServer
def initialize(port)
@port = port
@connections = []
end
def start()
socket = TCPServer.open(@port)
addr = socket.addr()
addr.shift()
puts "server is on #{addr.join(':')}"
acceptor = create_acceptor()
puts "acceptor created: #{acceptor.inspect}"
acceptor.send(socket, move: true)
periodic_timer = create_periodic_timer()
puts "periodic_timer created: #{periodic_timer.inspect}"
loop do
r, obj = Ractor.select(periodic_timer, acceptor)
case r
when acceptor
puts "Acceptor: #{obj}"
case obj
when Ractor
@connections << obj
when String
@connections.select! { |c| c.send(obj) rescue nil }
else
puts "warn: ignored event r=#{r}, obj=#{obj}"
end
when periodic_timer
puts "PeriodicTimer"
time = Time.now
@connections.select! { |c| c.send(time) rescue nil}
puts time
puts `vmstat`
puts "connections count: #{@connections.count}"
end
end
end
def create_periodic_timer()
Ractor.new do
loop do
Ractor.yield(nil)
sleep(60)
end
end
end
def create_acceptor()
Ractor.new do
tcp_server = Ractor.recv()
loop do
accept = tcp_server.accept
request = accept.readpartial(4096)
verb, path, _ = *request.split("\r\n").first.split(" ")
case verb
when "GET"
if(path == "/")
accept.print("HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n")
accept.print(<<__HTML__)
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body>
<form>
<input type="text" name="data">
<input type="submit" value="send">
</form>
<div id="messages"></div>
<script>
var source = new EventSource("/rooms/lobby"); // URL は / 以外ならなんでもいいけど将来にそなえてこうしてる
source.onmessage = function(event) {
document.getElementById("messages").innerHTML += event.data + "<br>";
};
var formElement = document.querySelector("form");
formElement.addEventListener('submit', function(event) {
event.preventDefault();
fetch("/rooms/lobby", {
method: "POST",
body: new FormData(formElement)
});
});
</script>
</body>
</html>
__HTML__
accept.flush()
accept.close()
else
r = Ractor.new do
c = Ractor.recv()
c.print("HTTP/1.1 200 OK\r\nCache-Control: no-cache\r\nContent-Type: text/event-stream\r\n\r\n")
c.flush()
loop do
data = Ractor.recv()
c.print("data: #{data}\r\n\r\n")
c.flush()
end
end
Ractor.yield(r)
r.send(accept, move: true)
end
when "POST"
accept.print("HTTP/1.1 202 Accepted\r\n")
accept.close()
body = request.split("\r\n\r\n").last.split("\r\n").first
Ractor.yield(body)
else
puts "warn: ignored request verb=#{verb}, path=#{path}"
end
end
end
end
end
ChatServer.new(8080).start()
@wallyqs
Copy link

wallyqs commented May 7, 2020

typo in the pwd command, (was missing $). Worked with:

curl -O -L https://gist.githubusercontent.com/niku/c19da11edf0b97470af27844b44d12fa/raw/91afc1b01dd5d7083965d7a718e3f1c09cafb7b0/chat_server.rb
docker run -v $(pwd):/app -p 8080:8080 -it wakaba260/ruby-ractor-dev ruby /app/chat_server.rb

@niku
Copy link
Author

niku commented May 7, 2020

Thanks!
Indeed, it needs $ prefix for bash or zsh.
I update README as you mentioned ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment