2015-04-04 2 views
2

В настоящее время я разрабатываю сервер загрузки в Go. Мне нужно ограничить скорость загрузки пользователей до 100 КБ/с.Как ограничить скорость загрузки с помощью 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. –

+2

@Not_a_Golfer это https://github.com/juju/ratelimit, хотя я предпочитаю https://github.com/aybabtme/iocontrol – VonC

ответ

1

Я не вижу ошибки, но я заметил некоторые проблемы с кодом. Для этого:

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

Вы должны использовать mime package «S:

func TypeByExtension(ext string) string 

Для определения типа содержимого. (Если вы в конечном итоге с пустой строкой по умолчанию для application/octet-stream)

Для:

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

Вы должны получить длину содержимого из самого файла. Используя длину содержимого запроса, для GET это в основном заканчивается как нет-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() 
2

Вместо того, чтобы отвод вокруг с получением правильного типа контента и длиной заголовки себе, что это, вероятно, будет гораздо лучше использовать http.ServeContent, который сделает это за вас (а также поддержку " If-Modified-Since ", запросы диапазона и т. Д. Если вы можете предоставить заголовок« ETag », он также может обрабатывать запросы« If-Range »и« If-None-Match »).

Как упоминалось ранее, это часто предпочтительнее, чтобы ограничить на стороне записи, но это неудобно, чтобы обернуть http.ResponseWriter, поскольку различные функции HTTP также проверить для дополнительных интерфейсов, таких как http.Flusher и http.Hijacker. Гораздо проще обернуть io.ReadSeeker, что нужно ServeContent.

Например, что-то вроде этого, может быть:

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

Что новогоLRS? EDIT: found;) – Royalty

+0

BTW, в эти дни я бы предпочел использовать ['golang.org/x/time/rate'](https://godoc.org/golang.org/x/time/rate) через' github ,com/juju/ratelimit'. –