diff --git a/src/lib/io.cr b/src/lib/io.cr new file mode 100644 index 0000000..1326859 --- /dev/null +++ b/src/lib/io.cr @@ -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 + diff --git a/src/lib/json_yaml_arrays.cr b/src/lib/json_yaml_arrays.cr new file mode 100644 index 0000000..5eb512c --- /dev/null +++ b/src/lib/json_yaml_arrays.cr @@ -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):[ \t]+NOW[ \t]*(?[-+])[ \t]*(?rand)?[ \t]*(?[0-9]+) *(?[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):[ \t]+NOW[ \t]*$/ + date = $~["date"] + yaml_date = Time::Format::YAML_DATE.format(Time.local) + + new_lines << "#{date}: #{yaml_date}" + next + when /(?[a-z]+_date):[ \t]+NOW[ \t]*$/ + date = $~["date"] + yaml_date = Time::Format::YAML_DATE.format(Time.local) + + new_lines << "#{date}: #{yaml_date}" + next + # when /(?[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 diff --git a/src/lib/tcp.cr b/src/lib/tcp.cr new file mode 100644 index 0000000..dcdad23 --- /dev/null +++ b/src/lib/tcp.cr @@ -0,0 +1,8 @@ + +class WrappedTCPFileDescriptor < TCPSocket + # do not close the connection when garbage collected!! + def finalize + # puts "WrappedTCPFileDescriptor garbage collection!!" + # super + end +end diff --git a/src/lib/websocket.cr b/src/lib/websocket.cr new file mode 100644 index 0000000..25ced60 --- /dev/null +++ b/src/lib/websocket.cr @@ -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 diff --git a/src/lib_modifications.cr b/src/lib_modifications.cr index f97a51e..c6a9c5a 100644 --- a/src/lib_modifications.cr +++ b/src/lib_modifications.cr @@ -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"