2015-04-04 164 views
2

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

这是我的代码:

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

但我发现了这个错误:

Corrupted Content Error

The page you are trying to view cannot be shown because an error in the data transmission was detected.

这是我在Go游乐场的代码:http://play.golang.org/p/ulgXQl4eQO

+3

我不知道那个包,但我限制了作家,而不是读者。此外,Content-Type和Content-Length标题不应该从请求中复制,而应由文件类型和文件长度来设置。此外,你正在阅读一个文本文件,并将其写为png。 – 2015-04-04 11:20:24

+2

@Not_a_Golfer它是https://github.com/juju/ratelimit,虽然我更喜欢https://github.com/aybabtme/iocontrol – VonC 2015-04-04 11:43:53

回答

1

我没有看到错误,但我确实注意到了代码的一些问题。对于这一点:

w.Header().Set("Content-Type", r.Header.Get("Content-Type")) 

您应该使用mime package的:

func TypeByExtension(ext string) string 

来确定内容类型。 (如果你最终用空字符串默认application/octet-stream

为:

w.Header().Set("Content-Length", r.Header.Get("Content-Length")) 

你需要从文件本身内容的长度。通过使用请求内容的长度,对于GET,这基本上以没有任何操作结束,但对于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() 
2

,而不是得到正确的内容类型和长度头自己大概要好得多使用http.ServeContent它将为你做的摆弄左右(以及支持“ If-Modified-Since“,范围请求等。如果你可以提供一个”ETag“头,它也可以处理”If-Range“和”If-None-Match“请求)。

正如前面提到的,这是通常优选的是在写入侧限制但它是笨拙包裹的http.ResponseWriter因为各种HTTP功能也检查可选的接口,如http.Flusherhttp.Hijacker。包装ServeContent需要的io.ReadSeeker要容易得多。

例如,这样的事情也许是:

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

什么newLRS是?编辑:找到;) – Royalty 2015-04-24 12:59:08

+0

顺便说一句,现在我更喜欢在'github上使用['golang.org/x/time/rate'](https://godoc.org/golang.org/x/time/rate) 。com/juju/ratelimit'。 – 2017-08-04 16:02:53