lib modifications: in lib/

master
Karchnu 2020-11-03 18:40:57 +01:00
parent 53ed216132
commit da683e96fa
5 changed files with 291 additions and 134 deletions

21
src/lib/io.cr Normal file
View File

@ -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

151
src/lib/json_yaml_arrays.cr Normal file
View File

@ -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

8
src/lib/tcp.cr Normal file
View File

@ -0,0 +1,8 @@
class WrappedTCPFileDescriptor < TCPSocket
# do not close the connection when garbage collected!!
def finalize
# puts "WrappedTCPFileDescriptor garbage collection!!"
# super
end
end

107
src/lib/websocket.cr Normal file
View File

@ -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

View File

@ -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"