Introduction
Dans cette quatrième partie, nous allons encapsuler la partie serveur HTTP dans une classe dédiée.
C'est partie.
Etats des lieux
Notre fichier principal ressemble à ça
require "http/server"
require "./singleton"
require "./config"
require "./repository"
# Define a route for the todo list page.
server = HTTP::Server.new() do |context|
request = context.request
case request.path
when "/"
items = Repository.new(Config::TODO_FILE_PATH).all
context.response.content_type = "text/html"
context.response.print(ECR.render "src/template.ecr")
when "/add"
if request.method == "POST"
body = request.body
if body.nil?
context.response.status_code = 400
context.response.print("Body is required")
else
params = body.gets_to_end
uri_params = URI::Params.parse(params)
item = uri_params.fetch("item",nil)
if item.nil?
context.response.status_code = 400
context.response.print("Body is required")
else
Repository.new(Config::TODO_FILE_PATH).add(item)
end
context.response.status_code = 302
context.response.headers["Location"] = "/"
end
else
context.response.status_code = 405
context.response.headers["Allow"] = "POST"
context.response.print("Method not allowed")
end
else
context.response.status_code = 404
context.response.print("Not found")
end
end
server.bind_tcp Config::BIND_IP, Config::PORT
if Config.environment != "test"
server.listen
end
Dans un premier temps, notre but va être d'isoler la partie serveur dans un objet.
Vision objet
Notre serveur est défini par :
- ce qu'il écoute : des requêtes au protocol HTTP sur une connexion TCP_IP (IP, PORT)
- ce qu'il renvoie.
Nous allons traiter la première partie.
Passons aux tests.
Quand nous initialisons le serveur HTTP, nous avons besoin de définir l'ip et le port d'écoute. Nous avons aussi besoin que cela fonctionne sans cette saisie. Créons le fichier ./spec/server_spec.cr
require "./spec_helper"
describe Server do
describe "#new" do
it "set default ip at 0.0.0.0" do
Server.new.bind_ip.should eq "0.0.0.0"
end
it "set default port at 3000" do
Server.new.port.should eq 3000
end
it "set bind_ip with argument value" do
Server.new(bind_ip: "254.43.35.34").bind_ip.should eq "254.43.35.34"
end
it "set port with argument value" do
Server.new(port: 4567).port.should eq 4567
end
it "set ip and port with argument value" do
server = Server.new(bind_ip: "123.123.123.123", port: 9876)
server.bind_ip.should eq "123.123.123.123"
server.port.should eq 9876
end
end
end
Ces tests sont simples mais permettent de montrer la syntaxe de Crystal sur les signatures de méthodes. En passant, dans la signature, le nom des variables de celle-ci, je me permets de laisser le choix de l'implémentation pour plus tard.
En revanche, cela impose d'avoir des valeurs par défaut dans notre implémentation.
Implémentation de notre objet Server
L'ip et le port d'écoute n'ont pas besoin d'être changé après l'initialisation. Je propose donc de ne définir que des getter
.
La partie serveur n'a pas à être exposé en dehors de la classe à mon humble avis.
L'implémentation donne alors :
class Server
getter bind_ip : String
getter port : Int32
def initialize(@bind_ip = "0.0.0.0", @port = 3000)
@server = HTTP::Server.new() do |context|
request = context.request
case request.path
...
end
end
end
def run
@server.bind_tcp @bind_ip, @port
@server.listen
end
end
getter
est une macro qui va définir la méthode def bind_ip ...
Si nous avions mis un setter
, cela aurait défini la méthode def bind_ip=
. Le la macro property
réalise les deux.
Ensuite, dans la signature de la méthode initialize
, nous pouvons mettre directement les variables de classe en lieu et place de :
def initialize(bind_ip)
@bind_ip = bind_ip
end
et pour définir une valeur par défaut, nous pouvons le faire dans la signature comme je l'ai fait ici.
Note importante , si une variable de classe est obligatoire (ne peut être nulle), le compilateur vérifiera que cette variable est bien renseignée à la fin de la méthode initialize
. Si ce n'est pas le cas, une erreur de compilation apparaîtra.
Conclusion
Cette partie est courte mais je voulais revenir sur l'initialisation d'un objet et l'importance de la signature de la méthode initialize
Dans la prochaine partie, nous allons nous attaquer à la partie routage. Pour cela, nous allons avoir besoin d'utiliser les REGEX.
Top comments (0)