Come posso scaricare in modo efficiente un file di grandi dimensioni utilizzando Go?


106

Esiste un modo per scaricare un file di grandi dimensioni utilizzando Go che memorizzerà il contenuto direttamente in un file invece di archiviarlo tutto in memoria prima di scriverlo su un file? Poiché il file è così grande, archiviarlo tutto in memoria prima di scriverlo su un file userà tutta la memoria.

Risposte:


214

Suppongo che tu intenda scaricare tramite http (controlli di errore omessi per brevità):

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)

Il corpo di http.Response è un Reader, quindi puoi utilizzare qualsiasi funzione che richieda un Reader, ad esempio, per leggere un pezzo alla volta piuttosto che tutto in una volta. In questo caso specifico, io.Copy()fa il grugnito per te.


85
Si noti che io.Copylegge 32kb (massimo) dall'input e li scrive nell'output, quindi si ripete. Quindi non preoccuparti della memoria.
Moshe Revah

come annullare l'avanzamento del download?
Geln Yang

puoi usarlo per annullare il download dopo il timeout client := http.Client{Timeout: 10 * time.Second,} client.Get("http://example.com/")
stabilito

55

Una versione più descrittiva della risposta di 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
}

1
Nel mio universo ho implementato un DSL che necessitava di scaricare un file ... era conveniente eseguire Exec () curl fino a quando non sono caduto in alcuni problemi di compatibilità del sistema operativo e chroot che non volevo configurare perché è un modello di sicurezza ragionevole. Quindi sostituisci il mio CURL con questo codice e ottieni un miglioramento delle prestazioni di 10-15 volte. DUH!
Richard,

14

La risposta selezionata sopra utilizzando io.Copyè esattamente ciò di cui hai bisogno, ma se sei interessato a funzionalità aggiuntive come la ripresa di download interrotti, la denominazione automatica dei file, la convalida del checksum o il monitoraggio dell'avanzamento di più download, controlla il pacchetto grab .


Potresti aggiungere uno snippet di codice per assicurarti che le informazioni non andranno perse se il collegamento diventa obsoleto?
030

-6
  1. Ecco un esempio. https://github.com/thbar/golang-playground/blob/master/download-files.go

  2. Inoltre ti do alcuni codici che potrebbero aiutarti.

codice:

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)
        }
    }
}

13
Questo esempio legge l'intero contenuto in memoria, con l'estensione ioutil.ReadAll(). Va bene, purché tu abbia a che fare con piccoli file.
eduncan911

13
@ eduncan911, ma non va bene per questa domanda che parla esplicitamente di file di grandi dimensioni e non vuole risucchiare tutto nella memoria.
Dave C

2
Esatto, è per questo che ho commentato così - affinché anche gli altri sappiano di non usarlo per file di grandi dimensioni.
eduncan911

4
Questa non è una risposta positiva e dovrebbe essere effettivamente rimossa. L'utilizzo di ReadAll tra una grande pila di codice è un problema latente in attesa che venga utilizzato un file di grandi dimensioni. Quello che succede è che se ci sono ReadAll su file di grandi dimensioni, la risposta di solito è quella di accompagnare l'elevato consumo di memoria e l'aumento delle fatture AWS fino a quando qualcosa non riesce. Quando il problema viene scoperto, le bollette sono già alte.
Rob,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.