integration with fasthttp - suggested approach
gilwo opened this issue · comments
The problem
Lack interoperability of go-socket.io with fasthttp based frameworks.
TL;DR
bridge the gap between go-socket.io and fasthttp
The story
I was working on a project which use go-fiber as a backend server. I wanted to use socket-io for notification channel and bidi client server logic. after couple of hours I found there is no example on the how to do the integration and even some referred to issue #127 that mentioned as a wont do feature.
apparently there was couple of others request along to support this (e.g. #353, #513 and valyala/fasthttp#87)
anyhow go-fiber was here to stay in my project and socket-io is very much wanted support.
Investigation
fasthttp include an adaptor that help to get net/http objects from its context. I tried using it but something was not working.
I tracked the internal implementation and after some thorough investigation I figure out that the hijack interface is missing there in order to be able to properly pass control to go-socket.io.
Solution
after modifying a custom response object I was able to hand-over the control of the connection to go-socket.io and from there everything started to work smoothly.
Code
Solution code is based on fasthttpadaptor
full workable example here
custom repsonse writer
type customReponseWriter struct {
statusCode int
h http.Header
w io.Writer
r io.Reader
conn net.Conn
}
func (cr *customReponseWriter) StatusCode() int {
if cr.statusCode == 0 {
return http.StatusOK
}
return cr.statusCode
}
func (cr *customReponseWriter) Header() http.Header {
if cr.h == nil {
cr.h = make(http.Header)
}
return cr.h
}
func (cr *customReponseWriter) WriteHeader(statusCode int) {
cr.statusCode = statusCode
}
func (cr *customReponseWriter) Write(p []byte) (int, error) {
return cr.w.Write(p)
}
func (cr *customReponseWriter) Flush() {
}
func (cr *customReponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return cr.conn, &bufio.ReadWriter{
Reader: bufio.NewReader(cr.r),
Writer: bufio.NewWriter(cr.w),
}, nil
}
mapping the endpoint
func map_server_endpoints(app *fiber.App, so *socketio.server) {
...
wrapper := func(ctx *fasthttp.RequestCtx, so *socketio.Server) error {
var r http.Request
err := fasthttpadaptor.ConvertRequest(ctx, &r, true)
if err != nil {
ctx.Error("convert request from fasthttp to net/http failed", fiber.StatusInternalServerError)
return err
}
w := customReponseWriter{
conn: ctx.Conn(),
w: ctx.Response.BodyWriter(),
r: ctx.RequestBodyStream(),
}
so.ServeHTTP(&w, r.WithContext(ctx))
return nil
}
app.Get("/socket.io/*", func(c *fiber.Ctx) error {
return wrapper(c.Context(), so)
})
...
}
now what?
I guess PR to fasthttp
Update
I have opened a PR in valyala/fasthttp#1525 - very simple solution (took only portion of the changes aforementioned)
Is this your sample code product ready? I reviewed your request on valyala/fasthttp#1525 and unfortunately, the tests failed. Do you think it's suitable to use this code sample in a Fiber project?
@gilwo