Comment puis-je télécharger efficacement un fichier volumineux avec Go?

106

Existe-t-il un moyen de télécharger un fichier volumineux à l'aide de Go qui stockera le contenu directement dans un fichier au lieu de tout stocker en mémoire avant de l'écrire dans un fichier? Parce que le fichier est si gros, le stockage de tout en mémoire avant de l'écrire dans un fichier va épuiser toute la mémoire.

Cory
la source

Réponses:

214

Je suppose que vous voulez dire télécharger via http (les vérifications d'erreur sont omises par souci de concision):

import ("net/http"; "io"; "os")
...
out, err := os.Create("output.txt")
defer out.Close()
...
resp, err := http.Get("http://example.com/")
defer resp.Body.Close()
...
n, err := io.Copy(out, resp.Body)

Le corps du http.Response est un lecteur, vous pouvez donc utiliser toutes les fonctions qui prennent un lecteur, pour, par exemple, lire un morceau à la fois plutôt que tout à la fois. Dans ce cas précis, io.Copy()fait le grognement pour vous.

Steve M
la source
85
Notez que io.Copylit 32 Ko (maximum) à partir de l'entrée et les écrit dans la sortie, puis se répète. Alors ne vous inquiétez pas de la mémoire.
Moshe Revah
comment annuler la progression du téléchargement?
Geln Yang
vous pouvez l'utiliser pour annuler le téléchargement après le délai imparticlient := http.Client{Timeout: 10 * time.Second,} client.Get("http://example.com/")
Bharath Kumar
55

Une version plus descriptive de la réponse de Steve M.

import (
    "os"
    "net/http"
    "io"
)

func downloadFile(filepath string, url string) (err error) {

  // Create the file
  out, err := os.Create(filepath)
  if err != nil  {
    return err
  }
  defer out.Close()

  // Get the data
  resp, err := http.Get(url)
  if err != nil {
    return err
  }
  defer resp.Body.Close()

  // Check server response
  if resp.StatusCode != http.StatusOK {
    return fmt.Errorf("bad status: %s", resp.Status)
  }

  // Writer the body to file
  _, err = io.Copy(out, resp.Body)
  if err != nil  {
    return err
  }

  return nil
}
Pablo Jomer
la source
1
Dans mon univers, j'ai implémenté un DSL qui nécessitait de télécharger un fichier ... c'était pratique pour Exec () curl jusqu'à ce que je tombe dans des problèmes de compatibilité OS et de chroot que je ne voulais vraiment pas configurer car c'est un modèle de sécurité raisonnable. Donc, U remplacez mon CURL par ce code et obtenez une amélioration des performances 10-15x. DUH!
Richard le
14

La réponse sélectionnée ci-dessus en utilisant io.Copyest exactement ce dont vous avez besoin, mais si vous êtes intéressé par des fonctionnalités supplémentaires telles que la reprise des téléchargements interrompus, l'attribution de noms automatiques aux fichiers, la validation de la somme de contrôle ou la surveillance de la progression de plusieurs téléchargements, consultez le package de capture .

Ryan Armstrong
la source
Pourriez-vous ajouter un extrait de code pour vous assurer que les informations ne seront pas perdues si le lien devient obsolète?
030
-6
  1. Voici un exemple. https://github.com/thbar/golang-playground/blob/master/download-files.go

  2. Aussi, je vous donne quelques codes qui pourraient vous aider.

code:

func HTTPDownload(uri string) ([]byte, error) {
    fmt.Printf("HTTPDownload From: %s.\n", uri)
    res, err := http.Get(uri)
    if err != nil {
        log.Fatal(err)
    }
    defer res.Body.Close()
    d, err := ioutil.ReadAll(res.Body)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("ReadFile: Size of download: %d\n", len(d))
    return d, err
}

func WriteFile(dst string, d []byte) error {
    fmt.Printf("WriteFile: Size of download: %d\n", len(d))
    err := ioutil.WriteFile(dst, d, 0444)
    if err != nil {
        log.Fatal(err)
    }
    return err
}

func DownloadToFile(uri string, dst string) {
    fmt.Printf("DownloadToFile From: %s.\n", uri)
    if d, err := HTTPDownload(uri); err == nil {
        fmt.Printf("downloaded %s.\n", uri)
        if WriteFile(dst, d) == nil {
            fmt.Printf("saved %s as %s\n", uri, dst)
        }
    }
}
TeeTracker
la source
13
Cet exemple lit tout le contenu en mémoire, avec l'extension ioutil.ReadAll(). C'est très bien, tant que vous avez affaire à de petits fichiers.
eduncan911
13
@ eduncan911, mais ce n'est pas bien pour cette question qui parle explicitement de gros fichiers et ne veut pas tout aspirer en mémoire.
Dave C
2
Exactement, c'est pourquoi je l'ai commenté - pour que les autres sachent également ne pas l'utiliser pour les gros fichiers.
eduncan911
4
Ce n'est pas une réponse bénigne et devrait en fait être supprimée. L'utilisation de ReadAll parmi une grande pile de code est un problème latent qui attend jusqu'à ce qu'un gros fichier soit utilisé. Ce qui se passe, c'est que s'il y a ReadAll sur des fichiers volumineux, la réponse est généralement d'accompagner la consommation de mémoire élevée et l'augmentation des factures AWS jusqu'à ce que quelque chose échoue. Au moment où le problème est découvert, les factures sont déjà élevées.
Rob