lib modifications: in lib/
parent
53ed216132
commit
da683e96fa
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
require "http"
|
||||||
|
require "http/web_socket/protocol"
|
||||||
|
|
||||||
|
# The Socket library uses the IO::Buffered module.
|
||||||
|
# IO::Buffered needs a new function to check if there is still something to read
|
||||||
|
# in its buffer. Since WebSocket is compatible with all IO instances, then IO
|
||||||
|
# needs this function as well, even if it doesn't really makes sense here.
|
||||||
|
class IO
|
||||||
|
def empty? : Bool
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module IO::Buffered
|
||||||
|
# :nodoc:
|
||||||
|
def empty? : Bool
|
||||||
|
@in_buffer_rem.size == 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -0,0 +1,151 @@
|
||||||
|
require "uuid"
|
||||||
|
|
||||||
|
# YAML UUID parser
|
||||||
|
def UUID.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node)
|
||||||
|
ctx.read_alias(node, UUID) do |obj|
|
||||||
|
return UUID.new obj
|
||||||
|
end
|
||||||
|
|
||||||
|
if node.is_a?(YAML::Nodes::Scalar)
|
||||||
|
value = node.value
|
||||||
|
ctx.record_anchor(node, value)
|
||||||
|
UUID.new value
|
||||||
|
else
|
||||||
|
node.raise "Expected UUID, not #{node.class.name}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module YAML
|
||||||
|
# change dates in YAML formated content
|
||||||
|
def self.human_dates(content : String)
|
||||||
|
new_lines = Array(String).new
|
||||||
|
content.each_line do |line|
|
||||||
|
case line
|
||||||
|
when /(?<date>.*date):[ \t]+NOW[ \t]*(?<op>[-+])[ \t]*(?<rand>rand)?[ \t]*(?<delta>[0-9]+) *(?<scale>[a-z]+)?/
|
||||||
|
date = $~["date"]
|
||||||
|
op = $~["op"]
|
||||||
|
delta = $~["delta"].to_i
|
||||||
|
rand = $~["rand"] rescue nil
|
||||||
|
scale = $~["scale"] rescue nil # days, hours
|
||||||
|
|
||||||
|
unless rand.nil?
|
||||||
|
old = delta
|
||||||
|
delta = Random.rand(delta)
|
||||||
|
end
|
||||||
|
|
||||||
|
vdelta = delta.days
|
||||||
|
case scale
|
||||||
|
when /day/
|
||||||
|
# default one
|
||||||
|
when /hour/
|
||||||
|
vdelta = delta.hours
|
||||||
|
else
|
||||||
|
# puts "scale infered: days"
|
||||||
|
end
|
||||||
|
|
||||||
|
yaml_date = Time::Format::YAML_DATE.format(Time.local + vdelta)
|
||||||
|
case op
|
||||||
|
when /-/
|
||||||
|
yaml_date = Time::Format::YAML_DATE.format(Time.local - vdelta)
|
||||||
|
# puts "-"
|
||||||
|
when /\+/
|
||||||
|
# default one
|
||||||
|
# puts "+"
|
||||||
|
else
|
||||||
|
# puts "date operation not understood: #{op}, + infered"
|
||||||
|
end
|
||||||
|
|
||||||
|
new_lines << "#{date}: #{yaml_date}"
|
||||||
|
next
|
||||||
|
when /(?<date>.+date):[ \t]+NOW[ \t]*$/
|
||||||
|
date = $~["date"]
|
||||||
|
yaml_date = Time::Format::YAML_DATE.format(Time.local)
|
||||||
|
|
||||||
|
new_lines << "#{date}: #{yaml_date}"
|
||||||
|
next
|
||||||
|
when /(?<date>[a-z]+_date):[ \t]+NOW[ \t]*$/
|
||||||
|
date = $~["date"]
|
||||||
|
yaml_date = Time::Format::YAML_DATE.format(Time.local)
|
||||||
|
|
||||||
|
new_lines << "#{date}: #{yaml_date}"
|
||||||
|
next
|
||||||
|
# when /(?<date>[a-z]+_date):/
|
||||||
|
# puts "date that does not compute: #{line}"
|
||||||
|
end
|
||||||
|
new_lines << line
|
||||||
|
end
|
||||||
|
|
||||||
|
new_lines.join "\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Array(T)
|
||||||
|
def self.from_yaml_files(files)
|
||||||
|
values = Array(T).new
|
||||||
|
files.each do |file|
|
||||||
|
raise "File doesn't exist #{file}" unless File.exists? file
|
||||||
|
from_yaml_file(file).each do |v|
|
||||||
|
values << v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
values
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.from_yaml_file(file)
|
||||||
|
from_yaml_content File.read file
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.from_yaml_content(input_content)
|
||||||
|
content = YAML.human_dates input_content
|
||||||
|
|
||||||
|
values = Array(T).new
|
||||||
|
|
||||||
|
begin
|
||||||
|
values << T.from_yaml content
|
||||||
|
rescue e
|
||||||
|
Baguette::Log.warning "reading the input #{e}"
|
||||||
|
begin
|
||||||
|
Array(T).from_yaml(content).each do |b|
|
||||||
|
values << b
|
||||||
|
end
|
||||||
|
rescue e
|
||||||
|
raise "wrong YAML content: #{e}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
values
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.from_json_files(files)
|
||||||
|
values = Array(T).new
|
||||||
|
files.each do |file|
|
||||||
|
raise "File doesn't exist #{file}" unless File.exists? file
|
||||||
|
from_json_file(file).each do |v|
|
||||||
|
values << v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
values
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.from_json_file(file)
|
||||||
|
from_json_content File.read file
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.from_json_content(content)
|
||||||
|
values = Array(T).new
|
||||||
|
|
||||||
|
begin
|
||||||
|
values << T.from_json content
|
||||||
|
rescue e
|
||||||
|
begin
|
||||||
|
Array(T).from_json(content).each do |b|
|
||||||
|
values << b
|
||||||
|
end
|
||||||
|
rescue e
|
||||||
|
raise "wrong JSON content: #{e}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
values
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
class WrappedTCPFileDescriptor < TCPSocket
|
||||||
|
# do not close the connection when garbage collected!!
|
||||||
|
def finalize
|
||||||
|
# puts "WrappedTCPFileDescriptor garbage collection!!"
|
||||||
|
# super
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,107 @@
|
||||||
|
|
||||||
|
class HTTP::WebSocket
|
||||||
|
# Infinite loop over `run_once`.
|
||||||
|
# Stops when the socket closes or an error occurs.
|
||||||
|
def run
|
||||||
|
loop do
|
||||||
|
ret = run_once
|
||||||
|
case ret
|
||||||
|
when Error | Close
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# `run_once` returns the type of the message which could be a Ping or Pong message, a closing socket, an error (when an exception occurs) or a message that is not yet entirely received.
|
||||||
|
record Ping
|
||||||
|
record Pong
|
||||||
|
record Close
|
||||||
|
record Error
|
||||||
|
# record NotFinal
|
||||||
|
|
||||||
|
struct NotFinal
|
||||||
|
property message : String
|
||||||
|
property info : Protocol::PacketInfo
|
||||||
|
def initialize(@message, @info)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
|
def run_once : Bytes | String | Close | Ping | Pong | NotFinal | Error
|
||||||
|
begin
|
||||||
|
info = @ws.receive(@buffer)
|
||||||
|
rescue IO::EOFError
|
||||||
|
close
|
||||||
|
return Error.new
|
||||||
|
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
|
||||||
|
return Ping.new
|
||||||
|
end
|
||||||
|
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
|
||||||
|
return Pong.new
|
||||||
|
end
|
||||||
|
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
|
||||||
|
return String.new message
|
||||||
|
else
|
||||||
|
return NotFinal.new String.new(message), info
|
||||||
|
end
|
||||||
|
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
|
||||||
|
return message
|
||||||
|
end
|
||||||
|
when Protocol::Opcode::CLOSE
|
||||||
|
@current_message.write @buffer[0, info.size]
|
||||||
|
if info.final
|
||||||
|
message = @current_message.to_s
|
||||||
|
@on_close.try &.call(HTTP::WebSocket::CloseCode::NormalClosure, message)
|
||||||
|
close(HTTP::WebSocket::CloseCode::NormalClosure, message) unless closed?
|
||||||
|
@current_message.clear
|
||||||
|
return Close.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
raise "what the fuck?"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the websocket instance.
|
||||||
|
def ws
|
||||||
|
@ws
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class HTTP::WebSocket::Protocol
|
||||||
|
def io
|
||||||
|
@io
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
class WebSocket < HTTP::WebSocket
|
||||||
|
getter? closed = false
|
||||||
|
|
||||||
|
def finalize
|
||||||
|
# puts "WrappedTCPFileDescriptor garbage collection!!"
|
||||||
|
# super
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,136 +1,6 @@
|
||||||
# Libraries modifications
|
# Libraries modifications
|
||||||
|
|
||||||
require "http"
|
require "./lib/json_yaml_arrays"
|
||||||
require "http/web_socket/protocol"
|
require "./lib/io"
|
||||||
|
require "./lib/websocket"
|
||||||
# The Socket library uses the IO::Buffered module.
|
require "./lib/tcp"
|
||||||
# IO::Buffered needs a new function to check if there is still something to read
|
|
||||||
# in its buffer. Since WebSocket is compatible with all IO instances, then IO
|
|
||||||
# needs this function as well, even if it doesn't really makes sense here.
|
|
||||||
class IO
|
|
||||||
def empty? : Bool
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module IO::Buffered
|
|
||||||
# :nodoc:
|
|
||||||
def empty? : Bool
|
|
||||||
@in_buffer_rem.size == 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class HTTP::WebSocket
|
|
||||||
# Infinite loop over `run_once`.
|
|
||||||
# Stops when the socket closes or an error occurs.
|
|
||||||
def run
|
|
||||||
loop do
|
|
||||||
ret = run_once
|
|
||||||
case ret
|
|
||||||
when Error | Close
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# `run_once` returns the type of the message which could be a Ping or Pong message, a closing socket, an error (when an exception occurs) or a message that is not yet entirely received.
|
|
||||||
record Ping
|
|
||||||
record Pong
|
|
||||||
record Close
|
|
||||||
record Error
|
|
||||||
# record NotFinal
|
|
||||||
|
|
||||||
struct NotFinal
|
|
||||||
property message : String
|
|
||||||
property info : Protocol::PacketInfo
|
|
||||||
def initialize(@message, @info)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# :nodoc:
|
|
||||||
def run_once : Bytes | String | Close | Ping | Pong | NotFinal | Error
|
|
||||||
begin
|
|
||||||
info = @ws.receive(@buffer)
|
|
||||||
rescue IO::EOFError
|
|
||||||
close
|
|
||||||
return Error.new
|
|
||||||
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
|
|
||||||
return Ping.new
|
|
||||||
end
|
|
||||||
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
|
|
||||||
return Pong.new
|
|
||||||
end
|
|
||||||
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
|
|
||||||
return String.new message
|
|
||||||
else
|
|
||||||
return NotFinal.new String.new(message), info
|
|
||||||
end
|
|
||||||
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
|
|
||||||
return message
|
|
||||||
end
|
|
||||||
when Protocol::Opcode::CLOSE
|
|
||||||
@current_message.write @buffer[0, info.size]
|
|
||||||
if info.final
|
|
||||||
message = @current_message.to_s
|
|
||||||
@on_close.try &.call(HTTP::WebSocket::CloseCode::NormalClosure, message)
|
|
||||||
close(HTTP::WebSocket::CloseCode::NormalClosure, message) unless closed?
|
|
||||||
@current_message.clear
|
|
||||||
return Close.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
raise "what the fuck?"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the websocket instance.
|
|
||||||
def ws
|
|
||||||
@ws
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class HTTP::WebSocket::Protocol
|
|
||||||
def io
|
|
||||||
@io
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocket < HTTP::WebSocket
|
|
||||||
getter? closed = false
|
|
||||||
|
|
||||||
def finalize
|
|
||||||
# puts "WrappedTCPFileDescriptor garbage collection!!"
|
|
||||||
# super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class WrappedTCPFileDescriptor < TCPSocket
|
|
||||||
# do not close the connection when garbage collected!!
|
|
||||||
def finalize
|
|
||||||
# puts "WrappedTCPFileDescriptor garbage collection!!"
|
|
||||||
# super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
Reference in New Issue