Added stream for context for file serving

This commit is contained in:
DataHoarder 2022-06-06 22:16:43 +02:00
parent 0feb1f4f31
commit bc9af1d613
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
5 changed files with 248 additions and 26 deletions

View file

@ -143,7 +143,7 @@ func (s *ContentMessage) Encode() []byte {
return append(message, s.Signature...)
}
func (s ContentMessage) String() string {
func (s *ContentMessage) String() string {
return fmt.Sprintf("%d %x %d %s %x", s.Version, s.PublicKey, s.IssueTime, s.Identifier.String(), s.Signature)
}

View file

@ -5,20 +5,12 @@ import (
"fmt"
"github.com/valyala/fasthttp"
"io"
"net/textproto"
"strconv"
"strings"
"time"
)
var fsHandler = (&fasthttp.FS{
Root: "/",
AcceptByteRange: true,
Compress: false,
CompressBrotli: false,
CacheDuration: time.Minute * 15,
PathRewrite: func(ctx *fasthttp.RequestCtx) []byte {
return ctx.Request.URI().PathOriginal()
},
}).NewRequestHandler()
type FastHTTPContext struct {
ctx *fasthttp.RequestCtx
server *Server
@ -89,14 +81,89 @@ func (c *FastHTTPContext) SetResponseHeader(name string, value string) {
c.ctx.Response.Header.Set(name, value)
}
func (c *FastHTTPContext) ServeFile(path string) {
c.ctx.Request.URI().Reset()
c.ctx.Request.URI().SetPath(path)
fsHandler(c.ctx)
func (c *FastHTTPContext) ServeStream(stream Stream) {
const rangePrefix = "bytes="
if stream == nil {
c.SetResponseCode(fasthttp.StatusInternalServerError)
return
}
if dstream, ok := stream.(DefinedStream); ok {
c.SetResponseHeader("Accept-Ranges", "bytes")
c.SetResponseHeader("Last-Modified", dstream.ModTime().UTC().Format(modTimeFormat))
size := dstream.Size()
rangeHeader := c.GetRequestHeader("range")
if len(rangeHeader) > 0 {
if !strings.HasPrefix(rangeHeader, rangePrefix) {
c.SetResponseCode(fasthttp.StatusRequestedRangeNotSatisfiable)
return
}
ranges := strings.Split(rangeHeader[len(rangePrefix):], ",")
if len(ranges) != 1 {
c.SetResponseCode(fasthttp.StatusRequestedRangeNotSatisfiable)
return
}
ra := ranges[0]
start, end, ok := strings.Cut(ra, "-")
if !ok {
c.SetResponseCode(fasthttp.StatusRequestedRangeNotSatisfiable)
return
}
start, end = textproto.TrimString(start), textproto.TrimString(end)
var rangeStart, rangeLength int64
if start == "" {
//Only supports forward ranges, not backward ranges
c.SetResponseCode(fasthttp.StatusRequestedRangeNotSatisfiable)
return
} else {
i, err := strconv.ParseInt(start, 10, 64)
if err != nil || i < 0 {
//Only supports forward ranges, not backward ranges
}
if i >= size {
// If the range begins after the size of the content,
// then it does not overlap.
c.SetResponseCode(fasthttp.StatusRequestedRangeNotSatisfiable)
return
}
rangeStart = i
if end == "" {
// If no end is specified, range extends to end of the file.
rangeLength = size - rangeStart
} else {
i, err := strconv.ParseInt(end, 10, 64)
if err != nil || rangeStart > i {
c.SetResponseCode(fasthttp.StatusRequestedRangeNotSatisfiable)
return
}
if i >= size {
i = size - 1
}
rangeLength = i - rangeStart + 1
}
}
if _, err := dstream.Seek(rangeStart, io.SeekStart); err != nil {
c.SetResponseCode(fasthttp.StatusRequestedRangeNotSatisfiable)
return
}
c.SetResponseCode(fasthttp.StatusPartialContent)
c.SetResponseHeader("Content-Range", fmt.Sprintf("bytes %d-%d/%d", rangeStart, rangeStart+rangeLength-1, size))
size = rangeLength
}
c.ctx.SetBodyStream(stream, int(size))
} else {
c.ctx.SetBodyStream(stream, -1)
}
}
func (c *FastHTTPContext) ServeBytes(content []byte) {
c.ctx.Write(content)
c.ctx.SetBody(content)
}
func (c *FastHTTPContext) SetResponseCode(code int) {

68
httputils/fileserver.go Normal file
View file

@ -0,0 +1,68 @@
package httputils
import (
"io"
"os"
"time"
)
type Stream interface {
io.Reader
io.Closer
}
type SeekableStream interface {
Stream
io.Seeker
}
type DefinedStream interface {
SeekableStream
Size() int64
ModTime() time.Time
}
type FileStream struct {
file *os.File
size int64
modTime time.Time
}
const modTimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
func (fs *FileStream) Read(p []byte) (n int, err error) {
return fs.file.Read(p)
}
func (fs *FileStream) Seek(offset int64, whence int) (int64, error) {
return fs.file.Seek(offset, whence)
}
func (fs *FileStream) Close() error {
return fs.file.Close()
}
func (fs *FileStream) Size() int64 {
return fs.size
}
func (fs *FileStream) ModTime() time.Time {
return fs.modTime
}
func NewStreamFromFile(file *os.File) DefinedStream {
if file == nil {
return nil
}
fstat, err := file.Stat()
if err != nil {
return nil
}
return &FileStream{
file: file,
size: fstat.Size(),
modTime: fstat.ModTime(),
}
}

View file

@ -2,8 +2,12 @@ package httputils
import (
"fmt"
"github.com/valyala/fasthttp"
"io"
"net/http"
"net/textproto"
"strconv"
"strings"
"time"
)
@ -58,9 +62,7 @@ func (c *NetHTTPContext) GetRequestTime() time.Time {
}
func (c *NetHTTPContext) GetTLSServerName() string {
return c.httpRequest.TLS.ServerName
}
func (c *NetHTTPContext) GetRequestHeader(name string) string {
@ -80,25 +82,104 @@ func (c *NetHTTPContext) SetResponseHeader(name string, value string) {
c.httpWriter.Header().Set(name, value)
}
func (c *NetHTTPContext) ServeFile(path string) {
func (c *NetHTTPContext) ServeStream(stream Stream) {
const rangePrefix = "bytes="
http.ServeFile(c.httpWriter, c.httpRequest, path)
if stream == nil {
c.SetResponseCode(fasthttp.StatusInternalServerError)
return
}
if dstream, ok := stream.(DefinedStream); ok {
c.SetResponseHeader("Last-Modified", dstream.ModTime().UTC().Format(modTimeFormat))
c.SetResponseHeader("Accept-Ranges", "bytes")
size := dstream.Size()
rangeHeader := c.GetRequestHeader("range")
if len(rangeHeader) > 0 {
if !strings.HasPrefix(rangeHeader, rangePrefix) {
c.SetResponseCode(fasthttp.StatusRequestedRangeNotSatisfiable)
return
}
ranges := strings.Split(rangeHeader[len(rangePrefix):], ",")
if len(ranges) != 1 {
c.SetResponseCode(fasthttp.StatusRequestedRangeNotSatisfiable)
return
}
ra := ranges[0]
start, end, ok := strings.Cut(ra, "-")
if !ok {
c.SetResponseCode(fasthttp.StatusRequestedRangeNotSatisfiable)
return
}
start, end = textproto.TrimString(start), textproto.TrimString(end)
var rangeStart, rangeLength int64
if start == "" {
//Only supports forward ranges, not backward ranges
c.SetResponseCode(fasthttp.StatusRequestedRangeNotSatisfiable)
return
} else {
i, err := strconv.ParseInt(start, 10, 64)
if err != nil || i < 0 {
//Only supports forward ranges, not backward ranges
}
if i >= size {
// If the range begins after the size of the content,
// then it does not overlap.
c.SetResponseCode(fasthttp.StatusRequestedRangeNotSatisfiable)
return
}
rangeStart = i
if end == "" {
// If no end is specified, range extends to end of the file.
rangeLength = size - rangeStart
} else {
i, err := strconv.ParseInt(end, 10, 64)
if err != nil || rangeStart > i {
c.SetResponseCode(fasthttp.StatusRequestedRangeNotSatisfiable)
return
}
if i >= size {
i = size - 1
}
rangeLength = i - rangeStart + 1
}
}
if _, err := dstream.Seek(rangeStart, io.SeekStart); err != nil {
c.SetResponseCode(fasthttp.StatusRequestedRangeNotSatisfiable)
return
}
c.SetResponseCode(fasthttp.StatusPartialContent)
c.SetResponseHeader("Content-Range", fmt.Sprintf("bytes %d-%d/%d", rangeStart, rangeStart+rangeLength-1, size))
size = rangeLength
}
c.SetResponseHeader("Content-Length", strconv.FormatInt(size, 10))
if !c.IsHead() {
io.CopyN(c.httpWriter, dstream, size)
}
} else {
if !c.IsHead() {
c.SetResponseHeader("Transfer-Encoding", "chunked")
io.Copy(c.httpWriter, dstream)
}
}
}
func (c *NetHTTPContext) ServeBytes(content []byte) {
c.httpWriter.Write(content)
}
func (c *NetHTTPContext) SetResponseCode(code int) {
c.httpWriter.WriteHeader(code)
}
func (c *NetHTTPContext) DoRedirect(location string, code int) {
http.Redirect(c.httpWriter, c.httpRequest, location, code)
}
func (c *NetHTTPContext) GetBody() io.Reader {

View file

@ -8,6 +8,7 @@ import (
"github.com/valyala/fasthttp"
"io"
"log"
"mime"
"net"
"net/http"
"sync"
@ -28,6 +29,8 @@ type Server struct {
extraHeaders map[string]string
}
const ServeStreamChunked = -1
type RequestContext interface {
GetPath() string
GetConnectionTime() time.Time
@ -38,7 +41,8 @@ type RequestContext interface {
GetResponseHeader(name string) string
AddResponseHeader(name string, value string)
SetResponseHeader(name string, value string)
ServeFile(path string)
ServeStream(stream Stream)
ServeBytes(content []byte)
SetResponseCode(code int)
DoRedirect(location string, code int)
@ -62,10 +66,12 @@ type NetHTTPRequestServer func(w http.ResponseWriter, r *http.Request) RequestCo
func (server *Server) Serve() {
var wg sync.WaitGroup
mime.AddExtensionType(".bin", "application/octet-stream")
if server.EnableHTTP2 {
server.TLSConfig.Config.NextProtos = []string{
"h2",
"http/1.1",
http2.H2TLSProto,
}
} else {
server.AddExtraHeader("Connection", "close")