Introduction : Pourquoi pourriez-vous avoir besoin de transformer des octets en flux ?
\n\nImaginez la situation : vous avez écrit un excellent service en Go qui reçoit des données d'une API externe. La réponse arrive sous forme de tableau d'octets ([]byte). Tout irait bien, mais voilà le problème — la fonction suivante dans votre pipeline attend en entrée non pas une tranche d'octets, mais une interface io.Reader. Que faire ? Paniquer ? Réinventer la roue ? Non ! Il suffit de penser à bytes.NewReader.
Cette question est l'une des plus populaires sur Stack Overflow (des milliers de vues !). Et ce n'est pas pour rien. En Go, le concept d'io.Reader est fondamental pour travailler avec des flux de données : fichiers, connexions réseau, réponses HTTP et, bien sûr, simplement des octets en mémoire. Si vous êtes un développeur Go débutant, tôt ou tard vous vous heurterez à ce mur. Voyons comment le contourner élégamment et efficacement.
Dans cet article, nous ne nous contenterons pas de donner la commande sèche « utilisez bytes.NewReader ». Nous expliquerons pourquoi cela fonctionne, quelles sont les alternatives, et montrerons des exemples concrets de la vie réelle. C'est parti !
Théorie : Qu'est-ce que io.Reader et avec quoi se mange-t-il ?
\n\nAvant de convertir, rappelons ce qu'est io.Reader. C'est une interface définie dans la bibliothèque standard de Go :
type Reader interface {\n Read(p []byte) (n int, err error)\n}\n\nTout est génialement simple. Toute structure qui possède une méthode Read(p []byte) (int, error) satisfait automatiquement l'interface io.Reader. Cela permet d'écrire du code universel pour lire des données depuis n'importe quelle source : fichier, socket, chaîne de caractères, tranche d'octets.
Pourquoi est-ce génial ? Parce que vous pouvez écrire une seule fonction qui analyse du JSON, du XML ou simplement copie des données, sans vous soucier de leur provenance. Vous avez seulement besoin d'un io.Reader. C'est une abstraction de flux.
Mais que faire si vous avez déjà des données en mémoire (par exemple, []byte) ? Vous avez besoin d'un objet qui « fera semblant » d'être un flux, tout en lisant à partir de votre tranche. C'est là que bytes.NewReader entre en scène.
Méthode principale : bytes.NewReader
\n\nC'est le moyen le plus simple et le plus correct. La fonction bytes.NewReader prend un []byte et retourne un pointeur vers une structure bytes.Reader, qui implémente io.Reader (ainsi que io.ReadSeeker, io.WriterTo et d'autres interfaces utiles).
Exemple 1 : Utilisation de base
\n\npackage main\n\nimport (\n "bytes"\n "fmt"\n "io"\n "log"\n)\n\nfunc main() {\n // Notre tranche d'octets source\n data := []byte("Bonjour le monde ! Ce sont des octets.")\n\n // Création d'un Reader à partir de la tranche d'octets\n reader := bytes.NewReader(data)\n\n // Lecture des données depuis le reader dans un buffer\n buf := make([]byte, 10) // lecture par paquets de 10 octets\n for {\n n, err := reader.Read(buf)\n if err != nil {\n if err == io.EOF {\n break // fin de la lecture\n }\n log.Fatal(err)\n }\n fmt.Printf("Lu %d octets : %s\\n", n, buf[:n])\n }\n // Output:\n // Lu 10 octets : Bonjour le\n // Lu 10 octets : monde ! C\n // Lu 10 octets : e sont des\n // Lu 6 octets : octets.\n}\n\nNotez que bytes.Reader se comporte comme un véritable flux. Lorsque les données sont épuisées, il retourne io.EOF. Tout est correct.
Exemple 2 : Utilisation avec io.Copy
\n\nLe scénario le plus courant est de copier les octets quelque part (dans un fichier, dans une réponse HTTP). Pour cela, io.Copy est idéal :
package main\n\nimport (\n "bytes"\n "fmt"\n "io"\n "os"\n)\n\nfunc main() {\n data := []byte("Sauvegardons ces données dans un fichier.")\n reader := bytes.NewReader(data)\n\n // Création du fichier\n file, err := os.Create("output.txt")\n if err != nil {\n panic(err)\n }\n defer file.Close()\n\n // Copie depuis le reader vers le fichier\n