Exploration de l'API Standard de Crystal-lang PART 1 (HTTP::Server, DIR, ECR, File)
Tags: crystal
Published: 2023-06-29 15:37:10

---
title: Exploration de l'API Standard de Crystal-lang PART 1 (HTTP::Server, DIR, ECR, File)
published: true
description: Première partie avec usage de File, HTTP::Server DIR, et ECR
tags: crystal
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-06-29 13:24 +0000
---

# Introduction Générale

Cette article est le premier d'une série. Cette série vise à explorer l'API standard de Crystal-lang à travers la réalisation d'une application.

L'application cible est un gestionnaire de la méthode "[Getting Things Done](https://fr.wikipedia.org/wiki/Getting_Things_Done)" avec une partie web, une api HTTP et une CLI.

# Cette article

Dans cette article en particulier, nous allons créer une page d'ajout de tâches et une persistance en fichier. Nous allons utiliser : 
- [HTTP::Server](https://crystal-lang.org/api/1.8.2/HTTP/Server.html) pour exposer le service
- [ECR](https://crystal-lang.org/api/1.8.2/ECR.html) pour concevoir l'écran
- [File](https://crystal-lang.org/api/1.8.2/File.html) pour la persistance
- [Dir](https://crystal-lang.org/api/1.8.2/Dir.html) pour la gestion de l’arborescence

# C'est parti

## Créons des tâches dans un fichier.

Pour l'instant, la structure d'une tâche est une chaine de caractères, chaque ligne dans le fichier sera une tâche.

Définissons une constante pour le chemin vers le fichier de persistance.

```crystal
TODO_FILE_PATH = File.join(Dir.current, "todo.txt")
```

`Dir.current` renvoie le chemin courant de l’exécutable.

`File.join` permet de créer le chemin en y ajoutant le nom du fichier voulu. Cette méthode prend autant d'élément que vous le souhaitez et va créer un chemin système valide en fonction de votre OS.

Maintenant que nous avons le chemin, nous allons écrire la méthode d'ajout de tâche au fichier.

```crystal
def add_task(item : String)
  file = File.open(TODO_FILE_PATH, "a")
  file.puts item
  file.close
end
```

Nous pouvons aussi l'écrire sous cette forme

```crystal
def add_task(item : String)
  File.open(TODO_FILE_PATH, "a") do |file|
    file.puts item
  end
end
```

Dans le deuxième cas, le `File.close` est implicitement exécuté à la fin du bloc `File.open`.

`File.open` va ouvrir le fichier avec le mode passé en argument. Par défaut, c'est le mode "r" pour "read". Vous avez aussi :
- w pour write, créer ou remplace le fichier avec le contenu.
- rw pour read-write, ouvre le fichier et remplace le contenu.
- a pour append, ajoute le contenu à la fin du fichier.

Nous souhaitons ajouter une tâche, le choix se porte sur le mode "a".

`File.puts` envoie, à la fin du fichier, la chaîne de caractère passée en argument.

Avec ce que nous venons de voir, ajoutons une méthode pour récupérer la liste de toutes les tâches.

```crystal
def task_list : Array(String)
  File.read_lines(TODO_FILE_PATH)
end
```

`File.read_lines` a l'avantage d'ouvrir le fichier, lire le contenu et le transforme en tableau puis refermer le fichier.

Nous en avons fini avec la persistance pour cette article. Passons à la partie mise en page.

## Créons un écran html.

Pour faire un template et embarquer du code crystal, l'API standard fournit ECR.

Dans son mode d'usage le plus simple, il faut :
- définir des variables
- exécuter un ECR.render avec le fichier template en argument. (Toutes les variables du même scope que le ECR.render seront transmises)

Dans notre cas, nous avons besoin de la liste des tâches (déjà réalisé ci-dessus) et du template. Nous sommes sur du web, il s'agit d'un template HTML.

```html
  <!DOCTYPE html>
  <html>
    <head>
      <title>Todo List</title>
    </head>
    <body>
      <h1>Todo List</h1>
      <ul>
        <% items.each do |item| %>
          <li><%= item %></li>
        <% end %>
      </ul>
    </body>
  </html>
```

Je vous passe la partie HTML réduit à sa plus simple expression.

Pour ce qui nous intéresse, nous pouvons voir l'usage des balise <% %> et <%= %> pour l'insertion de code crystal. Ici, un simple parcours de la liste des items pour ensuite les afficher en liste HTML.

Voyons comment créer un serveur HTTP pour afficher cette page

## Le serveur http.

Crystal-lang vient avec un serveur http dans son API standard.

le code de la documentation est 

```crystal
require "http/server"

server = HTTP::Server.new do |context|
  context.response.content_type = "text/plain"
  context.response.print "Hello world!"
end

address = server.bind_tcp 8080
puts "Listening on http://#{address}"
server.listen
```

Nous voyons que nous avons un HTTP::Context qui est à disposition. Cette objet contient la requête et permet de définir la réponse. A minima, nous devons définir le content-type et le body.

Une fois la gestion du contexte établi, nous pouvons configurer l'ip et le port d'écoute puis lancer l’écoute.

dans notre cas, nous voulons afficher notre page quand la requête est sur le chemin "/".

```crystal
require "http/server"

server = HTTP::Server.new do |context|
  request = context.request

  case request.path
  when "/"
    items = task_list
    context.response.content_type = "text/html"
    context.response.print(ECR.render "src/template.ecr")
  else
    context.response.status_code = 404
    context.response.print("Not found")
  end
end

address = server.bind_tcp 8080
puts "Listening on http://#{address}"
server.listen
```

En lançant l'application, vous aurez votre liste de tâches. Pour l'exemple, je vous conseille de créer un fichier "todo.txt" à la racine de votre projet et d'y ajouter des lignes.

Passons à la possibilité d'ajouter des tâches via notre IHM.

### Ajout du formulaire

reprenons notre template et ajoutons un formulaire simple d'envoi du texte de la tâche.

```html
  <!DOCTYPE html>
  <html>
    <head>
      <title>Todo List</title>
    </head>
    <body>
      <h1>Todo List</h1>
      <ul>
        <% items.each do |item| %>
          <li><%= item %></li>
        <% end %> 
      </ul>
      <form method="post" action="/add">
        <label for="item">New item:</label>
        <input type="text" id="item" name="item">
        <button type="submit">Add</button>
      </form>
    </body>
  </html>
```

L'ajout d'une tâche passe par une requête POST sur le chemin "/add".

Reprenons notre serveur pour en ajouter la gestion de cet appel.

```crystal
...
  server = HTTP::Server.new() do |context|
    request = context.request
  
    case request.path
    when "/" 
      items = task_list
      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
            add_task(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
...
```

Oui, je sais, beaucoup de bloc if et case imbriqué. Ce code est là pour gérer les différentes erreurs sans utiliser de lib externes.

La nouveauté de ce bloc est `URI::params.parse` qui récupère les objet du formulaire à partir de la lecture du body de la requête.

Voici ce qui termine ce premier article.

# Conclusion.

Nous avons vu File, Dir, HTTP::Server et ECR. Dans la suite, nous allons voir comment tester tout ça.

Prochaine API : [Spec](https://crystal-lang.org/api/1.8.2/Spec.html)

# Note :
[repository gitlab](https://gitlab.com/JadeKharats/gtd/-/tree/e9575d8c679bd945f39c5b216a5a636457947187)