// Package fasthttpadaptor provides helper functions for converting net/http // request handlers to fasthttp request handlers. package fasthttpadaptor import ( "bufio" "io" "net" "net/http" "sync" "github.com/valyala/fasthttp" ) // NewFastHTTPHandlerFunc wraps net/http handler func to fasthttp // request handler, so it can be passed to fasthttp server. // // While this function may be used for easy switching from net/http to fasthttp, // it has the following drawbacks comparing to using manually written fasthttp // request handler: // // - A lot of useful functionality provided by fasthttp is missing // from net/http handler. // - net/http -> fasthttp handler conversion has some overhead, // so the returned handler will be always slower than manually written // fasthttp handler. // // So it is advisable using this function only for quick net/http -> fasthttp // switching. Then manually convert net/http handlers to fasthttp handlers // according to https://github.com/valyala/fasthttp#switching-from-nethttp-to-fasthttp . func NewFastHTTPHandlerFunc(h http.HandlerFunc) fasthttp.RequestHandler { return NewFastHTTPHandler(h) } // NewFastHTTPHandler wraps net/http handler to fasthttp request handler, // so it can be passed to fasthttp server. // // While this function may be used for easy switching from net/http to fasthttp, // it has the following drawbacks comparing to using manually written fasthttp // request handler: // // - A lot of useful functionality provided by fasthttp is missing // from net/http handler. // - net/http -> fasthttp handler conversion has some overhead, // so the returned handler will be always slower than manually written // fasthttp handler. // // So it is advisable using this function only for quick net/http -> fasthttp // switching. Then manually convert net/http handlers to fasthttp handlers // according to https://github.com/valyala/fasthttp#switching-from-nethttp-to-fasthttp . func NewFastHTTPHandler(h http.Handler) fasthttp.RequestHandler { return func(ctx *fasthttp.RequestCtx) { var r http.Request if err := ConvertRequest(ctx, &r, true); err != nil { ctx.Logger().Printf("cannot parse requestURI %q: %v", r.RequestURI, err) ctx.Error("Internal Server Error", fasthttp.StatusInternalServerError) return } w := netHTTPResponseWriter{ w: ctx.Response.BodyWriter(), ctx: ctx, } h.ServeHTTP(&w, r.WithContext(ctx)) ctx.SetStatusCode(w.StatusCode()) haveContentType := false for k, vv := range w.Header() { if k == fasthttp.HeaderContentType { haveContentType = true } for _, v := range vv { ctx.Response.Header.Add(k, v) } } if !haveContentType { // From net/http.ResponseWriter.Write: // If the Header does not contain a Content-Type line, Write adds a Content-Type set // to the result of passing the initial 512 bytes of written data to DetectContentType. l := 512 b := ctx.Response.Body() if len(b) < 512 { l = len(b) } ctx.Response.Header.Set(fasthttp.HeaderContentType, http.DetectContentType(b[:l])) } } } type netHTTPResponseWriter struct { statusCode int h http.Header w io.Writer ctx *fasthttp.RequestCtx } func (w *netHTTPResponseWriter) StatusCode() int { if w.statusCode == 0 { return http.StatusOK } return w.statusCode } func (w *netHTTPResponseWriter) Header() http.Header { if w.h == nil { w.h = make(http.Header) } return w.h } func (w *netHTTPResponseWriter) WriteHeader(statusCode int) { w.statusCode = statusCode } func (w *netHTTPResponseWriter) Write(p []byte) (int, error) { return w.w.Write(p) } func (w *netHTTPResponseWriter) Flush() {} type wrappedConn struct { net.Conn wg sync.WaitGroup once sync.Once } func (c *wrappedConn) Close() (err error) { c.once.Do(func() { err = c.Conn.Close() c.wg.Done() }) return } func (w *netHTTPResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { // Hijack assumes control of the connection, so we need to prevent fasthttp from closing it or // doing anything else with it. w.ctx.HijackSetNoResponse(true) conn := &wrappedConn{Conn: w.ctx.Conn()} conn.wg.Add(1) w.ctx.Hijack(func(net.Conn) { conn.wg.Wait() }) bufW := bufio.NewWriter(conn) // Write any unflushed body to the hijacked connection buffer. unflushedBody := w.ctx.Response.Body() if len(unflushedBody) > 0 { if _, err := bufW.Write(unflushedBody); err != nil { conn.Close() return nil, nil, err } } return conn, &bufio.ReadWriter{Reader: bufio.NewReader(conn), Writer: bufW}, nil }