googollee / go-socket.io

socket.io library for golang, a realtime application framework.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

integration with fasthttp - suggested approach

gilwo opened this issue · comments

commented

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

commented

Update
I have opened a PR in valyala/fasthttp#1525 - very simple solution (took only portion of the changes aforementioned)

commented

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

commented

@thisismz
IMO it is good enough, the PR is an attempt to make the fix on fasthttp instead of a custom implementation everywhere...
I got stuck with fixing the tests for it and forgot about it...