# frozen_string_literal: true require "socket" module Toycol class Proxy include Helper def initialize(host, port) @host = host @port = port @request_method = nil @path = nil @query = nil @input = nil @protocol = ::Toycol::Protocol @proxy = TCPServer.new(@host, @port) end CHUNK_SIZE = 1024 * 16 def start puts <<~MESSAGE Toycol is running on #{@host}:#{@port} => Use Ctrl-C to stop MESSAGE loop do trap(:INT) { shutdown } @client = @proxy.accept while !@client.closed? && !@client.eof? begin request = @client.readpartial(CHUNK_SIZE) puts "[Toycol] Received message: #{request.inspect.chomp}" safe_execution! { @protocol.run!(request) } assign_parsed_attributes! http_request_message = build_http_request_message puts "[Toycol] Message has been translated to HTTP request message: #{http_request_message.inspect}" transfer_to_server(http_request_message) rescue StandardError => e puts "#{e.class} #{e.message} - closing socket." e.backtrace.each { |l| puts "\t#{l}" } @proxy.close @client.close end end @client.close end end private def assign_parsed_attributes! @request_method = @protocol.request_method @path = @protocol.request_path @query = @protocol.query @input = @protocol.input end def build_http_request_message request_message = "#{request_line}#{request_header}\r\n" request_message += @input if @input request_message end def request_line "#{@request_method} #{request_path} HTTP/1.1\r\n" end def request_path return @path unless @request_method == "GET" "#{@path}#{"?#{@query}" if @query && !@query.empty?}" end def request_header "Content-Length: #{@input&.bytesize || 0}\r\n" end def transfer_to_server(request_message) UNIXSocket.open(Toycol::UNIX_SOCKET_PATH) do |server| server.write request_message server.close_write puts "[Toycol] Successed to Send HTTP request message to server" response_message = [] response_message << server.readpartial(CHUNK_SIZE) until server.eof? response_message = response_message.join puts "[Toycol] Received response message from server: #{response_message.lines.first}" _, status_code, status_message = response_message.lines.first.split if (custom_message = @protocol.status_message(status_code.to_i)) != status_message response_message = response_message.sub(status_message, custom_message) puts "[Toycol] Status message has been translated to custom status message: #{custom_message}" end @client.write response_message @client.close_write puts "[Toycol] Finished to response to client" server.close end end def shutdown puts "[Toycol] Catched SIGINT -> Stop to server" exit end end end