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() }