A src/handlers/socket_handler.cr => src/handlers/socket_handler.cr +54 -0
@@ 0,0 1,54 @@
+class Handlers::SocketHandler
+ include HTTP::Handler
+
+ def initialize
+ @ws_handler = HTTP::WebSocketHandler.new(&->ws(HTTP::WebSocket, HTTP::Server::Context))
+ end
+
+ def serialize(nonce, result)
+ JSON.build do |builder|
+ builder.object do
+ builder.field("nonce", nonce.to_s)
+ builder.string "type"
+ if result.is_a?(Job::PlayerResult)
+ builder.string "player_result"
+ builder.string "data"
+ result.to_json(builder)
+ elsif result.is_a?(Job::BatchPlayers)
+ builder.string "batch_players"
+ builder.string "data"
+ result.to_json(builder)
+ elsif result.is_a?(Job::Error)
+ builder.string "error"
+ builder.field("message", result.message)
+ builder.field("data", result.id)
+ elsif result.is_a?(Exception)
+ builder.string "error"
+ builder.field("message", result.message)
+ end
+ end
+ end
+ end
+
+
+ def ws(socket : HTTP::WebSocket, context : HTTP::Server::Context)
+ socket.on_message do |message|
+ nonce = JobController::Nonce.from_json(message, "nonce")
+ JobController.dispatch(nonce) do |result|
+ payload = serialize(nonce, result)
+ socket.send(payload)
+ end
+ rescue ex : JSON::ParseException | KeyError
+ payload = serialize(nonce, ex)
+ socket.send(payload)
+ end
+ end
+
+ def call(context : HTTP::Server::Context)
+ case context.request.path
+ when "/api/relay" then @ws_handler.call(context)
+ else
+ call_next(context)
+ end
+ end
+end<
\ No newline at end of file
R src/web.cr => src/handlers/web_router.cr +71 -100
@@ 1,114 1,85 @@
-require "raze"
require "kilt/slang"
-require "http/client"
-require "logger"
-require "./steam"
-require "./mappings"
-require "./job_controller"
-
-def serialize(nonce, result)
- JSON.build do |builder|
- builder.object do
- builder.field("nonce", nonce.to_s)
- builder.string "type"
- if result.is_a?(Job::PlayerResult)
- builder.string "player_result"
- builder.string "data"
- result.to_json(builder)
- elsif result.is_a?(Job::BatchPlayers)
- builder.string "batch_players"
- builder.string "data"
- result.to_json(builder)
- elsif result.is_a?(Job::Error)
- builder.string "error"
- builder.field("message", result.message)
- builder.field("data", result.id)
- elsif result.is_a?(Exception)
- builder.string "error"
- builder.field("message", result.message)
- end
- end
+
+class Handlers::WebRouter
+ include HTTP::Handler
+
+ def initialize
+ @logger = Log.for("web")
+ @client = Steam::Client.new(ENV["STEAM_API_KEY"], @logger.for("Steam"))
+
+ JobController.logger = @logger.for("JobController")
end
-end
-
-logger = Logger.new(STDOUT)
-client = Steam::Client.new(ENV["STEAM_API_KEY"], logger)
-JobController.logger = logger
-
-get "/" do |ctx|
- render("views/index.slang")
-end
-
-post "/api/check" do |ctx|
- # TODO: validate request
- raw_ids = ctx.query["steamids"].split(',')
-
- # ctx.halt({"error": ""}, 400) if !raw_ids || raw_ids == ""
-
- nonce = JobController.create(raw_ids.size + 1) do |job|
- ids = [] of Steam::ID
- raw_ids.each do |string_id|
- begin
- id = Steam::ID.new(string_id)
- # Enforce Public universe bit (STEAM_1..) and Individual account type:
- id.universe = :public
- id.account_type = :individual
- id.instance = 1
-
- ids << id
- rescue ex : Steam::ID::Error
- job.send Job::Error.new(string_id, ex.message)
- end
+
+ def handle_check(context : HTTP::Server::Context)
+ if context.request.method != "POST"
+ context.response.respond_with_status(:method_not_allowed)
+ return
end
- # TODO: check if more than 100, maybe do this in middleware
- # for request validation
- players = client.get_players(ids)
- player_ids = players.map &.id
-
- ids.each do |id|
- unless player_ids.includes? id
- job.send Job::Error.new(
- id.to_s(Steam::ID::Format::Default),
- "Steam ID not found: #{id.to_s(Steam::ID::Format::Default)}"
- )
+ @logger.info { "Handling check endpoint!" }
+ raw_ids = context.request.query_params["steamids"].split(',')
+
+ # ctx.halt({"error": ""}, 400) if !raw_ids || raw_ids == ""
+
+ nonce = JobController.create(raw_ids.size + 1) do |job|
+ ids = [] of Steam::ID
+ raw_ids.each do |string_id|
+ begin
+ id = Steam::ID.new(string_id)
+ # Enforce Public universe bit (STEAM_1..) and Individual account type:
+ id.universe = :public
+ id.account_type = :individual
+ id.instance = 1
+
+ ids << id
+ rescue ex : Steam::ID::Error
+ job.send Job::Error.new(string_id, ex.message)
+ end
end
- end
- lender_ids = [] of Steam::ID
+ # TODO: check if more than 100, maybe do this in middleware
+ # for request validation
+ players = @client.get_players(ids)
+ player_ids = players.map &.id
- players.each do |player|
- lender_id = client.get_lender_id(player.id)
- lender_ids << lender_id unless lender_id.nil?
+ invalid_ids = [] of Steam::ID
+ ids.each do |id|
+ unless player_ids.includes? id
+ invalid_ids << id
+ job.send Job::Error.new(
+ id.to_s(Steam::ID::Format::Default),
+ "Not found"
+ )
+ end
+ end
- job.send Job::PlayerResult.new(player, lender_id)
- end
+ lender_ids = [] of Steam::ID
- unless lender_ids.empty?
- lenders = client.get_players(lender_ids)
- job.send Job::BatchPlayers.new(lenders)
- else
- job.send Job::BatchPlayers.new
+ players.each do |player|
+ lender_id = @client.get_lender_id(player.id)
+ lender_ids << lender_id unless lender_id.nil?
+
+ job.send Job::PlayerResult.new(player, lender_id)
+ end
+
+ unless lender_ids.empty?
+ lenders = @client.get_players(lender_ids)
+ job.send Job::BatchPlayers.new(lenders)
+ else
+ job.send Job::BatchPlayers.new
+ end
end
+
+ context.response.content_type = "application/json"
+ context.response.puts({nonce: nonce}.to_json)
end
- {nonce: nonce}.to_json
-end
-
-# WS Payloads (spec for each of these):
-# {"nonce": 123, "type": "error", "message": "bad id"}
-# {"nonce": 123, "type": "result", "data": {"player": {}, "lender_id": "123"}}
-# {"nonce": 123, "type": "result", "data": {"player": {}, "lender_id": null}}
-ws "/api/relay" do |ws, ctx|
- ws.on_message do |message|
- # Client sends: {"nonce": 123}
- nonce = JobController::Nonce.from_json(message, "nonce")
- JobController.dispatch(nonce) do |result|
- payload = serialize(nonce, result)
- ws.send(payload)
+ def call(context : HTTP::Server::Context)
+ case context.request.path
+ when "/" then context.response.print(Kilt.render("views/index.slang"))
+ when "/api/check" then handle_check(context)
+ else
+ call_next(context)
end
- rescue ex : JSON::ParseException | KeyError
- payload = serialize(nonce, ex)
- ws.send(payload)
end
-end
+end<
\ No newline at end of file
M src/run.cr => src/run.cr +22 -2
@@ 1,3 1,23 @@
-require "./web"
+require "http/server"
+require "log"
+require "./steam"
+require "./mappings"
+require "./job_controller"
+require "./handlers/web_router"
+require "./handlers/socket_handler"
-Raze.run
+server = HTTP::Server.new([
+ HTTP::ErrorHandler.new,
+ HTTP::LogHandler.new,
+ HTTP::StaticFileHandler.new("static",
+ directory_listing: false),
+ Handlers::WebRouter.new,
+ Handlers::SocketHandler.new
+])
+
+backend = Log::IOBackend.new
+Log.builder.bind "*", :debug, backend
+
+Log.info { "Starting server..." }
+server.bind_tcp "127.0.0.1", 8080
+server.listen