MakyuuIchaival/httputils/fasthttp.go

199 lines
4.9 KiB
Go

package httputils
import (
"bytes"
"fmt"
"github.com/valyala/fasthttp"
"io"
"net/textproto"
"strconv"
"strings"
"time"
)
type FastHTTPContext struct {
ctx *fasthttp.RequestCtx
server *Server
connTime time.Time
requestTime time.Time
timingEvents uint
}
func NewRequestContextFromFastHttp(server *Server, ctx *fasthttp.RequestCtx) *FastHTTPContext {
if ctx == nil {
return nil
}
return &FastHTTPContext{
ctx: ctx,
connTime: ctx.ConnTime(),
requestTime: ctx.Time(),
server: server,
}
}
func (c *FastHTTPContext) GetExtraHeaders() map[string]string {
return c.server.GetExtraHeaders()
}
func (c *FastHTTPContext) AddTiming(name string, desc string, d time.Duration) {
if d < 0 {
d = 0
}
c.AddResponseHeader("Server-Timing", fmt.Sprintf("%d_%s;desc=\"%s\";dur=%.6F", c.timingEvents, name, desc, float64(d.Nanoseconds())/1e6))
c.timingEvents++
}
func (c *FastHTTPContext) AddTimingInformational(name string, desc string) {
c.AddResponseHeader("Server-Timing", fmt.Sprintf("%d_%s;desc=\"%s\"", c.timingEvents, name, desc))
c.timingEvents++
}
func (c *FastHTTPContext) GetPath() string {
return string(c.ctx.Path())
}
func (c *FastHTTPContext) GetConnectionTime() time.Time {
return c.connTime
}
func (c *FastHTTPContext) GetRequestTime() time.Time {
return c.requestTime
}
func (c *FastHTTPContext) GetTLSServerName() string {
return c.ctx.TLSConnectionState().ServerName
}
func (c *FastHTTPContext) GetRequestHeader(name string) string {
return string(c.ctx.Request.Header.Peek(name))
}
func (c *FastHTTPContext) GetResponseHeader(name string) string {
return string(c.ctx.Response.Header.Peek(name))
}
func (c *FastHTTPContext) AddResponseHeader(name string, value string) {
c.ctx.Response.Header.Add(name, value)
}
func (c *FastHTTPContext) SetResponseHeader(name string, value string) {
c.ctx.Response.Header.Set(name, value)
}
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.SetBody(content)
}
func (c *FastHTTPContext) SetResponseCode(code int) {
c.ctx.Response.SetStatusCode(code)
}
func (c *FastHTTPContext) DoRedirect(location string, code int) {
c.ctx.Redirect(location, code)
}
func (c *FastHTTPContext) GetBody() io.Reader {
b := c.ctx.Request.Body()
buf := make([]byte, len(b))
copy(buf, b)
return bytes.NewBuffer(buf)
}
func (c *FastHTTPContext) IsGet() bool {
return c.ctx.IsGet()
}
func (c *FastHTTPContext) IsPost() bool {
return c.ctx.IsPost()
}
func (c *FastHTTPContext) IsOptions() bool {
return c.ctx.IsOptions()
}
func (c *FastHTTPContext) IsHead() bool {
return c.ctx.IsHead()
}