Performance 如何限制Go的下载速度?

Performance 如何限制Go的下载速度?,performance,download,go,throttling,rate,Performance,Download,Go,Throttling,Rate,我目前正在Go中开发一个下载服务器。我需要将用户的下载速度限制为100KB/s 这是我的密码: func serveFile(w http.ResponseWriter, r *http.Request) { fileID := r.URL.Query().Get("fileID") if len(fileID) != 0 { w.Header().Set("Content-Disposition", "attachment; filename=filename.

我目前正在Go中开发一个下载服务器。我需要将用户的下载速度限制为100KB/s

这是我的密码:

func serveFile(w http.ResponseWriter, r *http.Request) {
    fileID := r.URL.Query().Get("fileID")
    if len(fileID) != 0 {
        w.Header().Set("Content-Disposition", "attachment; filename=filename.txt")
        w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
        w.Header().Set("Content-Length", r.Header.Get("Content-Length"))

        file, err := os.Open(fmt.Sprintf("../../bin/files/test.txt"))
        defer file.Close()
        if err != nil {
            http.NotFound(w, r)
            return
        }
        io.Copy(w, file)
    } else {
        io.WriteString(w, "Invalid request.")
    }
}
然后我在github上找到了一个包,我的代码如下:

func serveFile(w http.ResponseWriter, r *http.Request) {
    fileID := r.URL.Query().Get("fileID")
    if len(fileID) != 0 {
        w.Header().Set("Content-Disposition", "attachment; filename=Wiki.png")
        w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
        w.Header().Set("Content-Length", r.Header.Get("Content-Length"))

        file, err := os.Open(fmt.Sprintf("../../bin/files/test.txt"))
        defer file.Close()
        if err != nil {
            http.NotFound(w, r)
            return
        }
        bucket := ratelimit.NewBucketWithRate(100*1024, 100*1024)
        reader := bufio.NewReader(file)
        io.Copy(w, ratelimit.Reader(reader, bucket))
    } else {
        io.WriteString(w, "Invalid request.")
    }
}
但我得到了一个错误:

损坏的内容错误

由于出现错误,无法显示您试图查看的页面 检测到数据传输


下面是我的代码:

我没有看到错误,但我确实注意到了代码中的一些问题。为此:

w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
您应该使用的是:

以确定内容类型。(如果以空字符串结束,则默认为
应用程序/八位字节流

用于:

您需要从文件本身获取内容长度。通过使用请求内容长度,对于
GET
来说,这基本上是一个no-op,但是对于
POST
来说,您返回了错误的长度,这可能解释了您看到的错误。打开文件后,请执行以下操作:

fi, err := file.Stat()
if err != nil {
    http.Error(w, err.Error(), 500)
    return
}
w.Header().Set("Content-Length", fmt.Sprint(fi.Size()))
最后一件事,当您打开文件时,如果出现错误,您不需要关闭文件句柄。改为这样做:

file, err := os.Open(...)
if err != nil {
    http.NotFound(w, r)
    return
}
defer file.Close()

与其自己纠结于获取正确的内容类型和长度标题,不如使用对您有帮助的标题(以及支持“If Modified Since”、范围请求等。如果您可以提供“ETag”标题,它还可以处理“If range”和“If None Match”请求)

如前所述,通常最好在写端进行限制,但包装
http.ResponseWriter
很难,因为各种http函数也会检查可选接口,如
http.Flusher
http.jacker
。封装
ServeContent
所需的
io.readseek
要容易得多

例如,像这样的事情可能:

func pathFromID(fileID string) string {
    // replace with whatever logic you need
    return "../../bin/files/test.txt"
}

// or more verbosely you could call this a "limitedReadSeeker"
type lrs struct {
    io.ReadSeeker
    // This reader must not buffer but just do something simple
    // while passing through Read calls to the ReadSeeker
    r io.Reader
}

func (r lrs) Read(p []byte) (int, error) {
    return r.r.Read(p)
}

func newLRS(r io.ReadSeeker, bucket *ratelimit.Bucket) io.ReadSeeker {
    // Here we know/expect that a ratelimit.Reader does nothing
    // to the Read calls other than add delays so it won't break
    // any io.Seeker calls.
    return lrs{r, ratelimit.Reader(r, bucket)}
}

func serveFile(w http.ResponseWriter, req *http.Request) {
    fileID := req.URL.Query().Get("fileID")
    if len(fileID) == 0 {
        http.Error(w, "invalid request", http.StatusBadRequest)
        return
    }

    path := pathFromID(fileID)
    file, err := os.Open(path)
    if err != nil {
        http.NotFound(w, req)
        return
    }
    defer file.Close()
    fi, err := file.Stat()
    if err != nil {
        http.Error(w, "blah", 500) // XXX fixme
        return
    }

    const (
        rate     = 100 << 10
        capacity = 100 << 10
    )
    // Normally we'd prefer to limit the writer but it's awkward to wrap
    // an http.ResponseWriter since it may optionally also implement
    // http.Flusher, or http.Hijacker.
    bucket := ratelimit.NewBucketWithRate(rate, capacity)
    lr := newLRS(file, bucket)
    http.ServeContent(w, req, path, fi.ModTime(), lr)
}
func-pathFromID(fileID字符串)字符串{
//替换为您需要的任何逻辑
返回“../../bin/files/test.txt”
}
//或者更详细地说,你可以称之为“有限的寻的者”
类型lrs结构{
io.ReadSeeker
//这个阅读器不能缓冲,而要做一些简单的事情
//将读取调用传递给ReadSeek时
读写器
}
func(r lrs)读取(p[]字节)(整数,错误){
返回r.r.Read(p)
}
func newLRS(r io.readseek,bucket*ratelimit.bucket)io.readseek{
//在这里,我们知道/期望一个费率限制。读者什么也不做
//除了添加延迟之外,还要添加读取调用,这样它就不会中断
//任何io.Seeker呼叫。
返回lrs{r,ratelimit.Reader(r,bucket)}
}
func serveFile(带http.ResponseWriter,req*http.Request){
fileID:=req.URL.Query().Get(“fileID”)
如果len(fileID)==0{
http.Error(w,“无效请求”,http.StatusBadRequest)
返回
}
路径:=pathFromID(文件ID)
文件,错误:=os.Open(路径)
如果错误!=零{
http.NotFound(w,req)
返回
}
延迟文件。关闭()
fi,err:=file.Stat()
如果错误!=零{
http.Error(w,“blah”,500)//XXX-fixme
返回
}
常数(

rate=100我不知道该软件包,但我会限制编写器,而不是阅读器。此外,内容类型和内容长度标题不应从请求中复制,而是由文件类型和文件长度设置。此外,您正在读取文本文件并将其写入png。@not_a_Golfer它是,尽管我更喜欢newLRS是什么?EDIT:found;)顺便说一句,现在我更喜欢使用github.com/juju/ratelimit
file, err := os.Open(...)
if err != nil {
    http.NotFound(w, r)
    return
}
defer file.Close()
func pathFromID(fileID string) string {
    // replace with whatever logic you need
    return "../../bin/files/test.txt"
}

// or more verbosely you could call this a "limitedReadSeeker"
type lrs struct {
    io.ReadSeeker
    // This reader must not buffer but just do something simple
    // while passing through Read calls to the ReadSeeker
    r io.Reader
}

func (r lrs) Read(p []byte) (int, error) {
    return r.r.Read(p)
}

func newLRS(r io.ReadSeeker, bucket *ratelimit.Bucket) io.ReadSeeker {
    // Here we know/expect that a ratelimit.Reader does nothing
    // to the Read calls other than add delays so it won't break
    // any io.Seeker calls.
    return lrs{r, ratelimit.Reader(r, bucket)}
}

func serveFile(w http.ResponseWriter, req *http.Request) {
    fileID := req.URL.Query().Get("fileID")
    if len(fileID) == 0 {
        http.Error(w, "invalid request", http.StatusBadRequest)
        return
    }

    path := pathFromID(fileID)
    file, err := os.Open(path)
    if err != nil {
        http.NotFound(w, req)
        return
    }
    defer file.Close()
    fi, err := file.Stat()
    if err != nil {
        http.Error(w, "blah", 500) // XXX fixme
        return
    }

    const (
        rate     = 100 << 10
        capacity = 100 << 10
    )
    // Normally we'd prefer to limit the writer but it's awkward to wrap
    // an http.ResponseWriter since it may optionally also implement
    // http.Flusher, or http.Hijacker.
    bucket := ratelimit.NewBucketWithRate(rate, capacity)
    lr := newLRS(file, bucket)
    http.ServeContent(w, req, path, fi.ModTime(), lr)
}