为什么`nbytes,err:= io.Copy(ioutil.Discard,resp.Body)`总是返回0?

时间:2017-01-14 08:58:56

标签: go

我编写了一个简单的程序来获取url列表以将它们存储在某些文件中。在这个例子中谷歌和Gmail。我总是在不同的软件版本中运行相同的命令。程序存储在goFetchAll中:这是算法的编译版本的名称。

0.23s        0  http://www.google.com
1.15s        0  http://www.gmail.com

第二个数字应该是内容的字节数。但它总是0。

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "os"
    "strings"
    "time"
)

func main() {
    start := time.Now()

    ch := make(chan string)

    for _, url := range os.Args[1:] {
        go fetch(url, ch)
    }

    for range os.Args[1:] {
        fmt.Println(<-ch)
    }

    secs := time.Since(start).Seconds()
    fmt.Sprintf("%.2fs elapsed\n", secs)
}

func fetch(url string, ch chan<- string) {
    start := time.Now()
    resp, err := http.Get(url)
    if err != nil {
        ch <- fmt.Sprint(err)
        return
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        ch <- fmt.Sprintf("Cant catch content")
        return
    }

    nbytes, err := io.Copy(ioutil.Discard, resp.Body)
    defer resp.Body.Close()
    if err != nil {
        ch <- fmt.Sprintf("while reading %s: %v", url, err)
        return
    }

    secs := time.Since(start).Seconds()
    ch <- fmt.Sprintf("%.2fs  %7d  %s", secs, nbytes, url)

    // store on file
    filename := string(url)
    filename = strings.Replace(filename, ":", "", -1)
    filename = strings.Replace(filename, "//", "-", -1)
    filename = strings.Replace(filename, "/", "", -1)
    filename = strings.Replace(filename, ".", "-", -1)
    filename = "downloads/" + filename + ".html"

    f, err := os.Create(filename)
    f.Write(body)
    defer f.Close()
    if err != nil {
        ch <- fmt.Sprintf("while writing %s: %v", url, err)
        return
    }
}

我还有一个实际可行的相同脚本的旧版本:

0.25s    10363  http://www.google.com
0.89s    66576  http://www.gmail.com
package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "os"
    "time"
)

func main() {
    start := time.Now()

    ch := make(chan string)

    for _, url := range os.Args[1:] {
        go fetch(url, ch)
    }

    for range os.Args[1:] {
        fmt.Println(<-ch)
    }

    fmt.Println("%.2fs elapsed\n", time.Since(start).Seconds())
}

func fetch(url string, ch chan<- string) {
    start := time.Now()
    resp, err := http.Get(url)
    if err != nil {
        ch <- fmt.Sprint(err)
        return
    }

    nbytes, err := io.Copy(ioutil.Discard, resp.Body)
    resp.Body.Close()
    if err != nil {
        ch <- fmt.Sprintf("whioe reading %s: %v", url, err)
        return
    }

    secs := time.Since(start).Seconds()
    ch <- fmt.Sprintf("%.2fs  %7d  %s", secs, nbytes, url)
}

有人可以解释为什么最新版本总是算0秒?

我的部分解决方案如下。我只是再次请求http.Get(url)

resp, err := http.Get(url)
nbytes, err := io.Copy(ioutil.Discard, resp.Body)
defer resp.Body.Close() // dont leak resources
if err != nil {
    ch <- fmt.Sprintf("while reading %s: %v", url, err)
    return
}

resp, err = http.Get(url)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
    ch <- fmt.Sprintf("Cant catch content")
    return
}

1 个答案:

答案 0 :(得分:1)

之所以这样,是因为您已经在该通话时读过一次响应。所以第二次,从流中读取0个字节。删除错误检查调用后:

resp, err := http.Get(url)
body, err := ioutil.ReadAll(resp.Body)
nbytes, err := io.Copy(ioutil.Discard, resp.Body)

请注意第二行的ReadAll电话。

我想提出的另一个小建议(与实际问题无关)是在初始化流后立即使用defer来电。例如:

resp, err := http.Get(url)
if err != nil {
    ch <- fmt.Sprint(err)
    return
}
defer resp.Body.Close()

虽然没有具体提及,但可以从this section in Effective Go推断出来。在这里解释:

  

其次,这意味着关闭位于开放附近,这比将其放置在函数末尾要清晰得多。