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.
|
// ParseClusterURL parses a URL into ClusterOptions that can be used to connect to Redis.
|
||||||
// The URL must be in the form:
|
// The URL must be in the form:
|
||||||
// redis://<user>:<password>@<host>:<port>
|
//
|
||||||
// or
|
// redis://<user>:<password>@<host>:<port>
|
||||||
// rediss://<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:
|
// 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
|
// redis://<user>:<password>@<host>:<port>?addr=<host2>:<port2>&addr=<host3>:<port3>
|
||||||
// rediss://<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:
|
// 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
|
// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries
|
||||||
// - only scalar type fields are supported (bool, int, time.Duration)
|
// - only scalar type fields are supported (bool, int, time.Duration)
|
||||||
// - for time.Duration fields, values must be a valid input for time.ParseDuration();
|
// - 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
|
// 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
|
// - 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
|
// value, leave the value blank or remove the parameter
|
||||||
// - only the last value is interpreted if a parameter is given multiple times
|
// - 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
|
// - fields "network", "addr", "username" and "password" can only be set using other
|
||||||
// URL attributes (scheme, host, userinfo, resp.), query paremeters using these
|
// URL attributes (scheme, host, userinfo, resp.), query paremeters using these
|
||||||
// names will be treated as unknown parameters
|
// names will be treated as unknown parameters
|
||||||
// - unknown parameter names will result in an error
|
// - unknown parameter names will result in an error
|
||||||
|
//
|
||||||
// Example:
|
// Example:
|
||||||
// redis://user:password@localhost:6789?dial_timeout=3&read_timeout=6s&addr=localhost:6790&addr=localhost:6791
|
//
|
||||||
// is equivalent to:
|
// redis://user:password@localhost:6789?dial_timeout=3&read_timeout=6s&addr=localhost:6790&addr=localhost:6791
|
||||||
// &ClusterOptions{
|
// is equivalent to:
|
||||||
// Addr: ["localhost:6789", "localhost:6790", "localhost:6791"]
|
// &ClusterOptions{
|
||||||
// DialTimeout: 3 * time.Second, // no time unit = seconds
|
// Addr: ["localhost:6789", "localhost:6790", "localhost:6791"]
|
||||||
// ReadTimeout: 6 * time.Second,
|
// DialTimeout: 3 * time.Second, // no time unit = seconds
|
||||||
// }
|
// ReadTimeout: 6 * time.Second,
|
||||||
|
// }
|
||||||
func ParseClusterURL(redisURL string) (*ClusterOptions, error) {
|
func ParseClusterURL(redisURL string) (*ClusterOptions, error) {
|
||||||
o := &ClusterOptions{}
|
o := &ClusterOptions{}
|
||||||
|
|
||||||
|
|
17
command.go
17
command.go
|
@ -1110,15 +1110,16 @@ func (cmd *KeyValueSliceCmd) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Many commands will respond to two formats:
|
// Many commands will respond to two formats:
|
||||||
// 1) 1) "one"
|
// 1. 1) "one"
|
||||||
// 2) (double) 1
|
// 2. (double) 1
|
||||||
// 2) 1) "two"
|
// 2. 1) "two"
|
||||||
// 2) (double) 2
|
// 2. (double) 2
|
||||||
|
//
|
||||||
// OR:
|
// OR:
|
||||||
// 1) "two"
|
// 1. "two"
|
||||||
// 2) (double) 2
|
// 2. (double) 2
|
||||||
// 3) "one"
|
// 3. "one"
|
||||||
// 4) (double) 1
|
// 4. (double) 1
|
||||||
func (cmd *KeyValueSliceCmd) readReply(rd *proto.Reader) error { // nolint:dupl
|
func (cmd *KeyValueSliceCmd) readReply(rd *proto.Reader) error { // nolint:dupl
|
||||||
n, err := rd.ReadArrayLen()
|
n, err := rd.ReadArrayLen()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -32,7 +32,9 @@ type Once struct {
|
||||||
|
|
||||||
// Do calls the function f if and only if Do has not been invoked
|
// 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
|
// 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
|
// 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
|
// 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
|
// 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
|
// 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
|
// is niladic, it may be necessary to use a function literal to capture the
|
||||||
// arguments to a function to be invoked by Do:
|
// 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 {
|
func (o *Once) Do(f func() error) error {
|
||||||
if atomic.LoadUint32(&o.done) == 1 {
|
if atomic.LoadUint32(&o.done) == 1 {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scan parses bytes `b` to `v` with appropriate type.
|
// Scan parses bytes `b` to `v` with appropriate type.
|
||||||
|
//
|
||||||
//nolint:gocyclo
|
//nolint:gocyclo
|
||||||
func Scan(b []byte, v interface{}) error {
|
func Scan(b []byte, v interface{}) error {
|
||||||
switch v := v.(type) {
|
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.
|
// Scheme is required.
|
||||||
// There are two connection types: by tcp socket and by unix socket.
|
// There are two connection types: by tcp socket and by unix socket.
|
||||||
// Tcp connection:
|
// Tcp connection:
|
||||||
// redis://<user>:<password>@<host>:<port>/<db_number>
|
//
|
||||||
|
// redis://<user>:<password>@<host>:<port>/<db_number>
|
||||||
|
//
|
||||||
// Unix connection:
|
// 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:
|
// 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
|
// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries
|
||||||
// - only scalar type fields are supported (bool, int, time.Duration)
|
// - only scalar type fields are supported (bool, int, time.Duration)
|
||||||
// - for time.Duration fields, values must be a valid input for time.ParseDuration();
|
// - 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
|
// 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
|
// - 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
|
// value, leave the value blank or remove the parameter
|
||||||
// - only the last value is interpreted if a parameter is given multiple times
|
// - 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
|
// - fields "network", "addr", "username" and "password" can only be set using other
|
||||||
// URL attributes (scheme, host, userinfo, resp.), query paremeters using these
|
// URL attributes (scheme, host, userinfo, resp.), query paremeters using these
|
||||||
// names will be treated as unknown parameters
|
// names will be treated as unknown parameters
|
||||||
// - unknown parameter names will result in an error
|
// - unknown parameter names will result in an error
|
||||||
|
//
|
||||||
// Examples:
|
// Examples:
|
||||||
// redis://user:password@localhost:6789/3?dial_timeout=3&db=1&read_timeout=6s&max_retries=2
|
//
|
||||||
// is equivalent to:
|
// redis://user:password@localhost:6789/3?dial_timeout=3&db=1&read_timeout=6s&max_retries=2
|
||||||
// &Options{
|
// is equivalent to:
|
||||||
// Network: "tcp",
|
// &Options{
|
||||||
// Addr: "localhost:6789",
|
// Network: "tcp",
|
||||||
// DB: 1, // path "/3" was overridden by "&db=1"
|
// Addr: "localhost:6789",
|
||||||
// DialTimeout: 3 * time.Second, // no time unit = seconds
|
// DB: 1, // path "/3" was overridden by "&db=1"
|
||||||
// ReadTimeout: 6 * time.Second,
|
// DialTimeout: 3 * time.Second, // no time unit = seconds
|
||||||
// MaxRetries: 2,
|
// ReadTimeout: 6 * time.Second,
|
||||||
// }
|
// MaxRetries: 2,
|
||||||
|
// }
|
||||||
func ParseURL(redisURL string) (*Options, error) {
|
func ParseURL(redisURL string) (*Options, error) {
|
||||||
u, err := url.Parse(redisURL)
|
u, err := url.Parse(redisURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
81
redis.go
81
redis.go
|
@ -29,9 +29,9 @@ func SetLogger(logger internal.Logging) {
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
type Hook interface {
|
type Hook interface {
|
||||||
DialHook(hook DialHook) DialHook
|
DialHook(next DialHook) DialHook
|
||||||
ProcessHook(hook ProcessHook) ProcessHook
|
ProcessHook(next ProcessHook) ProcessHook
|
||||||
ProcessPipelineHook(hook ProcessPipelineHook) ProcessPipelineHook
|
ProcessPipelineHook(next ProcessPipelineHook) ProcessPipelineHook
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -48,6 +48,43 @@ type hooks struct {
|
||||||
processTxPipelineHook ProcessPipelineHook
|
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) {
|
func (hs *hooks) AddHook(hook Hook) {
|
||||||
hs.slice = append(hs.slice, hook)
|
hs.slice = append(hs.slice, hook)
|
||||||
hs.dialHook = hook.DialHook(hs.dialHook)
|
hs.dialHook = hook.DialHook(hs.dialHook)
|
||||||
|
@ -575,7 +612,7 @@ func (c *Client) Conn() *Conn {
|
||||||
return newConn(c.opt, pool.NewStickyConnPool(c.connPool))
|
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 {
|
func (c *Client) Do(ctx context.Context, args ...interface{}) *Cmd {
|
||||||
cmd := NewCmd(ctx, args...)
|
cmd := NewCmd(ctx, args...)
|
||||||
_ = c.Process(ctx, cmd)
|
_ = 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,
|
// subscription may not be active immediately. To force the connection to wait,
|
||||||
// you may call the Receive() method on the returned *PubSub like so:
|
// you may call the Receive() method on the returned *PubSub like so:
|
||||||
//
|
//
|
||||||
// sub := client.Subscribe(queryResp)
|
// sub := client.Subscribe(queryResp)
|
||||||
// iface, err := sub.Receive()
|
// iface, err := sub.Receive()
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// // handle error
|
// // handle error
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// // Should be *Subscription, but others are possible if other actions have been
|
// // Should be *Subscription, but others are possible if other actions have been
|
||||||
// // taken on sub since it was created.
|
// // taken on sub since it was created.
|
||||||
// switch iface.(type) {
|
// switch iface.(type) {
|
||||||
// case *Subscription:
|
// case *Subscription:
|
||||||
// // subscribe succeeded
|
// // subscribe succeeded
|
||||||
// case *Message:
|
// case *Message:
|
||||||
// // received first message
|
// // received first message
|
||||||
// case *Pong:
|
// case *Pong:
|
||||||
// // pong received
|
// // pong received
|
||||||
// default:
|
// default:
|
||||||
// // handle error
|
// // handle error
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// ch := sub.Channel()
|
// ch := sub.Channel()
|
||||||
func (c *Client) Subscribe(ctx context.Context, channels ...string) *PubSub {
|
func (c *Client) Subscribe(ctx context.Context, channels ...string) *PubSub {
|
||||||
pubsub := c.pubSub()
|
pubsub := c.pubSub()
|
||||||
if len(channels) > 0 {
|
if len(channels) > 0 {
|
||||||
|
|
Loading…
Reference in New Issue