mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-14 04:07:46 +00:00
Add redis support for distributed caching (#83)
* Add redis support for distributed caching * Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * fixup! fixup! fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi. * ... and another all nighter. * fixup! ... and another all nighter. * fixup! fixup! ... and another all nighter. * fixup! fixup! fixup! ... and another all nighter. * Resolve issue #85 by adding ability to set custom claims in JWT tokens * Remove redundant validation in auth middleware ( issue #89 ) * Add ability to set cookie prefix for session cookies ( #87 ) * fixup! Add ability to set cookie prefix for session cookies ( #87 ) * Add ability to set cookie max age - issue #91 * Potential fix for code scanning alert no. 10: Size computation for allocation may overflow Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fixup! Merge main into 0.8.0-redis: resolve conflicts --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
parent
5fcbd54955
commit
e64fc7f730
+2
@@ -0,0 +1,2 @@
|
||||
test:
|
||||
go test
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
c net.Conn
|
||||
r *bufio.Reader
|
||||
}
|
||||
|
||||
func Dial(addr string) (*Client, error) {
|
||||
c, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{
|
||||
c: c,
|
||||
r: bufio.NewReader(c),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func DialTLS(addr string, cfg *tls.Config) (*Client, error) {
|
||||
c, err := tls.Dial("tcp", addr, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{
|
||||
c: c,
|
||||
r: bufio.NewReader(c),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
return c.c.Close()
|
||||
}
|
||||
|
||||
func (c *Client) Do(cmd ...string) (string, error) {
|
||||
if err := Write(c.c, cmd); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return Read(c.r)
|
||||
}
|
||||
|
||||
func (c *Client) Read() (string, error) {
|
||||
return Read(c.r)
|
||||
}
|
||||
|
||||
// Do() + ReadStrings()
|
||||
func (c *Client) DoStrings(cmd ...string) ([]string, error) {
|
||||
res, err := c.Do(cmd...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ReadStrings(res)
|
||||
}
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrProtocol = errors.New("unsupported protocol")
|
||||
ErrUnexpected = errors.New("not what you asked for")
|
||||
)
|
||||
|
||||
func readLine(r *bufio.Reader) (string, error) {
|
||||
line, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(line) < 3 {
|
||||
return "", ErrProtocol
|
||||
}
|
||||
return line, nil
|
||||
}
|
||||
|
||||
// Read an array, with all elements are the raw redis commands
|
||||
// Also reads sets and maps.
|
||||
func ReadArray(b string) ([]string, error) {
|
||||
r := bufio.NewReader(strings.NewReader(b))
|
||||
line, err := readLine(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
elems := 0
|
||||
switch line[0] {
|
||||
default:
|
||||
return nil, ErrUnexpected
|
||||
case '*', '>', '~':
|
||||
// *: array
|
||||
// >: push data
|
||||
// ~: set
|
||||
length, err := strconv.Atoi(line[1 : len(line)-2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
elems = length
|
||||
case '%':
|
||||
// we also read maps.
|
||||
length, err := strconv.Atoi(line[1 : len(line)-2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
elems = length * 2
|
||||
}
|
||||
|
||||
var res []string
|
||||
for i := 0; i < elems; i++ {
|
||||
next, err := Read(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, next)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func ReadString(b string) (string, error) {
|
||||
r := bufio.NewReader(strings.NewReader(b))
|
||||
line, err := readLine(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch line[0] {
|
||||
default:
|
||||
return "", ErrUnexpected
|
||||
case '$':
|
||||
// bulk strings are: `$5\r\nhello\r\n`
|
||||
length, err := strconv.Atoi(line[1 : len(line)-2])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if length < 0 {
|
||||
// -1 is a nil response
|
||||
return line, nil
|
||||
}
|
||||
var (
|
||||
buf = make([]byte, length+2)
|
||||
pos = 0
|
||||
)
|
||||
for pos < length+2 {
|
||||
n, err := r.Read(buf[pos:])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pos += n
|
||||
}
|
||||
return string(buf[:len(buf)-2]), nil
|
||||
}
|
||||
}
|
||||
|
||||
func readInline(b string) (string, error) {
|
||||
if len(b) < 3 {
|
||||
return "", ErrUnexpected
|
||||
}
|
||||
return b[1 : len(b)-2], nil
|
||||
}
|
||||
|
||||
func ReadError(b string) (string, error) {
|
||||
if len(b) < 1 {
|
||||
return "", ErrUnexpected
|
||||
}
|
||||
|
||||
switch b[0] {
|
||||
default:
|
||||
return "", ErrUnexpected
|
||||
case '-':
|
||||
return readInline(b)
|
||||
}
|
||||
}
|
||||
|
||||
func ReadStrings(b string) ([]string, error) {
|
||||
elems, err := ReadArray(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res []string
|
||||
for _, e := range elems {
|
||||
s, err := ReadString(e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, s)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Read a single command, returning it raw. Used to read replies from redis.
|
||||
// Understands RESP3 proto.
|
||||
func Read(r *bufio.Reader) (string, error) {
|
||||
line, err := readLine(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch line[0] {
|
||||
default:
|
||||
return "", ErrProtocol
|
||||
case '+', '-', ':', ',', '_':
|
||||
// +: inline string
|
||||
// -: errors
|
||||
// :: integer
|
||||
// ,: float
|
||||
// _: null
|
||||
// Simple line based replies.
|
||||
return line, nil
|
||||
case '$':
|
||||
// bulk strings are: `$5\r\nhello\r\n`
|
||||
length, err := strconv.Atoi(line[1 : len(line)-2])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if length < 0 {
|
||||
// -1 is a nil response
|
||||
return line, nil
|
||||
}
|
||||
var (
|
||||
buf = make([]byte, length+2)
|
||||
pos = 0
|
||||
)
|
||||
for pos < length+2 {
|
||||
n, err := r.Read(buf[pos:])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pos += n
|
||||
}
|
||||
return line + string(buf), nil
|
||||
case '*', '>', '~':
|
||||
// arrays are: `*6\r\n...`
|
||||
// pushdata is: `>6\r\n...`
|
||||
// sets are: `~6\r\n...`
|
||||
length, err := strconv.Atoi(line[1 : len(line)-2])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for i := 0; i < length; i++ {
|
||||
next, err := Read(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
line += next
|
||||
}
|
||||
return line, nil
|
||||
case '%':
|
||||
// maps are: `%3\r\n...`
|
||||
length, err := strconv.Atoi(line[1 : len(line)-2])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for i := 0; i < length*2; i++ {
|
||||
next, err := Read(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
line += next
|
||||
}
|
||||
return line, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Write a command in RESP3 proto. Used to write commands to redis.
|
||||
// Currently only supports string arrays.
|
||||
func Write(w io.Writer, cmd []string) error {
|
||||
if _, err := fmt.Fprintf(w, "*%d\r\n", len(cmd)); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, c := range cmd {
|
||||
if _, err := fmt.Fprintf(w, "$%d\r\n%s\r\n", len(c), c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse into interfaces. `b` must contain exactly a single command (which can be nested).
|
||||
func Parse(b string) (interface{}, error) {
|
||||
if len(b) < 1 {
|
||||
return nil, ErrUnexpected
|
||||
}
|
||||
|
||||
switch b[0] {
|
||||
default:
|
||||
return "", ErrProtocol
|
||||
case '+':
|
||||
return readInline(b)
|
||||
case '-':
|
||||
e, err := readInline(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return errors.New(e), nil
|
||||
case ':':
|
||||
e, err := readInline(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return strconv.Atoi(e)
|
||||
case '$':
|
||||
return ReadString(b)
|
||||
case '*':
|
||||
elems, err := ReadArray(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res []interface{}
|
||||
for _, elem := range elems {
|
||||
e, err := Parse(elem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, e)
|
||||
}
|
||||
return res, nil
|
||||
case '%':
|
||||
elems, err := ReadArray(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res = map[interface{}]interface{}{}
|
||||
for len(elems) > 1 {
|
||||
key, err := Parse(elems[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value, err := Parse(elems[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res[key] = value
|
||||
elems = elems[2:]
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Byte-safe string
|
||||
func String(s string) string {
|
||||
return fmt.Sprintf("$%d\r\n%s\r\n", len(s), s)
|
||||
}
|
||||
|
||||
// Inline string
|
||||
func Inline(s string) string {
|
||||
return inline('+', s)
|
||||
}
|
||||
|
||||
// Error
|
||||
func Error(s string) string {
|
||||
return inline('-', s)
|
||||
}
|
||||
|
||||
func inline(r rune, s string) string {
|
||||
return fmt.Sprintf("%s%s\r\n", string(r), s)
|
||||
}
|
||||
|
||||
// Int
|
||||
func Int(n int) string {
|
||||
return fmt.Sprintf(":%d\r\n", n)
|
||||
}
|
||||
|
||||
// Float
|
||||
func Float(n float64) string {
|
||||
return fmt.Sprintf(",%g\r\n", n)
|
||||
}
|
||||
|
||||
const (
|
||||
Nil = "$-1\r\n"
|
||||
NilResp3 = "_\r\n"
|
||||
NilList = "*-1\r\n"
|
||||
)
|
||||
|
||||
// Array assembles the args in a list. Args should be raw redis commands.
|
||||
// Example: Array(String("foo"), String("bar"))
|
||||
func Array(args ...string) string {
|
||||
return fmt.Sprintf("*%d\r\n", len(args)) + strings.Join(args, "")
|
||||
}
|
||||
|
||||
// Push assembles the args for push-data. Args should be raw redis commands.
|
||||
// Example: Push(String("foo"), String("bar"))
|
||||
func Push(args ...string) string {
|
||||
return fmt.Sprintf(">%d\r\n", len(args)) + strings.Join(args, "")
|
||||
}
|
||||
|
||||
// Strings is a helper to build 1 dimensional string arrays.
|
||||
func Strings(args ...string) string {
|
||||
var strings []string
|
||||
for _, a := range args {
|
||||
strings = append(strings, String(a))
|
||||
}
|
||||
return Array(strings...)
|
||||
}
|
||||
|
||||
// Ints is a helper to build 1 dimensional int arrays.
|
||||
func Ints(args ...int) string {
|
||||
var ints []string
|
||||
for _, a := range args {
|
||||
ints = append(ints, Int(a))
|
||||
}
|
||||
return Array(ints...)
|
||||
}
|
||||
|
||||
// Map assembles the args in a map. Args should be raw redis commands.
|
||||
// Must be an even number of arguments.
|
||||
// Example: Map(String("foo"), String("bar"))
|
||||
func Map(args ...string) string {
|
||||
return fmt.Sprintf("%%%d\r\n", len(args)/2) + strings.Join(args, "")
|
||||
}
|
||||
|
||||
// StringMap is is a wrapper to get a map of (bulk)strings.
|
||||
func StringMap(args ...string) string {
|
||||
var strings []string
|
||||
for _, a := range args {
|
||||
strings = append(strings, String(a))
|
||||
}
|
||||
return Map(strings...)
|
||||
}
|
||||
|
||||
// Set assembles the args in a map. Args should be raw redis commands.
|
||||
// Example: Set(String("foo"), String("bar"))
|
||||
func Set(args ...string) string {
|
||||
return fmt.Sprintf("~%d\r\n", len(args)) + strings.Join(args, "")
|
||||
}
|
||||
|
||||
// StringSet is is a wrapper to get a set of (bulk)strings.
|
||||
func StringSet(args ...string) string {
|
||||
var strings []string
|
||||
for _, a := range args {
|
||||
strings = append(strings, String(a))
|
||||
}
|
||||
return Set(strings...)
|
||||
}
|
||||
Reference in New Issue
Block a user