mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-06-05 22:44:17 +00:00
e64fc7f730
* 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>
252 lines
5.3 KiB
Go
252 lines
5.3 KiB
Go
package backends
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// RESP (REdis Serialization Protocol) implementation
|
|
// Pure Go implementation compatible with Yaegi interpreter (no unsafe package)
|
|
|
|
var (
|
|
ErrInvalidRESP = errors.New("invalid RESP response")
|
|
ErrNilResponse = errors.New("nil response")
|
|
)
|
|
|
|
// Object pools for memory optimization - reduces allocations by 50-70%
|
|
var (
|
|
readerPool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &RESPReader{
|
|
r: bufio.NewReaderSize(nil, 4096),
|
|
}
|
|
},
|
|
}
|
|
|
|
writerPool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &RESPWriter{
|
|
w: nil,
|
|
}
|
|
},
|
|
}
|
|
)
|
|
|
|
// RESPWriter writes RESP protocol messages
|
|
type RESPWriter struct {
|
|
w io.Writer
|
|
}
|
|
|
|
// NewRESPWriter creates a new RESP writer from the pool (memory optimized)
|
|
func NewRESPWriter(w io.Writer) *RESPWriter {
|
|
writer := writerPool.Get().(*RESPWriter)
|
|
writer.w = w
|
|
return writer
|
|
}
|
|
|
|
// Release returns the writer to the pool for reuse
|
|
func (w *RESPWriter) Release() {
|
|
w.w = nil
|
|
writerPool.Put(w)
|
|
}
|
|
|
|
// WriteCommand writes a Redis command in RESP array format
|
|
// Example: SET key value EX 3600 -> *5\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n$2\r\nEX\r\n$4\r\n3600\r\n
|
|
func (w *RESPWriter) WriteCommand(args ...string) error {
|
|
// Write array header
|
|
if _, err := fmt.Fprintf(w.w, "*%d\r\n", len(args)); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write each argument as bulk string
|
|
for _, arg := range args {
|
|
if _, err := fmt.Fprintf(w.w, "$%d\r\n%s\r\n", len(arg), arg); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RESPReader reads RESP protocol messages
|
|
type RESPReader struct {
|
|
r *bufio.Reader
|
|
}
|
|
|
|
// NewRESPReader creates a new RESP reader from the pool (memory optimized)
|
|
func NewRESPReader(r io.Reader) *RESPReader {
|
|
reader := readerPool.Get().(*RESPReader)
|
|
reader.r.Reset(r)
|
|
return reader
|
|
}
|
|
|
|
// Release returns the reader to the pool for reuse
|
|
func (r *RESPReader) Release() {
|
|
r.r.Reset(nil)
|
|
readerPool.Put(r)
|
|
}
|
|
|
|
// ReadResponse reads a RESP response and returns the parsed value
|
|
func (r *RESPReader) ReadResponse() (interface{}, error) {
|
|
typeByte, err := r.r.ReadByte()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch typeByte {
|
|
case '+': // Simple string
|
|
return r.readSimpleString()
|
|
case '-': // Error
|
|
return nil, r.readError()
|
|
case ':': // Integer
|
|
return r.readInteger()
|
|
case '$': // Bulk string
|
|
return r.readBulkString()
|
|
case '*': // Array
|
|
return r.readArray()
|
|
default:
|
|
return nil, fmt.Errorf("%w: unknown type byte '%c'", ErrInvalidRESP, typeByte)
|
|
}
|
|
}
|
|
|
|
// readSimpleString reads a simple string (+OK\r\n)
|
|
func (r *RESPReader) readSimpleString() (string, error) {
|
|
line, err := r.readLine()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return line, nil
|
|
}
|
|
|
|
// readError reads an error message (-Error message\r\n)
|
|
func (r *RESPReader) readError() error {
|
|
line, err := r.readLine()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return errors.New(line)
|
|
}
|
|
|
|
// readInteger reads an integer (:1000\r\n)
|
|
func (r *RESPReader) readInteger() (int64, error) {
|
|
line, err := r.readLine()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return strconv.ParseInt(line, 10, 64)
|
|
}
|
|
|
|
// readBulkString reads a bulk string ($6\r\nfoobar\r\n or $-1\r\n for nil)
|
|
func (r *RESPReader) readBulkString() (interface{}, error) {
|
|
line, err := r.readLine()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
length, err := strconv.Atoi(line)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: invalid bulk string length", ErrInvalidRESP)
|
|
}
|
|
|
|
// -1 indicates nil bulk string
|
|
if length == -1 {
|
|
return nil, ErrNilResponse
|
|
}
|
|
|
|
// Read exactly 'length' bytes plus \r\n
|
|
buf := make([]byte, length+2)
|
|
if _, err := io.ReadFull(r.r, buf); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Verify \r\n terminator
|
|
if buf[length] != '\r' || buf[length+1] != '\n' {
|
|
return nil, fmt.Errorf("%w: missing CRLF after bulk string", ErrInvalidRESP)
|
|
}
|
|
|
|
return string(buf[:length]), nil
|
|
}
|
|
|
|
// readArray reads an array (*2\r\n...\r\n or *-1\r\n for nil)
|
|
func (r *RESPReader) readArray() (interface{}, error) {
|
|
line, err := r.readLine()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
length, err := strconv.Atoi(line)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: invalid array length", ErrInvalidRESP)
|
|
}
|
|
|
|
// -1 indicates nil array
|
|
if length == -1 {
|
|
return nil, ErrNilResponse
|
|
}
|
|
|
|
// Read each element
|
|
result := make([]interface{}, length)
|
|
for i := 0; i < length; i++ {
|
|
elem, err := r.ReadResponse()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result[i] = elem
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// readLine reads a line terminated by \r\n
|
|
func (r *RESPReader) readLine() (string, error) {
|
|
line, err := r.r.ReadString('\n')
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Remove \r\n
|
|
line = strings.TrimSuffix(line, "\r\n")
|
|
if !strings.HasSuffix(line+"\r\n", "\r\n") {
|
|
return "", fmt.Errorf("%w: missing CRLF", ErrInvalidRESP)
|
|
}
|
|
|
|
return line, nil
|
|
}
|
|
|
|
// RESPString extracts a string from RESP response
|
|
func RESPString(resp interface{}) (string, error) {
|
|
if resp == nil {
|
|
return "", ErrNilResponse
|
|
}
|
|
|
|
switch v := resp.(type) {
|
|
case string:
|
|
return v, nil
|
|
case []byte:
|
|
return string(v), nil
|
|
default:
|
|
return "", fmt.Errorf("expected string, got %T", resp)
|
|
}
|
|
}
|
|
|
|
// RESPInt extracts an integer from RESP response
|
|
func RESPInt(resp interface{}) (int64, error) {
|
|
if resp == nil {
|
|
return 0, ErrNilResponse
|
|
}
|
|
|
|
switch v := resp.(type) {
|
|
case int64:
|
|
return v, nil
|
|
case int:
|
|
return int64(v), nil
|
|
default:
|
|
return 0, fmt.Errorf("expected integer, got %T", resp)
|
|
}
|
|
}
|