mirror of https://github.com/go-redis/redis.git
Merge pull request #2344 from monkey92t/hook
docs: add a description of the hook
This commit is contained in:
commit
09fcdbe6e4
53
cluster.go
53
cluster.go
|
@ -136,34 +136,39 @@ func (opt *ClusterOptions) init() {
|
|||
|
||||
// ParseClusterURL parses a URL into ClusterOptions that can be used to connect to Redis.
|
||||
// The URL must be in the form:
|
||||
// redis://<user>:<password>@<host>:<port>
|
||||
// or
|
||||
// rediss://<user>:<password>@<host>:<port>
|
||||
//
|
||||
// redis://<user>:<password>@<host>:<port>
|
||||
// or
|
||||
// rediss://<user>:<password>@<host>:<port>
|
||||
//
|
||||
// To add additional addresses, specify the query parameter, "addr" one or more times. e.g:
|
||||
// redis://<user>:<password>@<host>:<port>?addr=<host2>:<port2>&addr=<host3>:<port3>
|
||||
// or
|
||||
// rediss://<user>:<password>@<host>:<port>?addr=<host2>:<port2>&addr=<host3>:<port3>
|
||||
//
|
||||
// redis://<user>:<password>@<host>:<port>?addr=<host2>:<port2>&addr=<host3>:<port3>
|
||||
// or
|
||||
// rediss://<user>:<password>@<host>:<port>?addr=<host2>:<port2>&addr=<host3>:<port3>
|
||||
//
|
||||
// Most Option fields can be set using query parameters, with the following restrictions:
|
||||
// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries
|
||||
// - only scalar type fields are supported (bool, int, time.Duration)
|
||||
// - for time.Duration fields, values must be a valid input for time.ParseDuration();
|
||||
// additionally a plain integer as value (i.e. without unit) is intepreted as seconds
|
||||
// - to disable a duration field, use value less than or equal to 0; to use the default
|
||||
// value, leave the value blank or remove the parameter
|
||||
// - only the last value is interpreted if a parameter is given multiple times
|
||||
// - fields "network", "addr", "username" and "password" can only be set using other
|
||||
// URL attributes (scheme, host, userinfo, resp.), query paremeters using these
|
||||
// names will be treated as unknown parameters
|
||||
// - unknown parameter names will result in an error
|
||||
// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries
|
||||
// - only scalar type fields are supported (bool, int, time.Duration)
|
||||
// - for time.Duration fields, values must be a valid input for time.ParseDuration();
|
||||
// additionally a plain integer as value (i.e. without unit) is intepreted as seconds
|
||||
// - to disable a duration field, use value less than or equal to 0; to use the default
|
||||
// value, leave the value blank or remove the parameter
|
||||
// - only the last value is interpreted if a parameter is given multiple times
|
||||
// - fields "network", "addr", "username" and "password" can only be set using other
|
||||
// URL attributes (scheme, host, userinfo, resp.), query paremeters using these
|
||||
// names will be treated as unknown parameters
|
||||
// - unknown parameter names will result in an error
|
||||
//
|
||||
// Example:
|
||||
// redis://user:password@localhost:6789?dial_timeout=3&read_timeout=6s&addr=localhost:6790&addr=localhost:6791
|
||||
// is equivalent to:
|
||||
// &ClusterOptions{
|
||||
// Addr: ["localhost:6789", "localhost:6790", "localhost:6791"]
|
||||
// DialTimeout: 3 * time.Second, // no time unit = seconds
|
||||
// ReadTimeout: 6 * time.Second,
|
||||
// }
|
||||
//
|
||||
// redis://user:password@localhost:6789?dial_timeout=3&read_timeout=6s&addr=localhost:6790&addr=localhost:6791
|
||||
// is equivalent to:
|
||||
// &ClusterOptions{
|
||||
// Addr: ["localhost:6789", "localhost:6790", "localhost:6791"]
|
||||
// DialTimeout: 3 * time.Second, // no time unit = seconds
|
||||
// ReadTimeout: 6 * time.Second,
|
||||
// }
|
||||
func ParseClusterURL(redisURL string) (*ClusterOptions, error) {
|
||||
o := &ClusterOptions{}
|
||||
|
||||
|
|
17
command.go
17
command.go
|
@ -1110,15 +1110,16 @@ func (cmd *KeyValueSliceCmd) String() string {
|
|||
}
|
||||
|
||||
// Many commands will respond to two formats:
|
||||
// 1) 1) "one"
|
||||
// 2) (double) 1
|
||||
// 2) 1) "two"
|
||||
// 2) (double) 2
|
||||
// 1. 1) "one"
|
||||
// 2. (double) 1
|
||||
// 2. 1) "two"
|
||||
// 2. (double) 2
|
||||
//
|
||||
// OR:
|
||||
// 1) "two"
|
||||
// 2) (double) 2
|
||||
// 3) "one"
|
||||
// 4) (double) 1
|
||||
// 1. "two"
|
||||
// 2. (double) 2
|
||||
// 3. "one"
|
||||
// 4. (double) 1
|
||||
func (cmd *KeyValueSliceCmd) readReply(rd *proto.Reader) error { // nolint:dupl
|
||||
n, err := rd.ReadArrayLen()
|
||||
if err != nil {
|
||||
|
|
|
@ -32,7 +32,9 @@ type Once struct {
|
|||
|
||||
// Do calls the function f if and only if Do has not been invoked
|
||||
// without error for this instance of Once. In other words, given
|
||||
// var once Once
|
||||
//
|
||||
// var once Once
|
||||
//
|
||||
// if once.Do(f) is called multiple times, only the first call will
|
||||
// invoke f, even if f has a different value in each invocation unless
|
||||
// f returns an error. A new instance of Once is required for each
|
||||
|
@ -41,7 +43,8 @@ type Once struct {
|
|||
// Do is intended for initialization that must be run exactly once. Since f
|
||||
// is niladic, it may be necessary to use a function literal to capture the
|
||||
// arguments to a function to be invoked by Do:
|
||||
// err := config.once.Do(func() error { return config.init(filename) })
|
||||
//
|
||||
// err := config.once.Do(func() error { return config.init(filename) })
|
||||
func (o *Once) Do(f func() error) error {
|
||||
if atomic.LoadUint32(&o.done) == 1 {
|
||||
return nil
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
)
|
||||
|
||||
// Scan parses bytes `b` to `v` with appropriate type.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func Scan(b []byte, v interface{}) error {
|
||||
switch v := v.(type) {
|
||||
|
|
52
options.go
52
options.go
|
@ -223,32 +223,38 @@ func NewDialer(opt *Options) func(context.Context, string, string) (net.Conn, er
|
|||
// Scheme is required.
|
||||
// There are two connection types: by tcp socket and by unix socket.
|
||||
// Tcp connection:
|
||||
// redis://<user>:<password>@<host>:<port>/<db_number>
|
||||
//
|
||||
// redis://<user>:<password>@<host>:<port>/<db_number>
|
||||
//
|
||||
// Unix connection:
|
||||
// unix://<user>:<password>@</path/to/redis.sock>?db=<db_number>
|
||||
//
|
||||
// unix://<user>:<password>@</path/to/redis.sock>?db=<db_number>
|
||||
//
|
||||
// Most Option fields can be set using query parameters, with the following restrictions:
|
||||
// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries
|
||||
// - only scalar type fields are supported (bool, int, time.Duration)
|
||||
// - for time.Duration fields, values must be a valid input for time.ParseDuration();
|
||||
// additionally a plain integer as value (i.e. without unit) is intepreted as seconds
|
||||
// - to disable a duration field, use value less than or equal to 0; to use the default
|
||||
// value, leave the value blank or remove the parameter
|
||||
// - only the last value is interpreted if a parameter is given multiple times
|
||||
// - fields "network", "addr", "username" and "password" can only be set using other
|
||||
// URL attributes (scheme, host, userinfo, resp.), query paremeters using these
|
||||
// names will be treated as unknown parameters
|
||||
// - unknown parameter names will result in an error
|
||||
// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries
|
||||
// - only scalar type fields are supported (bool, int, time.Duration)
|
||||
// - for time.Duration fields, values must be a valid input for time.ParseDuration();
|
||||
// additionally a plain integer as value (i.e. without unit) is intepreted as seconds
|
||||
// - to disable a duration field, use value less than or equal to 0; to use the default
|
||||
// value, leave the value blank or remove the parameter
|
||||
// - only the last value is interpreted if a parameter is given multiple times
|
||||
// - fields "network", "addr", "username" and "password" can only be set using other
|
||||
// URL attributes (scheme, host, userinfo, resp.), query paremeters using these
|
||||
// names will be treated as unknown parameters
|
||||
// - unknown parameter names will result in an error
|
||||
//
|
||||
// Examples:
|
||||
// redis://user:password@localhost:6789/3?dial_timeout=3&db=1&read_timeout=6s&max_retries=2
|
||||
// is equivalent to:
|
||||
// &Options{
|
||||
// Network: "tcp",
|
||||
// Addr: "localhost:6789",
|
||||
// DB: 1, // path "/3" was overridden by "&db=1"
|
||||
// DialTimeout: 3 * time.Second, // no time unit = seconds
|
||||
// ReadTimeout: 6 * time.Second,
|
||||
// MaxRetries: 2,
|
||||
// }
|
||||
//
|
||||
// redis://user:password@localhost:6789/3?dial_timeout=3&db=1&read_timeout=6s&max_retries=2
|
||||
// is equivalent to:
|
||||
// &Options{
|
||||
// Network: "tcp",
|
||||
// Addr: "localhost:6789",
|
||||
// DB: 1, // path "/3" was overridden by "&db=1"
|
||||
// DialTimeout: 3 * time.Second, // no time unit = seconds
|
||||
// ReadTimeout: 6 * time.Second,
|
||||
// MaxRetries: 2,
|
||||
// }
|
||||
func ParseURL(redisURL string) (*Options, error) {
|
||||
u, err := url.Parse(redisURL)
|
||||
if err != nil {
|
||||
|
|
81
redis.go
81
redis.go
|
@ -29,9 +29,9 @@ func SetLogger(logger internal.Logging) {
|
|||
//------------------------------------------------------------------------------
|
||||
|
||||
type Hook interface {
|
||||
DialHook(hook DialHook) DialHook
|
||||
ProcessHook(hook ProcessHook) ProcessHook
|
||||
ProcessPipelineHook(hook ProcessPipelineHook) ProcessPipelineHook
|
||||
DialHook(next DialHook) DialHook
|
||||
ProcessHook(next ProcessHook) ProcessHook
|
||||
ProcessPipelineHook(next ProcessPipelineHook) ProcessPipelineHook
|
||||
}
|
||||
|
||||
type (
|
||||
|
@ -48,6 +48,43 @@ type hooks struct {
|
|||
processTxPipelineHook ProcessPipelineHook
|
||||
}
|
||||
|
||||
// AddHook is to add a hook to the queue.
|
||||
// Hook is a function executed during network connection, command execution, and pipeline,
|
||||
// it is a first-in-last-out stack queue (FILO).
|
||||
// The first to be added to the queue is the execution function of the redis command (the last to be executed).
|
||||
// You need to execute the next hook in each hook, unless you want to terminate the execution of the command.
|
||||
// For example, you added hook-1, hook-2:
|
||||
//
|
||||
// client.AddHook(hook-1, hook-2)
|
||||
//
|
||||
// hook-1:
|
||||
//
|
||||
// func (Hook1) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
|
||||
// return func(ctx context.Context, cmd Cmder) error {
|
||||
// print("hook-1 start")
|
||||
// next(ctx, cmd)
|
||||
// print("hook-1 end")
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// hook-2:
|
||||
//
|
||||
// func (Hook2) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
|
||||
// return func(ctx context.Context, cmd redis.Cmder) error {
|
||||
// print("hook-2 start")
|
||||
// next(ctx, cmd)
|
||||
// print("hook-2 end")
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// The execution sequence is:
|
||||
//
|
||||
// hook-2 start -> hook-1 start -> exec redis cmd -> hook-1 end -> hook-2 end
|
||||
//
|
||||
// Please note: "next(ctx, cmd)" is very important, it will call the next hook,
|
||||
// if "next(ctx, cmd)" is not executed in hook-1, the redis command will not be executed.
|
||||
func (hs *hooks) AddHook(hook Hook) {
|
||||
hs.slice = append(hs.slice, hook)
|
||||
hs.dialHook = hook.DialHook(hs.dialHook)
|
||||
|
@ -575,7 +612,7 @@ func (c *Client) Conn() *Conn {
|
|||
return newConn(c.opt, pool.NewStickyConnPool(c.connPool))
|
||||
}
|
||||
|
||||
// Do creates a Cmd from the args and processes the cmd.
|
||||
// Do create a Cmd from the args and processes the cmd.
|
||||
func (c *Client) Do(ctx context.Context, args ...interface{}) *Cmd {
|
||||
cmd := NewCmd(ctx, args...)
|
||||
_ = c.Process(ctx, cmd)
|
||||
|
@ -648,26 +685,26 @@ func (c *Client) pubSub() *PubSub {
|
|||
// subscription may not be active immediately. To force the connection to wait,
|
||||
// you may call the Receive() method on the returned *PubSub like so:
|
||||
//
|
||||
// sub := client.Subscribe(queryResp)
|
||||
// iface, err := sub.Receive()
|
||||
// if err != nil {
|
||||
// // handle error
|
||||
// }
|
||||
// sub := client.Subscribe(queryResp)
|
||||
// iface, err := sub.Receive()
|
||||
// if err != nil {
|
||||
// // handle error
|
||||
// }
|
||||
//
|
||||
// // Should be *Subscription, but others are possible if other actions have been
|
||||
// // taken on sub since it was created.
|
||||
// switch iface.(type) {
|
||||
// case *Subscription:
|
||||
// // subscribe succeeded
|
||||
// case *Message:
|
||||
// // received first message
|
||||
// case *Pong:
|
||||
// // pong received
|
||||
// default:
|
||||
// // handle error
|
||||
// }
|
||||
// // Should be *Subscription, but others are possible if other actions have been
|
||||
// // taken on sub since it was created.
|
||||
// switch iface.(type) {
|
||||
// case *Subscription:
|
||||
// // subscribe succeeded
|
||||
// case *Message:
|
||||
// // received first message
|
||||
// case *Pong:
|
||||
// // pong received
|
||||
// default:
|
||||
// // handle error
|
||||
// }
|
||||
//
|
||||
// ch := sub.Channel()
|
||||
// ch := sub.Channel()
|
||||
func (c *Client) Subscribe(ctx context.Context, channels ...string) *PubSub {
|
||||
pubsub := c.pubSub()
|
||||
if len(channels) > 0 {
|
||||
|
|
Loading…
Reference in New Issue