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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
require "./lib/json_yaml_arrays"
|
||||
require "./lib/io"
|
||||
require "./lib/websocket"
|
||||
require "./lib/tcp"
|
||||
|
|
Reference in New Issue