Go를 사용하여 대용량 파일을 효율적으로 다운로드하려면 어떻게해야합니까?


106

Go를 사용하여 대용량 파일을 다운로드하여 파일에 쓰기 전에 콘텐츠를 모두 메모리에 저장하는 대신 파일에 직접 저장하는 방법이 있습니까? 파일이 너무 크기 때문에 파일에 쓰기 전에 메모리에 모두 저장하면 모든 메모리가 소모됩니다.

답변:


214

http를 통한 다운로드를 의미한다고 가정합니다 (간결성을 위해 오류 검사 생략).

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)

http.Response의 본문은 독자이므로 독자를 사용하는 모든 기능을 사용할 수 있습니다 (예 : 한 번에 모두 읽기보다는 한 번에 청크 읽기). 이 특정한 경우에, io.Copy()당신을 위해 gruntwork합니다.


85
io.Copy입력에서 32kb (최대) 를 읽고 출력에 쓴 다음 반복합니다. 따라서 기억에 대해 걱정하지 마십시오.
Moshe Revah

다운로드 진행을 취소하는 방법?
Geln Yang

이 기능을 사용하여 지정된 시간 초과 후 다운로드를 취소 할 수 있습니다.client := http.Client{Timeout: 10 * time.Second,} client.Get("http://example.com/")
Bharath Kumar

55

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
내 우주에서 나는 파일을 다운로드하는 데 필요한 DSL을 구현했습니다. 합리적인 보안 모델이기 때문에 구성하고 싶지 않은 OS 호환 및 chroot 문제에 빠질 때까지 Exec () curl이 편리했습니다. 그래서 U는 내 CURL을이 코드로 바꾸고 10-15 배의 성능 향상을 얻었습니다. 이런!
Richard

14

위에서 선택한 대답 io.Copy은 정확히 필요한 것입니다. 그러나 손상된 다운로드 재개, 파일 자동 이름 지정, 체크섬 유효성 검사 또는 여러 다운로드의 진행 상황 모니터링과 같은 추가 기능에 관심이있는 경우 grab 패키지를 확인하십시오 .


링크가 더 이상 사용되지 않는 경우 정보가 손실되지 않도록 코드 스 니펫을 추가 할 수 있습니까?
030

-6
  1. 다음은 샘플입니다. https://github.com/thbar/golang-playground/blob/master/download-files.go

  2. 또한 몇 가지 코드가 도움이 될 수 있습니다.

암호:

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
이 예제는 전체 내용을 ioutil.ReadAll(). 작은 파일을 다루는 한 괜찮습니다.
eduncan911

13
@ eduncan911이지만 큰 파일에 대해 명시 적으로 이야기하고 모든 파일을 메모리에 저장하고 싶지 않은이 질문에는 적합하지 않습니다.
Dave C

2
그렇기 때문에 다른 사람들이 대용량 파일에 이것을 사용하지 않는 것을 알기 위해 그렇게 주석을 달았습니다.
eduncan911

4
이것은 무해한 답변이 아니므로 실제로 제거해야합니다. 많은 코드 더미에서 ReadAll을 사용하는 것은 큰 파일이 사용될 때까지 기다리는 잠재적 인 문제입니다. 대용량 파일에 ReadAll이있는 경우 일반적으로 응답은 높은 메모리 소비량과 함께 문제가 발생할 때까지 AWS 청구액을 늘리는 것입니다. 문제가 발견 될 즈음에는 청구서가 이미 비싸다.
Rob
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.