Networkd => Plumberd, websocketd working.
parent
550f4e77ae
commit
176d8d1507
34
README.md
34
README.md
|
@ -1,15 +1,15 @@
|
||||||
|
|
||||||
Networkd is a program to handle networking for all other software.
|
Plumberd is a program to handle networking for all other software.
|
||||||
|
|
||||||
# WARNING
|
# WARNING
|
||||||
|
|
||||||
Security is TBD. Currently, only TCPd is implemented, which means no communication security.
|
Security is TBD. Currently, only TCPd is implemented, which means no communication security.
|
||||||
|
|
||||||
# Networkd functionalities
|
# Plumberd functionalities
|
||||||
|
|
||||||
## firewall
|
## firewall
|
||||||
|
|
||||||
`Networkd` has to filter the connections to local services.
|
`Plumberd` has to filter the connections to local services.
|
||||||
|
|
||||||
```Warning
|
```Warning
|
||||||
WIP.
|
WIP.
|
||||||
|
@ -17,7 +17,7 @@ WIP.
|
||||||
|
|
||||||
## authentication
|
## authentication
|
||||||
|
|
||||||
`Networkd` has to authenticate clients asking for a service.
|
`Plumberd` has to authenticate clients asking for a service.
|
||||||
|
|
||||||
```Warning
|
```Warning
|
||||||
WIP.
|
WIP.
|
||||||
|
@ -49,31 +49,31 @@ This program can be used as follow:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# with some static rules
|
# with some static rules
|
||||||
networkd --allow in authd tls:example.com --deny in * * --allow out pong tls:pong.example.com:9000
|
plumberd --allow in authd tls:example.com --deny in * * --allow out pong tls:pong.example.com:9000
|
||||||
networkd --redirect authd nextversion-authd
|
plumberd --redirect authd nextversion-authd
|
||||||
```
|
```
|
||||||
|
|
||||||
## usage examples
|
## usage examples
|
||||||
|
|
||||||
`networkd` is requested each time a client is launched when the right environment variable is used.
|
`plumberd` is requested each time a client is launched when the right environment variable is used.
|
||||||
For example, we want to connect to a distant `authd` service:
|
For example, we want to connect to a distant `authd` service:
|
||||||
|
|
||||||
IPC_NETWORKD="authd tls://user@passwd:example.com:9000/authd"
|
IPC_NETWORKD="authd tls://user@passwd:example.com:9000/authd"
|
||||||
|
|
||||||
|
|
||||||
```Warning
|
```Warning
|
||||||
Currently, the networkd only works with tcp and unix routes.
|
Currently, the plumberd only works with tcp and unix routes.
|
||||||
```
|
```
|
||||||
|
|
||||||
IPC_NETWORKD="pongd tcp://example.com:9000/pongd"
|
IPC_NETWORKD="pongd tcp://example.com:9000/pongd"
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
* v0.1: (current) networkd (redirections), tcpd
|
* v0.1: (current) plumberd (redirections), tcpd
|
||||||
|
|
||||||
* `networkd` understands URIs (`tcp://example.com/service` or `unix:///service`)
|
* `plumberd` understands URIs (`tcp://example.com/service` or `unix:///service`)
|
||||||
* `tcp` scheme is understood: `networkd` contacts the `tcpd` service
|
* `tcp` scheme is understood: `plumberd` contacts the `tcpd` service
|
||||||
* `unix` scheme is understood: `networkd` performs a redirection
|
* `unix` scheme is understood: `plumberd` performs a redirection
|
||||||
|
|
||||||
|
|
||||||
# Roadmap
|
# Roadmap
|
||||||
|
@ -87,16 +87,16 @@ Currently, the networkd only works with tcp and unix routes.
|
||||||
* v1.0: TBD
|
* v1.0: TBD
|
||||||
|
|
||||||
|
|
||||||
# Networkd explanations
|
# Plumberd explanations
|
||||||
|
|
||||||
1. client contacts `networkd`
|
1. client contacts `plumberd`
|
||||||
1. `networkd` understand the request from the client then contacts the local service responsible for the communication protocol required
|
1. `plumberd` understand the request from the client then contacts the local service responsible for the communication protocol required
|
||||||
1. once the distant connection is established (between the two `tlsd` services for example) `networkd` provides a file descriptor to the client
|
1. once the distant connection is established (between the two `tlsd` services for example) `plumberd` provides a file descriptor to the client
|
||||||
1. finally, the client can perform requests to the distant service transparently
|
1. finally, the client can perform requests to the distant service transparently
|
||||||
|
|
||||||
during the connection:
|
during the connection:
|
||||||
|
|
||||||
client <-> networkd <-> tlsd <=> tlsd <-> networkd <-> service
|
client <-> plumberd <-> tlsd <=> tlsd <-> plumberd <-> service
|
||||||
|
|
||||||
then:
|
then:
|
||||||
|
|
||||||
|
|
11
shard.yml
11
shard.yml
|
@ -1,11 +1,11 @@
|
||||||
name: networkd
|
name: plumberd
|
||||||
version: 0.1.1
|
version: 0.2.0
|
||||||
|
|
||||||
authors:
|
authors:
|
||||||
- karchnu <karchnu@karchnu.fr>
|
- karchnu <karchnu@karchnu.fr>
|
||||||
|
|
||||||
description: |
|
description: |
|
||||||
Networkd allows IPC clients to contact remote services.
|
Plumberd allows IPC clients to contact remote services.
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
ipc:
|
ipc:
|
||||||
|
@ -18,9 +18,12 @@ targets:
|
||||||
pongd:
|
pongd:
|
||||||
main: src/pongd.cr
|
main: src/pongd.cr
|
||||||
|
|
||||||
networkd:
|
plumberd:
|
||||||
main: src/main.cr
|
main: src/main.cr
|
||||||
|
|
||||||
|
admind:
|
||||||
|
main: src/admind.cr
|
||||||
|
|
||||||
tcpd:
|
tcpd:
|
||||||
main: src/tcpd.cr
|
main: src/tcpd.cr
|
||||||
tcp:
|
tcp:
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
require "option_parser"
|
||||||
|
require "ipc"
|
||||||
|
require "./colors"
|
||||||
|
|
||||||
|
require "json"
|
||||||
|
|
||||||
|
verbosity = 1
|
||||||
|
service_name = "admind"
|
||||||
|
|
||||||
|
OptionParser.parse! do |parser|
|
||||||
|
parser.on "-s service_name", "--service-name service_name", "URI" do |optsn|
|
||||||
|
service_name = optsn
|
||||||
|
end
|
||||||
|
|
||||||
|
parser.on "-v verbosity", "--verbosity verbosity", "Verbosity (0 = nothing is printed, 1 = only events, 2 = events and messages). Default: 1" do |optsn|
|
||||||
|
verbosity = optsn.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
parser.on "-h", "--help", "Show this help" do
|
||||||
|
puts parser
|
||||||
|
exit 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Rectangle
|
||||||
|
JSON.mapping(
|
||||||
|
x: UInt32,
|
||||||
|
y: UInt32
|
||||||
|
)
|
||||||
|
|
||||||
|
def initialize(@x : UInt32, @y : UInt32)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class WidgetTree
|
||||||
|
JSON.mapping(
|
||||||
|
rectangles: Array(Rectangle)
|
||||||
|
)
|
||||||
|
|
||||||
|
def initialize(@rectangles : Array(Rectangle))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Page
|
||||||
|
JSON.mapping(
|
||||||
|
page: String,
|
||||||
|
title: String,
|
||||||
|
tree: WidgetTree
|
||||||
|
)
|
||||||
|
|
||||||
|
def initialize(@page, @title, @tree)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
IPC::Service.new (service_name) do |event|
|
||||||
|
case event
|
||||||
|
when IPC::Event::Timer
|
||||||
|
if verbosity >= 1
|
||||||
|
puts "#{CORANGE}IPC::Event::Timer#{CRESET}"
|
||||||
|
end
|
||||||
|
when IPC::Event::Connection
|
||||||
|
if verbosity >= 1
|
||||||
|
puts "#{CBLUE}IPC::Event::Connection#{CRESET}, client: #{event.connection.fd}"
|
||||||
|
end
|
||||||
|
when IPC::Event::Disconnection
|
||||||
|
if verbosity >= 1
|
||||||
|
puts "#{CBLUE}IPC::Event::Disconnection#{CRESET}, client: #{event.connection.fd}"
|
||||||
|
end
|
||||||
|
when IPC::Event::Message
|
||||||
|
if verbosity >= 1
|
||||||
|
puts "#{CGREEN}IPC::Event::Message#{CRESET}, client: #{event.connection.fd}"
|
||||||
|
if verbosity == 2
|
||||||
|
puts "#{CBLUE}message: #{event.message} #{CRESET}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# JSON message
|
||||||
|
rectangles = Array(Rectangle).new
|
||||||
|
rectangles << Rectangle.new 1, 2
|
||||||
|
rectangles << Rectangle.new 2, 1
|
||||||
|
rectangles << Rectangle.new 3, 1
|
||||||
|
widgets = WidgetTree.new(rectangles)
|
||||||
|
page = Page.new "admin", "This is the Admin Page", widgets
|
||||||
|
|
||||||
|
event.connection.send 2, page.to_json
|
||||||
|
else
|
||||||
|
if verbosity >= 1
|
||||||
|
puts "#{CRED}Exception: message = #{event.message} #{CRESET}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
12
src/main.cr
12
src/main.cr
|
@ -1,16 +1,16 @@
|
||||||
require "./networkd"
|
require "./plumberd"
|
||||||
|
|
||||||
networkd = NetworkD.new "network"
|
plumberd = NetworkD.new "plumber"
|
||||||
|
|
||||||
# --deny <in|out> <service> <url>
|
# --deny <in|out> <service> <url>
|
||||||
# --allow <in|ou> <service> <url>
|
# --allow <in|ou> <service> <url>
|
||||||
# --redirect <service> <service>
|
# --redirect <service> <service>
|
||||||
# --redirect <service-name> <new-service-name> <origin-url> <dest-url>
|
# --redirect <service-name> <new-service-name> <origin-url> <dest-url>
|
||||||
|
|
||||||
networkd.parse_cli ARGV
|
plumberd.parse_cli ARGV
|
||||||
puts networkd.to_s
|
puts plumberd.to_s
|
||||||
|
|
||||||
networkd.loop do |event|
|
plumberd.loop do |event|
|
||||||
puts "there is an event!"
|
puts "there is an event!"
|
||||||
case event
|
case event
|
||||||
when IPC::Event::Connection
|
when IPC::Event::Connection
|
||||||
|
@ -27,7 +27,7 @@ networkd.loop do |event|
|
||||||
puts "\033[32mthere is a message\033[00m"
|
puts "\033[32mthere is a message\033[00m"
|
||||||
puts event.message.to_s
|
puts event.message.to_s
|
||||||
|
|
||||||
networkd.service_lookup event.message, event.connection
|
plumberd.service_lookup event.message, event.connection
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
|
|
||||||
require "./rules"
|
require "./rules"
|
||||||
require "./networkdcliparser"
|
require "./plumberdcliparser"
|
||||||
require "ipc"
|
require "ipc"
|
||||||
require "uri"
|
require "uri"
|
||||||
|
|
||||||
class IPC::NetworkD < IPC::Service
|
class IPC::NetworkD < IPC::Service
|
||||||
|
|
||||||
# The client asks to networkd to open a connection to a service
|
# The client asks to plumberd to open a connection to a service
|
||||||
# the service name is used unless there is a redirection that is provided to the client through
|
# the service name is used unless there is a redirection that is provided to the client through
|
||||||
# a IPC_NETWORK environment variable
|
# a IPC_NETWORK environment variable
|
||||||
# This environment variable is sent from the client to networkd, that's what is parsed here
|
# This environment variable is sent from the client to plumberd, that's what is parsed here
|
||||||
|
|
||||||
# parse_lookup_payload extract the right service URI to use from the IPC_NETWORK content
|
# parse_lookup_payload extract the right service URI to use from the IPC_NETWORK content
|
||||||
# sent by the client in the form:
|
# sent by the client in the form:
|
||||||
|
@ -80,7 +80,7 @@ class IPC::NetworkD < IPC::Service
|
||||||
# TODO: for remote services, we have to connect to communication service
|
# TODO: for remote services, we have to connect to communication service
|
||||||
# these communication services need an URI to work on
|
# these communication services need an URI to work on
|
||||||
# The protocol:
|
# The protocol:
|
||||||
# networkd sends an URI to the communication service, which responds with a "OK" message
|
# plumberd sends an URI to the communication service, which responds with a "OK" message
|
||||||
if scheme != "unix"
|
if scheme != "unix"
|
||||||
service.send 1.to_u8, "#{requested_service.to_s}\n"
|
service.send 1.to_u8, "#{requested_service.to_s}\n"
|
||||||
response = service.read
|
response = service.read
|
||||||
|
@ -97,7 +97,7 @@ class IPC::NetworkD < IPC::Service
|
||||||
raise Exception.new "cannot send the file descriptor of the requested service: #{m}"
|
raise Exception.new "cannot send the file descriptor of the requested service: #{m}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# finally, the service should be closed in networkd
|
# finally, the service should be closed in plumberd
|
||||||
service.close
|
service.close
|
||||||
rescue e
|
rescue e
|
||||||
puts "\033[31mException during the connection to the requested service #{requested_service}: #{e}\033[00m"
|
puts "\033[31mException during the connection to the requested service #{requested_service}: #{e}\033[00m"
|
||||||
|
@ -118,8 +118,8 @@ class IPC::NetworkD < IPC::Service
|
||||||
def wait_event(server : IPC::Connection?, &block) : Tuple(LibIPC::EventType, IPC::Message, IPC::Connection)
|
def wait_event(server : IPC::Connection?, &block) : Tuple(LibIPC::EventType, IPC::Message, IPC::Connection)
|
||||||
event = LibIPC::Event.new
|
event = LibIPC::Event.new
|
||||||
|
|
||||||
# TODO: networkd should be able to transfer messages???
|
# TODO: plumberd should be able to transfer messages???
|
||||||
r = LibIPC.ipc_wait_event self.pointer, @service_info.pointer, pointerof(event)
|
r = LibIPC.ipc_wait_event self.pointer, @service_info.pointer, pointerof(event), pointerof(@timer)
|
||||||
if r != 0
|
if r != 0
|
||||||
m = String.new LibIPC.ipc_errors_get (r)
|
m = String.new LibIPC.ipc_errors_get (r)
|
||||||
yield IPC::Exception.new "error waiting for a new event: #{m}"
|
yield IPC::Exception.new "error waiting for a new event: #{m}"
|
29
src/pongd.cr
29
src/pongd.cr
|
@ -2,6 +2,7 @@ require "option_parser"
|
||||||
require "ipc"
|
require "ipc"
|
||||||
require "./colors"
|
require "./colors"
|
||||||
|
|
||||||
|
verbosity = 1
|
||||||
service_name = "pong"
|
service_name = "pong"
|
||||||
|
|
||||||
OptionParser.parse! do |parser|
|
OptionParser.parse! do |parser|
|
||||||
|
@ -9,6 +10,10 @@ OptionParser.parse! do |parser|
|
||||||
service_name = optsn
|
service_name = optsn
|
||||||
end
|
end
|
||||||
|
|
||||||
|
parser.on "-v verbosity", "--verbosity verbosity", "Verbosity (0 = nothing is printed, 1 = only events, 2 = events and messages). Default: 1" do |optsn|
|
||||||
|
verbosity = optsn.to_i
|
||||||
|
end
|
||||||
|
|
||||||
parser.on "-h", "--help", "Show this help" do
|
parser.on "-h", "--help", "Show this help" do
|
||||||
puts parser
|
puts parser
|
||||||
exit 0
|
exit 0
|
||||||
|
@ -18,13 +23,29 @@ end
|
||||||
|
|
||||||
IPC::Service.new (service_name) do |event|
|
IPC::Service.new (service_name) do |event|
|
||||||
case event
|
case event
|
||||||
|
when IPC::Event::Timer
|
||||||
|
if verbosity >= 1
|
||||||
|
puts "#{CORANGE}IPC::Event::Timer#{CRESET}"
|
||||||
|
end
|
||||||
when IPC::Event::Connection
|
when IPC::Event::Connection
|
||||||
puts "#{CBLUE}IPC::Event::Connection#{CRESET}, client: #{event.connection.fd}"
|
if verbosity >= 1
|
||||||
|
puts "#{CBLUE}IPC::Event::Connection#{CRESET}, client: #{event.connection.fd}"
|
||||||
|
end
|
||||||
when IPC::Event::Disconnection
|
when IPC::Event::Disconnection
|
||||||
puts "#{CBLUE}IPC::Event::Disconnection#{CRESET}, client: #{event.connection.fd}"
|
if verbosity >= 1
|
||||||
|
puts "#{CBLUE}IPC::Event::Disconnection#{CRESET}, client: #{event.connection.fd}"
|
||||||
|
end
|
||||||
when IPC::Event::Message
|
when IPC::Event::Message
|
||||||
puts "#{CGREEN}IPC::Event::Message#{CRESET}, client: #{event.connection.fd}"
|
if verbosity >= 1
|
||||||
# puts event.message.to_s
|
puts "#{CGREEN}IPC::Event::Message#{CRESET}, client: #{event.connection.fd}"
|
||||||
|
if verbosity == 2
|
||||||
|
puts "#{CBLUE}message: #{event.message} #{CRESET}"
|
||||||
|
end
|
||||||
|
end
|
||||||
event.connection.send event.message
|
event.connection.send event.message
|
||||||
|
else
|
||||||
|
if verbosity >= 1
|
||||||
|
puts "#{CRED}Exception: message = #{event.message} #{CRESET}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
require "./utils.cr"
|
||||||
|
|
||||||
|
begin
|
||||||
|
message = String.build do |str|
|
||||||
|
i = 0
|
||||||
|
80.times do
|
||||||
|
str << "i=#{i}"
|
||||||
|
i += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue e
|
||||||
|
puts "Error: #{e}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
bin = to_message 2, message
|
||||||
|
|
||||||
|
rescue e
|
||||||
|
puts "error in to_message: #{e}"
|
||||||
|
exit 2
|
||||||
|
end
|
||||||
|
|
||||||
|
print_hexa String.new(bin), "message"
|
|
@ -1,14 +1,11 @@
|
||||||
require "io/hexdump"
|
require "io/hexdump"
|
||||||
|
|
||||||
def to_message (user_type : Int, message : String)
|
def to_message (user_type : Int, message : String)
|
||||||
payload = Bytes.new (6 + message.size)
|
payload = Bytes.new (6 + message.to_slice.size)
|
||||||
|
|
||||||
# true start
|
# true start
|
||||||
payload[0] = 1.to_u8
|
payload[0] = 1.to_u8
|
||||||
payload[1] = 0.to_u8
|
IO::ByteFormat::NetworkEndian.encode message.to_slice.size, (payload + 1)
|
||||||
payload[2] = 0.to_u8
|
|
||||||
payload[3] = 0.to_u8
|
|
||||||
payload[4] = message.size.to_u8
|
|
||||||
|
|
||||||
# second part: user message
|
# second part: user message
|
||||||
payload[5] = user_type.to_u8
|
payload[5] = user_type.to_u8
|
||||||
|
|
|
@ -55,16 +55,26 @@ end
|
||||||
begin
|
begin
|
||||||
ws = WebSocket.new(URI.parse(uri))
|
ws = WebSocket.new(URI.parse(uri))
|
||||||
|
|
||||||
|
puts "connection done: sending pong"
|
||||||
|
|
||||||
ws.on_close do |socket|
|
ws.on_close do |socket|
|
||||||
puts "socket is closing"
|
puts "socket is closing"
|
||||||
exit 0
|
exit 0
|
||||||
end
|
end
|
||||||
|
|
||||||
read ws
|
if uri.ends_with? ".JSON"
|
||||||
send ws, "pong"
|
json_message = STDIN.gets_to_end
|
||||||
read ws
|
json_message = json_message.chomp
|
||||||
send ws, to_message(2, "coucou")
|
puts "json_message: #{json_message}"
|
||||||
read ws
|
final_json_message = "{ \"mtype\": 2, \"payload\": #{json_message} }"
|
||||||
|
puts "final json message: #{final_json_message}"
|
||||||
|
send ws, final_json_message
|
||||||
|
# send ws, "{ \"mtype\": 2, \"payload\": \"coucou\" }"
|
||||||
|
read ws
|
||||||
|
else
|
||||||
|
send ws, to_message(2, STDIN.gets_to_end)
|
||||||
|
read ws
|
||||||
|
end
|
||||||
|
|
||||||
ws.close
|
ws.close
|
||||||
ws.read
|
ws.read
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
require "http/web_socket"
|
||||||
|
require "option_parser"
|
||||||
|
|
||||||
|
require "./colors"
|
||||||
|
require "./utils"
|
||||||
|
|
||||||
|
require "./ws"
|
||||||
|
|
||||||
|
uri = "ws://localhost:1234/pong"
|
||||||
|
|
||||||
|
OptionParser.parse! do |parser|
|
||||||
|
parser.on "-u uri", "--uri uri", "URI" do |opturi|
|
||||||
|
uri = opturi
|
||||||
|
end
|
||||||
|
|
||||||
|
parser.on "-h", "--help", "Show this help" do
|
||||||
|
puts parser
|
||||||
|
exit 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_then_print(ws : WebSocket)
|
||||||
|
m = read ws
|
||||||
|
puts "message: #{String.new(m)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_then_print_hexa(ws : WebSocket)
|
||||||
|
m = read ws
|
||||||
|
print_hexa(String.new(m), "#{CBLUE}Received message hexa#{CRESET}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def read(ws : WebSocket)
|
||||||
|
m = ws.read
|
||||||
|
if m.nil?
|
||||||
|
raise "empty message"
|
||||||
|
end
|
||||||
|
|
||||||
|
m
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_with_announce(ws : WebSocket, m : String)
|
||||||
|
puts "sending #{m}"
|
||||||
|
send ws, m
|
||||||
|
end
|
||||||
|
|
||||||
|
def send(ws : WebSocket, m : String)
|
||||||
|
ws.send m
|
||||||
|
end
|
||||||
|
|
||||||
|
def send(ws : WebSocket, m : Slice)
|
||||||
|
ws.send m
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
begin
|
||||||
|
ws = WebSocket.new(URI.parse(uri))
|
||||||
|
|
||||||
|
puts "connection done: sending pong"
|
||||||
|
|
||||||
|
ws.on_close do |socket|
|
||||||
|
puts "socket is closing"
|
||||||
|
exit 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if uri.ends_with? ".JSON"
|
||||||
|
json_message = STDIN.gets_to_end
|
||||||
|
json_message = json_message.chomp
|
||||||
|
# puts "json_message: #{json_message}"
|
||||||
|
final_json_message = "{ \"mtype\": 2, \"payload\": #{json_message} }"
|
||||||
|
puts "sending 10 messages: #{final_json_message}"
|
||||||
|
10.times do |i|
|
||||||
|
send ws, final_json_message
|
||||||
|
puts "sent message #{i}"
|
||||||
|
# send ws, "{ \"mtype\": 2, \"payload\": \"coucou\" }"
|
||||||
|
end
|
||||||
|
|
||||||
|
10.times do |i|
|
||||||
|
read ws
|
||||||
|
puts "read message #{i}"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
send ws, to_message(2, STDIN.gets_to_end)
|
||||||
|
read ws
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
ws.read
|
||||||
|
ws.close
|
||||||
|
rescue e
|
||||||
|
puts "Exception: #{e}"
|
||||||
|
end
|
||||||
|
|
|
@ -54,7 +54,6 @@ OptionParser.parse! do |parser|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
class InstanceStorage
|
class InstanceStorage
|
||||||
property service : IPC::SwitchingService
|
property service : IPC::SwitchingService
|
||||||
property switchtable : Hash(Int32, Int32)
|
property switchtable : Hash(Int32, Int32)
|
||||||
|
@ -93,6 +92,10 @@ class InstanceStorage
|
||||||
# 6. removing the client and the service from is_client
|
# 6. removing the client and the service from is_client
|
||||||
@is_client = @is_client.select do |fd,v| fd != fdclient end
|
@is_client = @is_client.select do |fd,v| fd != fdclient end
|
||||||
@is_json = @is_json.select do |fd,v| fd != fdclient end
|
@is_json = @is_json.select do |fd,v| fd != fdclient end
|
||||||
|
|
||||||
|
@fd_to_websocket.select! do |fd, ws|
|
||||||
|
fd != fdclient
|
||||||
|
end
|
||||||
|
|
||||||
unless fdservice.nil?
|
unless fdservice.nil?
|
||||||
service = @fd_to_ipcconnection[fdservice]
|
service = @fd_to_ipcconnection[fdservice]
|
||||||
|
@ -107,7 +110,8 @@ class InstanceStorage
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
server = TCPServer.new("localhost", port_to_listen)
|
# by default, listen on any IP address
|
||||||
|
server = TCPServer.new("0.0.0.0", port_to_listen)
|
||||||
service = IPC::SwitchingService.new service_name
|
service = IPC::SwitchingService.new service_name
|
||||||
service << server.fd
|
service << server.fd
|
||||||
context = InstanceStorage.new service
|
context = InstanceStorage.new service
|
||||||
|
@ -171,6 +175,9 @@ def websocket_client_connection(client, context : InstanceStorage)
|
||||||
client.send "#{headers_header}\n#{headers.to_s}\r\n"
|
client.send "#{headers_header}\n#{headers.to_s}\r\n"
|
||||||
|
|
||||||
wsclient = WebSocket.new client
|
wsclient = WebSocket.new client
|
||||||
|
wsclient.on_pong do |m|
|
||||||
|
puts "Received a pong message: #{m}"
|
||||||
|
end
|
||||||
context.is_client[client.fd] = true
|
context.is_client[client.fd] = true
|
||||||
|
|
||||||
# listen to the client's file descriptor
|
# listen to the client's file descriptor
|
||||||
|
@ -236,6 +243,7 @@ class IPC::Message
|
||||||
end
|
end
|
||||||
|
|
||||||
def websocket_switching_procedure (activefd : Int, context : InstanceStorage)
|
def websocket_switching_procedure (activefd : Int, context : InstanceStorage)
|
||||||
|
# FIXME: debugging purposes
|
||||||
begin
|
begin
|
||||||
if context.is_client[activefd]
|
if context.is_client[activefd]
|
||||||
# The client is a WebSocket on top of a TCP connection
|
# The client is a WebSocket on top of a TCP connection
|
||||||
|
@ -244,6 +252,7 @@ def websocket_switching_procedure (activefd : Int, context : InstanceStorage)
|
||||||
begin
|
begin
|
||||||
message = wsclient.read
|
message = wsclient.read
|
||||||
|
|
||||||
|
# puts "RECEIVING A MESSAGE from #{activefd}: #{message}"
|
||||||
rescue e
|
rescue e
|
||||||
puts "#{CRED}Exception (receiving a message)#{CRESET} #{e}"
|
puts "#{CRED}Exception (receiving a message)#{CRESET} #{e}"
|
||||||
closing_client activefd, context
|
closing_client activefd, context
|
||||||
|
@ -262,9 +271,29 @@ def websocket_switching_procedure (activefd : Int, context : InstanceStorage)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if message.is_a?(WebSocket::Ping)
|
||||||
|
# puts "#{CBLUE}This is a ping message#{CRESET}"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if message.is_a?(WebSocket::Pong)
|
||||||
|
# puts "#{CBLUE}This is a pong message#{CRESET}"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if message.is_a?(WebSocket::Close)
|
||||||
|
# puts "#{CRED}This is a close message#{CRESET}"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if message.is_a?(Slice(UInt8))
|
||||||
|
# puts "#{CRED}This is a binary message: not yet implemented#{CRESET}"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
# TODO: verify this
|
# TODO: verify this
|
||||||
if context.is_json[activefd]
|
if context.is_json[activefd]
|
||||||
jsonmessage = JSONWSMessage.from_json String.new(message)
|
jsonmessage = JSONWSMessage.from_json message
|
||||||
message = to_message jsonmessage.mtype, jsonmessage.payload
|
message = to_message jsonmessage.mtype, jsonmessage.payload
|
||||||
|
|
||||||
# puts "JSON TYPE !!!"
|
# puts "JSON TYPE !!!"
|
||||||
|
@ -281,16 +310,21 @@ def websocket_switching_procedure (activefd : Int, context : InstanceStorage)
|
||||||
serv = WrappedTCPFileDescriptor.new(fd: fdservice, family: Socket::Family::INET)
|
serv = WrappedTCPFileDescriptor.new(fd: fdservice, family: Socket::Family::INET)
|
||||||
#serv = context.fd_to_ipcconnection[fdservice]
|
#serv = context.fd_to_ipcconnection[fdservice]
|
||||||
serv.send message
|
serv.send message
|
||||||
|
# puts "SENT MESSAGE TO SERVER: #{message}"
|
||||||
|
|
||||||
else
|
else
|
||||||
# puts "switching: service to client"
|
# puts "switching: service to client"
|
||||||
# service => client
|
# service => client
|
||||||
|
|
||||||
|
# puts "RECEIVING A MESSAGE FROM THE SERVER #{activefd}"
|
||||||
|
|
||||||
fdclient = context.switchtable[activefd]
|
fdclient = context.switchtable[activefd]
|
||||||
wsclient = context.fd_to_websocket[fdclient]
|
wsclient = context.fd_to_websocket[fdclient]
|
||||||
|
|
||||||
serv = context.fd_to_ipcconnection[activefd]
|
serv = context.fd_to_ipcconnection[activefd]
|
||||||
message = serv.read
|
message = serv.read
|
||||||
|
|
||||||
|
# puts "RECEIVING A MESSAGE FROM THE SERVER #{activefd}: #{message}"
|
||||||
# puts "received message from service: #{message.to_s}"
|
# puts "received message from service: #{message.to_s}"
|
||||||
|
|
||||||
if context.is_json[fdclient]
|
if context.is_json[fdclient]
|
||||||
|
@ -316,11 +350,28 @@ def websocket_switching_procedure (activefd : Int, context : InstanceStorage)
|
||||||
closing_client clientfd, context
|
closing_client clientfd, context
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Every few seconds, the service should trigger the timer
|
||||||
|
service.base_timer = 30
|
||||||
|
|
||||||
service.loop do |event|
|
service.loop do |event|
|
||||||
case event
|
case event
|
||||||
|
when IPC::Event::Timer
|
||||||
|
# puts "#{CORANGE}IPC::Event::Timer#{CRESET}"
|
||||||
|
context.fd_to_websocket.each do |fd, ws|
|
||||||
|
|
||||||
|
begin
|
||||||
|
ws.ping "coucou from #{fd}"
|
||||||
|
rescue e
|
||||||
|
puts "#{CRED}Exception: #{e}#{CRESET}, already closed client #{fd}"
|
||||||
|
begin
|
||||||
|
context.remove_fd fd
|
||||||
|
rescue e
|
||||||
|
puts "#{CRED}Cannot remove #{fd} from clients: #{e}#{CRESET}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
when IPC::Event::Connection
|
when IPC::Event::Connection
|
||||||
puts "#{CBLUE}IPC::Event::Connection: #{event.connection.fd}#{CRESET}"
|
puts "#{CBLUE}IPC::Event::Connection: #{event.connection.fd}#{CRESET}"
|
||||||
when IPC::Event::Disconnection
|
when IPC::Event::Disconnection
|
||||||
|
|
86
src/ws.cr
86
src/ws.cr
|
@ -2,35 +2,71 @@
|
||||||
require "http"
|
require "http"
|
||||||
require "http/web_socket/protocol"
|
require "http/web_socket/protocol"
|
||||||
|
|
||||||
|
class HTTP::WebSocket
|
||||||
|
record Pong
|
||||||
|
record Ping
|
||||||
|
record Close
|
||||||
|
|
||||||
|
def read : Slice(UInt8) | String | Close | Ping | Pong | Nil
|
||||||
|
size = 0
|
||||||
|
begin
|
||||||
|
info = @ws.receive(@buffer)
|
||||||
|
rescue IO::EOFError
|
||||||
|
close
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
case info.opcode
|
||||||
|
when Protocol::Opcode::PING
|
||||||
|
@current_message.write @buffer[0, info.size]
|
||||||
|
if info.final
|
||||||
|
message = @current_message.to_s
|
||||||
|
@on_ping.try &.call(message)
|
||||||
|
pong(message) unless closed?
|
||||||
|
@current_message.clear
|
||||||
|
end
|
||||||
|
return Ping.new
|
||||||
|
when Protocol::Opcode::PONG
|
||||||
|
@current_message.write @buffer[0, info.size]
|
||||||
|
if info.final
|
||||||
|
@on_pong.try &.call(@current_message.to_s)
|
||||||
|
@current_message.clear
|
||||||
|
end
|
||||||
|
return Pong.new
|
||||||
|
when Protocol::Opcode::TEXT
|
||||||
|
message = @buffer[0, info.size]
|
||||||
|
@current_message.write message
|
||||||
|
if info.final
|
||||||
|
@on_message.try &.call(@current_message.to_s)
|
||||||
|
@current_message.clear
|
||||||
|
end
|
||||||
|
return String.new message
|
||||||
|
when Protocol::Opcode::BINARY
|
||||||
|
message = @buffer[0, info.size]
|
||||||
|
@current_message.write message
|
||||||
|
if info.final
|
||||||
|
@on_binary.try &.call(@current_message.to_slice)
|
||||||
|
@current_message.clear
|
||||||
|
end
|
||||||
|
return message
|
||||||
|
when Protocol::Opcode::CLOSE
|
||||||
|
@current_message.write @buffer[0, info.size]
|
||||||
|
if info.final
|
||||||
|
message = @current_message.to_s
|
||||||
|
@on_close.try &.call(message)
|
||||||
|
close(message) unless closed?
|
||||||
|
@current_message.clear
|
||||||
|
end
|
||||||
|
return Close.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
class WebSocket < HTTP::WebSocket
|
class WebSocket < HTTP::WebSocket
|
||||||
getter? closed = false
|
getter? closed = false
|
||||||
|
|
||||||
def read
|
|
||||||
size = 0
|
|
||||||
begin
|
|
||||||
info = @ws.receive(@buffer)
|
|
||||||
size = info.size
|
|
||||||
rescue IO::EOFError
|
|
||||||
close
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
case info.opcode
|
|
||||||
when Protocol::Opcode::TEXT
|
|
||||||
return @buffer[0..size-1]
|
|
||||||
when Protocol::Opcode::BINARY
|
|
||||||
return @buffer[0..size-1]
|
|
||||||
when Protocol::Opcode::CLOSE
|
|
||||||
begin
|
|
||||||
close
|
|
||||||
rescue e
|
|
||||||
puts "\033[31mwebsocket failed to close properly\033[00m #{e}"
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def finalize
|
def finalize
|
||||||
# puts "WrappedTCPFileDescriptor garbage collection!!"
|
# puts "WrappedTCPFileDescriptor garbage collection!!"
|
||||||
# super
|
# super
|
||||||
|
|
Reference in New Issue