alexedwards / scs

HTTP Session Management for Go

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Echo compatibility

jpfluger opened this issue Β· comments

Well done @alexedwards on re-imagining scs. πŸ₯‡ This was a comprehensive rewrite. A number of items I appreciate:

  • Status
  • Efficient load/save with help from Status
  • Session and SessionCookie are global in scope
  • I also like that IdleTimeout and Lifetime were split from the old options.
  • go.mod added for go modules support

For me, v2 solves a number of issues for which I had kept a permanent local fork of scs on my machine. (My fork was getting a bit dated too!)

I've done some testing and so far so good. I had one issue with a private token but this example provides a powerful solution to my issue.

Thanks Jaret!

My main objectives for this were decoupling the modification and saving of session data, and making it easier for developers to communicate the session token to clients not using a cookie if they want to.

There's still a bit of work to do, I still need to port some of the stores (boltstore, dynamostore etc) from the old version and improve the documentation, but I'm happy with the new API : )

I have my code all tested. Unfortunately I could not interface the new (or old) scs with Echo cleanly. I really like the clear design of the new scs and really like echo (much of my code-base uses echo already). The gorilla/sessions version of middleware doesn't meet my needs either.

I have a working version of an echo-enabled scs repository as a fork.

I don't mind keeping the fork current with scs changes but there isn't a way to post echo-related issues there. The way echo handles redirects/header writing and context saving were the "difficult" (impossible?) points of integration. I tried with an interface but... it just diverges too much.

Thoughts? I don't want to create confusion with the fork but I also think it would be helpful to echo-users. I could write a GIST... or could rename the repo to (scs-echo) and have echo-users post issues there.

Sorry for the delay on this.

It's not obvious what to do about Echo. I'm pretty certain that the 'fault' (if there's such a thing) for the incompatibility issues lies with the design of Echo, not the design of SCS. SCS doesn't do anything unconventional, it works with the standard library, and I don't know of any other Go routers or frameworks where there is a compatibility problem.

A couple of years ago I spent some time trying to figure out exactly what was causing the problems with Echo. IIRC, I filed an issue for one bug I found, but even when that was fixed the problems continued. I think it has all got something to do with the way that Echo manipulates the request Context field. But I was never able to get to the bottom of it.

If it is possible to create a fork of SCS which works with Echo, called scs-echo then that would be great and I'm all for you (or anyone else) to do it πŸ‘

Thanks for the discussion about this. I don't think there is currently any specific action that needs to be taken on the main SCS respository, so I'm going to close it for now. Feel free to reopen if necessary.

Hi,

just tested this package with echo v4.1.17: it looks like it works.

Accessing session data works, writing session data works, renew token works and the cookie expiration gets updated.

Here is a code example with redis:

var SessionManager *scs.SessionManager

func Session() echo.MiddlewareFunc {
	pool := &redis.Pool{
		Dial: func() (redis.Conn, error) {
			return redis.Dial("tcp", viper.GetString(config.RedisHost))
		},
		MaxIdle: viper.GetInt(config.RedisMaxIdleConnections),
	}

	SessionManager = scs.New()
	SessionManager.Store = redisstore.New(pool)

	return echo.WrapMiddleware(SessionManager.LoadAndSave)
}

Accessing and writing session data works like this (another middleware):

func AuthMiddleware() echo.MiddlewareFunc {
	return func(h echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {

			userId := SessionManager.GetString(c.Request().Context(), "userId")
			// do some stuff 

		}
	}
}

To be honest, I didn't test it very well yet.

@razorness Thanks, that's interesting. I've updated the note on Echo compatibility in the README to be less strongly-worded for now:

You may have some problems using this package with the Echo framework. If you do, then please consider using the Echo session manager instead.

I also tried to get scs working with gin-gonic. Because of the implementation of ResponseWriter and instantly sending of the headers in gin, scs is not working properly with gin-gonic.
However, echo and scs seems to be a great combination. 😎

I tried echo 4.2 with scs 2.4, error hander didn't work. Both default error handler and custom error handler returned 200 although http.StatusBadRequest was specified in ctx.JSON. When I commented out the middle ware usage with scs, it worked again.

I am kind of locate the problem which'd be in the function func (bw *bufferedResponseWriter) WriteHeader(code int).

The following snippet is the test function:

type bufferedResponseWriter struct {
	http.ResponseWriter
	buf         bytes.Buffer
	code        int
	wroteHeader bool
}

func (bw *bufferedResponseWriter) Write(b []byte) (int, error) {
	return bw.buf.Write(b)
}

func (bw *bufferedResponseWriter) WriteHeader(code int) {
	if !bw.wroteHeader {
		bw.code = code
		bw.wroteHeader = true
	}
}

func testLoadAndSave(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("in test load")

		bw := &bufferedResponseWriter{ResponseWriter: w}
		next.ServeHTTP(bw, r)
		fmt.Println("out test load")
	})
}

When I added the function to e.Use(echo.WrapMiddleware(testLoadAndSave)), the problem was reproduced. If I changed the code to

type bufferedResponseWriter struct {
	http.ResponseWriter
	buf         bytes.Buffer
	code        int
	wroteHeader bool
	W           http.ResponseWriter  // Keeping the original writer
}

func (bw *bufferedResponseWriter) Write(b []byte) (int, error) {
	return bw.buf.Write(b)
}

func (bw *bufferedResponseWriter) WriteHeader(code int) {
        bw.W.WriteHeader(code)        // execute the original writer
	if !bw.wroteHeader {
		bw.code = code
		bw.wroteHeader = true
	}
	
}

func testLoadAndSave(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("in test load")

		bw := &bufferedResponseWriter{ResponseWriter: w, W: w}.    // make a copy of original writer
		next.ServeHTTP(bw, r)
		fmt.Println("out test load")
	})
}

The problem was gone. Therefore, if someone could investigate further, the reason might be found and got it solved.

Thank you