From 86495365d60af5ba25a0d01289c2c906857f849e Mon Sep 17 00:00:00 2001 From: Karchnu Date: Tue, 20 Oct 2020 19:15:50 +0200 Subject: [PATCH] IPC::JSON structure --- src/common/requests/auth.cr | 4 +- src/common/requests/download.cr | 4 +- src/common/requests/errors.cr | 14 +-- src/common/requests/login.cr | 4 +- src/common/requests/transfer.cr | 8 +- src/common/requests/upload.cr | 6 +- src/server/main.cr | 161 ++++++++++++-------------------- src/server/network.cr | 49 +--------- src/server/storage/file_info.cr | 6 +- src/structures | 2 +- 10 files changed, 89 insertions(+), 169 deletions(-) diff --git a/src/common/requests/auth.cr b/src/common/requests/auth.cr index bd50b03..9e9248f 100644 --- a/src/common/requests/auth.cr +++ b/src/common/requests/auth.cr @@ -10,7 +10,7 @@ # Quotas are not defined yet. #class FileStorage::Request -# JSONIPC.request Authentication, 10 do +# IPC::JSON.message Authentication, 10 do # property mid : String # autogenerated # property token : Token # property uploads : Array(UploadRequest)? @@ -70,7 +70,7 @@ #end # #class FileStorage::Response -# JSONIPC.request AuthenticationSuccess, 10 do +# IPC::JSON.message AuthenticationSuccess, 10 do # property mid : String # property responses : Array(FileStorage::Response) # def initialize(@mid, @responses) diff --git a/src/common/requests/download.cr b/src/common/requests/download.cr index 067b7d8..f9655ed 100644 --- a/src/common/requests/download.cr +++ b/src/common/requests/download.cr @@ -1,6 +1,6 @@ class FileStorage::Request - JSONIPC.request Download, 30 do + IPC::JSON.message Download, 30 do property mid : String # autogenerated property filedigest : String? # SHA256 digest of the file, used as ID property name : String? @@ -28,7 +28,7 @@ class FileStorage::Request end class FileStorage::Response - JSONIPC.request Download, 30 do + IPC::JSON.message Download, 30 do property mid : String property nb_chunks : Int32 def initialize(@mid, @nb_chunks) diff --git a/src/common/requests/errors.cr b/src/common/requests/errors.cr index 270d1f9..f8afcfb 100644 --- a/src/common/requests/errors.cr +++ b/src/common/requests/errors.cr @@ -1,5 +1,5 @@ class FileStorage::Errors - JSONIPC.request GenericError, 200 do + IPC::JSON.message GenericError, 200 do property mid : String property reason : String def initialize(@mid, @reason) @@ -7,7 +7,7 @@ class FileStorage::Errors end FileStorage.errors << GenericError - JSONIPC.request Authorization, 201 do + IPC::JSON.message Authorization, 201 do property mid : String property reason : String def initialize(@mid, @reason = "authorization") @@ -16,7 +16,7 @@ class FileStorage::Errors FileStorage.errors << Authorization # When uploading a chunk already present in the DB. - JSONIPC.request ChunkAlreadyUploaded, 202 do + IPC::JSON.message ChunkAlreadyUploaded, 202 do property mid : String property reason = "Chunk already present" property filedigest : String @@ -28,7 +28,7 @@ class FileStorage::Errors FileStorage.errors << ChunkAlreadyUploaded # You upload a chunk, but you are not the owner of the file. - JSONIPC.request ChunkUploadDenied, 203 do + IPC::JSON.message ChunkUploadDenied, 203 do property mid : String property reason = "This file is not yours" property filedigest : String @@ -39,7 +39,7 @@ class FileStorage::Errors FileStorage.errors << ChunkUploadDenied # When uploading a file already present in the DB. - JSONIPC.request FileExists, 204 do + IPC::JSON.message FileExists, 204 do property mid : String property reason = "file already present" property path : String @@ -51,7 +51,7 @@ class FileStorage::Errors FileStorage.errors << FileExists # When transfering a chunk for an inexistent file. - JSONIPC.request FileDoesNotExist, 205 do + IPC::JSON.message FileDoesNotExist, 205 do property mid : String property reason = "file does not exist" property filedigest : String @@ -62,7 +62,7 @@ class FileStorage::Errors FileStorage.errors << FileDoesNotExist # When a file was already fully uploaded. - JSONIPC.request FileFullyUploaded, 206 do + IPC::JSON.message FileFullyUploaded, 206 do property mid : String property reason = "file already uploaded fully" property path : String diff --git a/src/common/requests/login.cr b/src/common/requests/login.cr index 73e83b4..40be883 100644 --- a/src/common/requests/login.cr +++ b/src/common/requests/login.cr @@ -4,7 +4,7 @@ require "json" require "base64" class FileStorage::Request - JSONIPC.request Login, 0 do + IPC::JSON.message Login, 0 do property mid : String = "" property token : String @@ -34,7 +34,7 @@ class FileStorage::Request end class FileStorage::Response - JSONIPC.request Login, 5 do + IPC::JSON.message Login, 5 do property mid : String def initialize(@mid) end diff --git a/src/common/requests/transfer.cr b/src/common/requests/transfer.cr index 8140889..435d1ab 100644 --- a/src/common/requests/transfer.cr +++ b/src/common/requests/transfer.cr @@ -1,5 +1,5 @@ class FileStorage::Request - JSONIPC.request PutChunk, 40 do + IPC::JSON.message PutChunk, 40 do property mid : String # autogenerated property filedigest : String # SHA256 digest of the entire file # Chunk: @@ -36,7 +36,7 @@ class FileStorage::Request end FileStorage.requests << PutChunk - JSONIPC.request GetChunk, 41 do + IPC::JSON.message GetChunk, 41 do property mid : String # autogenerated property filedigest : String # SHA256 digest of the entire file property n : Int32 # chunk number @@ -64,7 +64,7 @@ class FileStorage::Request end class FileStorage::Response - JSONIPC.request PutChunk, 40 do + IPC::JSON.message PutChunk, 40 do property mid : String property file_digest : String property n : Int32 # chunk number @@ -72,7 +72,7 @@ class FileStorage::Response end end - JSONIPC.request GetChunk, 41 do + IPC::JSON.message GetChunk, 41 do property mid : String property file_digest : String # Chunk: diff --git a/src/common/requests/upload.cr b/src/common/requests/upload.cr index 8ac9f17..6bf8e2e 100644 --- a/src/common/requests/upload.cr +++ b/src/common/requests/upload.cr @@ -1,6 +1,6 @@ class FileStorage::Request - JSONIPC.request Upload, 20 do + IPC::JSON.message Upload, 20 do property mid : String # autogenerated property file : FileInfo def initialize(@file : FileInfo) @@ -26,7 +26,7 @@ class FileStorage::Request end class FileStorage::Response - JSONIPC.request Upload, 20 do + IPC::JSON.message Upload, 20 do property mid : String property path : String def initialize(@mid, @path) @@ -34,7 +34,7 @@ class FileStorage::Response end FileStorage.responses << Upload -# JSONIPC.request Responses, 100 do +# IPC::JSON.message Responses, 100 do # property mid : String # property responses : Array(Response | Errors) # a response for each request # property response : String diff --git a/src/server/main.cr b/src/server/main.cr index 87d929a..a31422c 100644 --- a/src/server/main.cr +++ b/src/server/main.cr @@ -29,6 +29,23 @@ require "./network.cr" require "dodb" +class IPC::JSON + def handle(filestoraged : FileStorage::Service, event : IPC::Event::Events) + raise "unknown request" + end +end + +module FileStorage + class Exception < ::Exception + end + class AuthorizationException < ::Exception + end + class NotLoggedException < ::Exception + end + class AdminAuthorizationException < ::Exception + end +end + class FileStorage::Service < IPC::Server # List of connected users (fd => uid). @@ -85,11 +102,49 @@ class FileStorage::Service < IPC::Server @storage.user_data_per_user.update_or_create user_data.uid.to_s, user_data end - # TODO: could be useful to send notifications. - #def send_notifications(fd : Int32, value : Int32) - # @all_connections.select(&.!=(fd)).each do |fd| ... end - # IPC::Connection.new(fd).send Response::Something.new ... - #end + def handle_request(event : IPC::Event::MessageReceived) + + request = FileStorage.requests.parse_ipc_json event.message + if request.nil? + raise "unknown request type" + end + + request_name = request.class.name.sub /^FileStorage::Request::/, "" + Baguette::Log.info "<< #{request_name}" + + response = FileStorage::Errors::GenericError.new "#{request.id}", "generic error" + + request_id = "#{request.id}" + + begin + response = request.handle self, event + rescue e : AuthorizationException + Baguette::Log.error "#{request_name} authorization error" + response = FileStorage::Errors::GenericError.new request_id, "authorization error" + rescue e : AdminAuthorizationException + Baguette::Log.error "#{request_name} no admin authorization" + response = FileStorage::Errors::GenericError.new request_id, "admin authorization error" + rescue e : NotLoggedException + Baguette::Log.error "#{request_name} user not logged" + response = FileStorage::Errors::GenericError.new request_id, "user not logged" + # Do not handle generic exception case: do not provide a response. + # rescue e # Generic case + # Baguette::Log.error "#{request_name} generic error #{e}" + end + + # If clients sent requests with an “id” field, it is copied + # in the responses. Allows identifying responses easily. + response.id = request.id + + send event.fd, response + + response_name = response.class.name.sub /^FileStorage::(Response|Errors)::/, "" + if response.responds_to?(:reason) + Baguette::Log.warning ">> #{response_name} (#{response.reason})" + else + Baguette::Log.info ">> #{response_name}" + end + end def run Baguette::Log.title "Starting filestoraged" @@ -128,102 +183,10 @@ class FileStorage::Service < IPC::Server Baguette::Log.debug "IPC::Event::Message: #{event.fd}" request_start = Time.utc - - request = parse_message FileStorage.requests, event.message - - if request.nil? - raise "unknown request type" - end - - Baguette::Log.info "<< #{request.class.name.sub /^FileStorage::Request::/, ""}" - - response = request.handle self, event - response_type = response.class.name - - if response.responds_to?(:reason) - Baguette::Log.warning ">> #{response_type.sub /^FileStorage::Errors::/, ""} (#{response.reason})" - else - Baguette::Log.info ">> #{response_type.sub /^FileStorage::Response::/, ""}" - end - - ################################################################# - # THERE START - ################################################################# - -# # The first message sent to the server has to be the AuthenticationMessage. -# # Users sent their token (JWT) to authenticate themselves. -# # The token contains the user id, its login and a few other parameters. -# # (see the authd documentation). -# # TODO: for now, the token is replaced by a hardcoded one, for debugging -# -# mtype = FileStorage::MessageType.new event.message.utype.to_i32 -# -# # First, the user has to be authenticated unless we are receiving its first message. -# userid = Context.connected_users[event.fd]? -# -# # If the user is not yet connected but does not try to perform authentication. -# if ! userid && mtype != FileStorage::MessageType::Authentication -# # TODO: replace this with an Error message. -# mid = "no message id" -# response = FileStorage::Response.new mid, "Not OK", "Action on non connected user" -# do_response event, response -# end -# -# case mtype -# when .authentication? -# Baguette::Log.debug "Receiving an authentication message" -# # Test if the client is already authenticated. -# if userid -# user = Context.users_status[userid] -# raise "Authentication message while the user was already connected: this should not happen" -# else -# Baguette::Log.debug "User is not currently connected" -# hdl_authentication event -# end -# -# when .upload_request? -# Baguette::Log.debug "Upload request" -# request = FileStorage::UploadRequest.from_json( -# String.new event.message.payload -# ) -# response = hdl_upload request, Context.users_status[userid] -# do_response event, response -# -# when .download_request? -# Baguette::Log.debug "Download request" -# request = FileStorage::DownloadRequest.from_json( -# String.new event.message.payload -# ) -# response = hdl_download request, Context.users_status[userid] -# do_response event, response -# -# when .transfer? -# # throw an error if the user isn't recorded -# unless user = Context.users_status[userid]? -# raise "The user isn't recorded in the users_status structure" -# end -# -# transfer = FileStorage::PutChunk.from_json( -# String.new event.message.payload -# ) -# response = hdl_transfer transfer, Context.users_status[userid] -# -# do_response event, response -# end - - ################################################################# - # FINISH - ################################################################# - - - # If clients sent requests with an “id” field, it is copied - # in the responses. Allows identifying responses easily. - response.id = request.id - - send event.fd, response - + handle_request event duration = Time.utc - request_start Baguette::Log.debug "request took: #{duration}" + when IPC::Event::MessageSent Baguette::Log.debug "IPC::Event::MessageSent: #{event.fd}" else diff --git a/src/server/network.cr b/src/server/network.cr index 804407e..5c96764 100644 --- a/src/server/network.cr +++ b/src/server/network.cr @@ -1,42 +1,16 @@ require "ipc" require "json" -class JSONIPC - include JSON::Serializable - - getter type = -1 - class_getter type = -1 - - property id : JSON::Any? - - def handle(service : IPC::Server, event : IPC::Event::Events) - raise "unimplemented" - end - - macro request(id, type, &block) - class {{id}} < ::JSONIPC - include JSON::Serializable - - @@type = {{type}} - def type - @@type - end - - {{yield}} - end - end -end - class IPC::Context - def send(fd : Int32, request : JSONIPC) + def send(fd : Int32, request : IPC::JSON) send fd, request.type.to_u8, request.to_json end end -class FileStorage - class_getter requests = [] of JSONIPC.class - class_getter responses = [] of JSONIPC.class - class_getter errors = [] of JSONIPC.class +module FileStorage + class_getter requests = [] of IPC::JSON.class + class_getter responses = [] of IPC::JSON.class + class_getter errors = [] of IPC::JSON.class end class FileStorage::Client < IPC::Client @@ -45,19 +19,6 @@ class FileStorage::Client < IPC::Client end end -def parse_message(requests : Array(JSONIPC.class), message : IPC::Message) : JSONIPC? - request_type = requests.find &.type.==(message.utype) - - payload = String.new message.payload - - if request_type.nil? - raise "invalid request type (#{message.utype})" - end - - request_type.from_json payload -end - - require "../common/requests/client.cr" require "../common/requests/login.cr" require "../common/requests/transfer.cr" diff --git a/src/server/storage/file_info.cr b/src/server/storage/file_info.cr index 0c4f9c1..bddec32 100644 --- a/src/server/storage/file_info.cr +++ b/src/server/storage/file_info.cr @@ -10,7 +10,7 @@ # }) #end -class FileStorage +module FileStorage # 1 MB read buffer, on-disk def self.file_reading_buffer_size @@ -45,10 +45,6 @@ class FileStorage end end - -class FileStorage::Exception < ::Exception -end - class FileStorage::Chunk include JSON::Serializable diff --git a/src/structures b/src/structures index 8606069..04893e6 100644 --- a/src/structures +++ b/src/structures @@ -1,4 +1,4 @@ -class FileStorage +module FileStorage def self.message_buffer_size def self.file_reading_buffer_size def self.data_digest(data : Bytes)