docs: add a description of the hook

Signed-off-by: monkey92t <golang@88.com>
This commit is contained in:
monkey92t 2023-01-07 16:30:56 +08:00
parent af893143ae
commit d42dd1007c
6 changed files with 132 additions and 79 deletions

View File

@ -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{}

View File

@ -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 {

View File

@ -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

View File

@ -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) {

View File

@ -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 {

View File

@ -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 {