英文:
How to serve http partial content with Go?
问题
我有一个用Go语言编写的Web服务器,我正在从不同的来源(本地、其他服务器、S3)提供一些音频文件。我想要启用部分内容的提供,以便HTML音频标签能够进行定位和循环播放。
我该如何实现这个功能?我知道http
包的ServeContent
函数可以实现这个,但是我如何通过自己提供文件来实现呢?我需要做到不使用该函数,以便能够使用相同的处理程序从不同的来源提供文件。
英文:
I have a webserver written in go and I'm serving some audio files from different sources (local, other servers, S3). I want to enable serving partial content for this files so the HTML audio tags are able to seek and loop.
How can I achive this? I know that the http
package ServeContent
function does this but how can I do it by serving the files myself? I need to do it without this so that I can serve files from different sources with the same handler.
答案1
得分: 31
提供部分内容是一项复杂的任务。请参考维基百科上的字节服务(Byte serving)进行了解。你需要处理特定的状态码和头部(请求和响应),这并不太难,但你不应该浪费时间自己去做这些。
如果要提供的内容(或从中提供的内容)是一个文件,你可以使用http.ServeFile()
,就像你之前提到的那样,它可以处理部分内容(范围请求)。
如果要提供的内容不是文件,则可以使用http.ServeContent()
:
func ServeContent(w ResponseWriter, req *Request,
name string, modtime time.Time, content io.ReadSeeker)
是你的好朋友。是的,它也可以处理部分内容(范围请求):
ServeContent相对于io.Copy的主要优势在于它可以正确处理范围请求,设置MIME类型,并处理If-Modified-Since请求。
你只需要提供一个io.ReadSeeker
作为你的内容的“视图”,这是必需的,这样实现才能“跳转”到客户端请求的部分,即需要提供的部分。你可能会问:如何做到这一点?
bytes
包中包含了一个实现了io.ReadSeeker
的类型:bytes.Reader
。
所以,例如,如果你的内容是一个[]byte
,你可以这样获得一个io.ReadSeeker
:
var content []byte
// 填充content
r := bytes.NewReader(content)
那么如果你没有将整个内容作为[]byte
呢?一种选择是提供一个自己的类型值,该类型实现了io.ReadSeeker
。
io.ReadSeeker
是:
type ReadSeeker interface {
Reader
Seeker
}
io.Reader
包含一个方法:
Read(p []byte) (n int, err error)
io.Seeker
也包含一个方法:
Seek(offset int64, whence int) (int64, error)
你的内容以某种方式在某个地方可访问,你知道如何访问。Seek()
被调用以告知你所需内容的哪个部分(位置),而Read()
被调用以便你填充传递的切片(提供实际内容)。请注意,这些方法可能会被多次调用,因此你必须跟踪你在内容(源)中的位置。如果你选择这条路,请阅读链接接口的文档,以确保你满足接口的“一般契约”,以避免意外错误。
英文:
Serving partial content is non-trivial. See Byte serving on wikipedia for an introduction. You have to handle specific status codes and headers (both request and response), which is not too hard but you shouldn't waste your time doing that yourself.
If the content to serve (or serve from) is a file, you may use http.ServeFile()
just as you mentioned, which handles serving partial content (Range requests).
If the content to be served is not present as a file, then http.ServeContent()
is your friend:
func ServeContent(w ResponseWriter, req *Request,
name string, modtime time.Time, content io.ReadSeeker)
And yes, it also handles serving partial content (Range requests):
> The main benefit of ServeContent over io.Copy is that it handles Range requests properly, sets the MIME type, and handles If-Modified-Since requests.
All you need to do is provide an io.ReadSeeker
"view" of your content, this is required so that the implementation can "jump" to the part that is requested by the client, the part that needs to be served. You might ask: how to do that?
The bytes
package contains a type that implements io.ReadSeeker
: bytes.Reader
.
So for example if you have the content as a []byte
, you may obtain an io.ReadSeeker
like this:
var content []byte
// fill content
r := bytes.NewReader(content)
And what if you don't have the whole content as a []byte
? One option is to provide a value of your own type that implements io.ReadSeeker
.
io.ReadSeeker
is:
type ReadSeeker interface {
Reader
Seeker
}
io.Reader
contains one method:
Read(p []byte) (n int, err error)
io.Seeker
also contains one method:
Seek(offset int64, whence int) (int64, error)
Your content is accessible somewhere, somehow, you know how. Seek()
is called to let you know what part (position) is required from your content, and Read()
is called so you can populate the passed slice (to provide the actual content). Note that these methods may be called multiple times, so you have to keep track where you are in your content (source). If you choose to go down this path, please read the doc of the linked interfaces to make sure you meet the "general contract" of the interfaces to avoid surprise errors.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论