pgstore

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 29, 2021 License: MIT Imports: 12 Imported by: 0

README

pgstore

build

A session store backend for github.com/gorilla/sessions.

This is a fork of github.com/antonlindstrom/pgstore.

Development

make deps

Integration tests against a live DB can be run on a machine with docker and docker-compose installed. Those files live in the directory .docker/.

# Build test environment and run tests.
make -f .docker/Makefile test-up

# Teardown test environment.
make -f .docker/Makefile test-down

Documentation

See full documentation of the underlying interface.

See examples/ for example usage.

Differences from original project

These are some notable differences compared to the original project. The schema of the http_sessions DB table differs in order to:

  • more efficiently lookup a session by key
  • ease lookups of expired sessions in the automated Cleanup job

The id field was removed from the http_sessions table because the library does not use it. Likewise, the PGSession.ID field was removed. The primary key is now an opaque identifier, key, which is already generated in the original implementation.

Thanks

I've stolen, borrowed and gotten inspiration from the other backends available:

Thank you all for sharing your code!

What makes this backend different is that it's for PostgreSQL.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type PGSession

type PGSession struct {
	Key        string
	Data       string
	CreatedOn  time.Time
	ModifiedOn time.Time
	ExpiresOn  time.Time
}

PGSession type

type PGStore

type PGStore struct {
	Codecs  []securecookie.Codec
	Options *sessions.Options
	Path    string
	DbPool  *sql.DB
}

PGStore represents the currently configured session store.

Example

This example shows how to use pgstore in an HTTP handler.

package main

import (
	"log"
	"net/http"
	"os"

	"github.com/rafaelespinoza/pgstore"
)

func main() {
	handler := func(w http.ResponseWriter, r *http.Request) {
		// Fetch new store.
		store, err := pgstore.NewPGStore(os.Getenv("DB_DSN"), []byte(os.Getenv("SECRET_KEY")))
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			log.Println("failed to initialize session store:", err)
			return
		}
		defer store.Close()

		// Get a session.
		session, err := store.Get(r, "session-key")
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			log.Println("failed to get session:", err)
			return
		}

		// Add a value.
		session.Values["foo"] = "bar"

		// Save.
		if err = session.Save(r, w); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			log.Println("failed to save session:", err)
			return
		}

		// Delete session.
		session.Options.MaxAge = -1
		if err = session.Save(r, w); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			log.Println("failed to delete session:", err)
			return
		}

		w.WriteHeader(http.StatusCreated)
	}

	err := http.ListenAndServe("localhost:6543", http.HandlerFunc(handler))
	if err == http.ErrServerClosed {
		log.Println(err)
	} else {
		log.Fatal(err)
	}
}
Output:

func NewPGStore

func NewPGStore(dbURL string, keyPairs ...[]byte) (*PGStore, error)

NewPGStore creates a new PGStore instance and a new database/sql pool. This will also create in the database the schema needed by pgstore.

func NewPGStoreFromPool

func NewPGStoreFromPool(db *sql.DB, keyPairs ...[]byte) (*PGStore, error)

NewPGStoreFromPool creates a new PGStore instance from an existing database/sql pool. This will also create the database schema needed by pgstore.

func (*PGStore) Cleanup

func (db *PGStore) Cleanup(interval time.Duration) (chan<- struct{}, <-chan struct{})

Cleanup runs a background goroutine every interval that deletes expired sessions from the database.

The design is based on https://github.com/yosssi/boltstore

func (*PGStore) Close

func (db *PGStore) Close()

Close closes the database connection.

func (*PGStore) DeleteExpired added in v0.0.2

func (db *PGStore) DeleteExpired() (err error)

DeleteExpired deletes expired sessions from the database.

func (*PGStore) Get

func (db *PGStore) Get(r *http.Request, name string) (*sessions.Session, error)

Get Fetches a session for a given name after it has been added to the registry.

func (*PGStore) MaxAge

func (db *PGStore) MaxAge(age int)

MaxAge sets the maximum age for the store and the underlying cookie implementation. Individual sessions can be deleted by setting Options.MaxAge = -1 for that session.

func (*PGStore) MaxLength

func (db *PGStore) MaxLength(l int)

MaxLength restricts the maximum length of new sessions to l. If l is 0 there is no limit to the size of a session, use with caution. The default for a new PGStore is 4096. PostgreSQL allows for max value sizes of up to 1GB (http://www.postgresql.org/docs/current/interactive/datatype-character.html)

func (*PGStore) New

func (db *PGStore) New(r *http.Request, name string) (*sessions.Session, error)

New returns a new session for the given name without adding it to the registry.

func (*PGStore) RunCleanup added in v0.0.2

func (db *PGStore) RunCleanup(ctx context.Context, interval time.Duration) <-chan error

RunCleanup deletes expired sessions from the database every interval within a background goroutine. It's similar to the Cleanup method except its API provides a way to consume errors via the read-only channel. Use the input context to facilitate quit or timeout signals from the parent goroutine.

Example
package main

import (
	"context"
	"log"
	"os"
	"os/signal"
	"time"

	"github.com/rafaelespinoza/pgstore"
)

func main() {
	store, err := pgstore.NewPGStore(os.Getenv("DB_DSN"), []byte(os.Getenv("SECRET_KEY")))
	if err != nil {
		panic(err)
	}
	defer store.Close()

	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		// Run indefinitely. Shut it down with ^C (Control-C).
		sigint := make(chan os.Signal, 1)
		signal.Notify(sigint, os.Interrupt)
		<-sigint
		cancel()
	}()

	const interval = 5 * time.Minute

	// Consume error values as they're available.
	for err := range store.RunCleanup(ctx, interval) {
		switch err {
		case nil:
			log.Println("ran cleanup")
		case context.Canceled:
			log.Println("done", err)
		default:
			log.Println("error:", err)
		}
	}
}
Output:

func (*PGStore) Save

func (db *PGStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error

Save saves the given session into the database and deletes cookies if needed

func (*PGStore) StopCleanup

func (db *PGStore) StopCleanup(quit chan<- struct{}, done <-chan struct{})

StopCleanup stops the background cleanup from running.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL