In depth analysis of macaron 2 Middleware in go web Framework

Keywords: Go

The article continues to look at how the framework executes handler and some other middleware

Middleware format

func(resp http.ResponseWriter, req *http.Request)
func(ctx *macaron.Context)     
func(resp http.ResponseWriter, req *http.Request,ctx *macaron.Context)

Is it much more flexible than other web frameworks (* ~) ~

You can also customize the format

For example, call c.Map(value) first, and the middleware supports this form

func(value *valueType)    

How is middleware registered

Go back to the get method

// Get is a shortcut for r.Handle("GET", pattern, handlers)
func (r *Router) Get(pattern string, h ...Handler) (leaf *Route) {
   leaf = r.Handle("GET", pattern, h)
   if r.autoHead {
      r.Head(pattern, h...)
   }
   return leaf
}

There is such a step in the handle method

handlers = validateAndWrapHandlers(handlers, r.handlerWrapper)

The method is to validate and package the passed in instance

// validateAndWrapHandlers preforms validation and wrapping for each input handler.
// It accepts an optional wrapper function to perform custom wrapping on handlers.
func validateAndWrapHandlers(handlers []Handler, wrappers ...func(Handler) Handler) []Handler {
    var wrapper func(Handler) Handler
    if len(wrappers) > 0 {
        wrapper = wrappers[0]
    }
​
    wrappedHandlers := make([]Handler, len(handlers))
    for i, h := range handlers {
        h = validateAndWrapHandler(h)
        if wrapper != nil && !inject.IsFastInvoker(h) {
            h = wrapper(h)
        }
        wrappedHandlers[i] = h
    }
​
    return wrappedHandlers
}

Validateandwrapphandler implementation

// validateAndWrapHandler makes sure a handler is a callable function, it panics if not.
// When the handler is also potential to be any built-in inject.FastInvoker,
// it wraps the handler automatically to have some performance gain.
func validateAndWrapHandler(h Handler) Handler {
   if reflect.TypeOf(h).Kind() != reflect.Func { //handler is not a function, so you can use panic directly
      panic("Macaron handler must be a callable function")
   }
​
   if !inject.IsFastInvoker(h) { //If it is not fastnvoker type, perform the following conversion. If it is not fastnvoker type, it is not returned directly
      switch v := h.(type) {
      case func(*Context):
         return ContextInvoker(v)
      case func(*Context, *log.Logger):
         return LoggerInvoker(v)
      case func(http.ResponseWriter, *http.Request):
         return handlerFuncInvoker(v)
      case func(http.ResponseWriter, error):
         return internalServerErrorInvoker(v)
      }
   }
   return h
}

IsFastInvoker judgment

// IsFastInvoker check interface is FastInvoker
func IsFastInvoker(h interface{}) bool {
   _, ok := h.(FastInvoker)
   return ok
}
type FastInvoker interface {
    // Invoke attempts to call the ordinary functions. If f is a function
    // with the appropriate signature, f.Invoke([]interface{}) is a Call that calls f.
    // Returns a slice of reflect.Value representing the returned values of the function.
    // Returns an error if the injection fails.
    Invoke([]interface{}) ([]reflect.Value, error)
}

If the interface implements the Invoke method, it is FastInvoker. Let's see how the handler is wrapped

ContextInvoker

// ContextInvoker is an inject.FastInvoker wrapper of func(ctx *Context).
type ContextInvoker func(ctx *Context)
​
// Invoke implements inject.FastInvoker which simplifies calls of `func(ctx *Context)` function.
func (invoke ContextInvoker) Invoke(params []interface{}) ([]reflect.Value, error) {
   invoke(params[0].(*Context))
   return nil, nil
}

handlerFuncInvoker

// handlerFuncInvoker is an inject.FastInvoker wrapper of func(http.ResponseWriter, *http.Request).
type handlerFuncInvoker func(http.ResponseWriter, *http.Request)

func (invoke handlerFuncInvoker) Invoke(params []interface{}) ([]reflect.Value, error) {
   invoke(params[0].(http.ResponseWriter), params[1].(*http.Request))
   return nil, nil
}
  • If the handlers passed in by the user are these types, it will be converted to the corresponding function. If not, it will directly return the handler

  • Note that the return value is nil, which means that they can only be used as middleware in the future, and there is no return value

Middleware execution mode

func(resp http.ResponseWriter, req *http.Request, params Params) {
   c := r.m.createContext(resp, req)
   c.params = params
   c.handlers = make([]Handler, 0, len(r.m.handlers)+len(handlers))
   c.handlers = append(c.handlers, r.m.handlers...)//Add global default handle
   c.handlers = append(c.handlers, handlers...) //Add handle of user route
   c.run() //Execution Middleware
}

context structure

context.run()

func (ctx *Context) run() {
	for ctx.index <= len(ctx.handlers) {
		vals, err := ctx.Invoke(ctx.handler())
		if err != nil {
			panic(err)
		}
		ctx.index++

		// if the handler returned something, write it to the http response
		if len(vals) > 0 { //If the return value is greater than 0, it is obtained from the return value
			ev := ctx.GetVal(reflect.TypeOf(ReturnHandler(nil)))
			handleReturn := ev.Interface().(ReturnHandler) //Get the default ReturnHandler
			handleReturn(ctx, vals)
		}

		if ctx.Written() {
			return
		}
	}
}
  • This method is the same as the method of executing Middleware in all web frameworks. The handler is iterated, and the index is + 1 for each execution

  • ctx.handler() gets the middleware that has not executed the index at present. When the middleware is executed, action is executed. Action is also a handler. Only a special handler is executed after the middleware is executed. It is generally a business processing function.

func (ctx *Context) handler() Handler {
	if ctx.index < len(ctx.handlers) {
		return ctx.handlers[ctx.index]
	}
	if ctx.index == len(ctx.handlers) {
		return ctx.action
	}
	panic("invalid index for context handler")
}

action setting method

// Action sets the handler that will be called after all the middleware has been invoked.
// This is set to macaron.Router in a macaron.Classic().
func (m *Macaron) Action(handler Handler) {
	handler = validateAndWrapHandler(handler)
	m.action = handler
}
  • GetVal takes values from a map corresponding to a type and value. We will talk about the injector later. The framework will use the map method to map some values into the map. key is the type obtained by reflection and value is the value obtained by reflection. At that time, just get them according to the value

  • If a handler will write data to the front end, CTX. Write () will be true and return

ReturnHandler

This method processes the return value of the calling business, and vals is the value of the call

func defaultReturnHandler() ReturnHandler {
   return func(ctx *Context, vals []reflect.Value) {
      rv := ctx.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil)))
      resp := rv.Interface().(http.ResponseWriter)//Get the instance of resp from the injector
      var respVal reflect.Value
      if len(vals) > 1 && vals[0].Kind() == reflect.Int {//If vals length is > 1 and the first parameter is int, then this is the status code. Call resp.WriteHeader to write the status code
         resp.WriteHeader(int(vals[0].Int()))
         respVal = vals[1]
      } else if len(vals) > 0 {
         respVal = vals[0]

         if isError(respVal) {
            err := respVal.Interface().(error) //If the first response value is an error, respond directly to the server error
            if err != nil {
               ctx.internalServerError(ctx, err)
            }
            return
         } else if canDeref(respVal) {
            if respVal.IsNil() {
               return // Ignore nil error
            }
         }
      }
      if canDeref(respVal) {
         respVal = respVal.Elem()
      }
      if isByteSlice(respVal) {
         _, _ = resp.Write(respVal.Bytes())
      } else {
         _, _ = resp.Write([]byte(respVal.String()))
      }
   }
}
  • Get the response value first to see if the first value is of type int. if so, write this value as the status code instead of following the branch below

  • If the first value is error, it directly responds to the server error, and the internal server error is a callback, which can be set by yourself

    internalServerError func(*Context, error)

  • If it is a pointer or an interface, get the value of respVal

  • Finally, judge whether it is a byte slice. If it is a direct response, if not, obtain a string and respond

    internalServerError structure

// InternalServerError configurates handler which is called when route handler returns
// error. If it is not set, default handler is used.
// Be sure to set 500 response code in your handler.
func (r *Router) InternalServerError(handlers ...Handler) {
	handlers = validateAndWrapHandlers(handlers)
	r.internalServerError = func(c *Context, err error) {
		c.index = 0
		c.handlers = handlers
		c.Map(err)
		c.run()
	}
}

context.Next()

The essence of middleware is here

// Next runs the next handler in the context chain
func (ctx *Context) Next() {
   ctx.index++
   ctx.run()
}

For example, if you write a middleware like this, it must be executed in order

func main() {
	m := macaron.Classic()
	m.Get("/", func(ctx *macaron.Context) {
		fmt.Println("middleWare1")
	}, func() string {
		fmt.Println("hello1")
		return "Hello world!"
	})
	m.Run()
}

Print results when / is requested

middleWare1
hello1

If you add next, it is equivalent to a recursive call, and the dolls are executed

func main() {
	m := macaron.Classic()
	m.Get("/", func(ctx *macaron.Context) {
		fmt.Println("middleWare1")
		ctx.Next()
		fmt.Println("middleWare2")
	}, func() string {
		fmt.Println("hello1")
		return "Hello world!"
	})
	m.Run()
}

Last print result

middleWare1
hello1
middleWare2

Draw a diagram to describe the execution process

 

When the middleware calls next, it will be executed according to the sequence number in the figure. In the actual middleware application, if you want other middleware to execute first and then execute the code later, you have to call context.Next for execution, such as calculating the time consumption of business code.

For example, computing time-consuming middleware

func Logger() macaron.Handler {
	return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) {
		start := time.Now()
		c.Data["perfmon.start"] = start

		rw := res.(macaron.ResponseWriter)
		c.Next()

		timeTakenMs := time.Since(start) / time.Millisecond
}

ctx.Invoke

Next, let's look at the functions that execute the middleware

context does not have this function. It inherits from inject.Injector

// Context represents the runtime context of current request of Macaron instance.
// It is the integration of most frequently used middlewares and helper methods.
type Context struct {
   inject.Injector
   handlers []Handler
   action   Handler
   index    int

   *Router
   Req    Request
   Resp   ResponseWriter
   params Params
   Render
   Locale
   Data map[string]interface{}
}

When creating context, it is assigned as inject.New()

func (m *Macaron) createContext(rw http.ResponseWriter, req *http.Request) *Context {
	c := &Context{
		Injector: inject.New(),
		handlers: m.handlers,
		action:   m.action,
		index:    0,
		Router:   m.Router,
		Req:      Request{req},
		Resp:     NewResponseWriter(req.Method, rw),
		Render:   &DummyRender{rw},
		Data:     make(map[string]interface{}),
	}
	c.SetParent(m)
    c.Map(c) //Mapping C into it is map [reflect (c.type)] = reflect (c.value)
	c.MapTo(c.Resp, (*http.ResponseWriter)(nil))//Replace the value mapping of http.ResponseWriter type with your own resp. When you find the http.ResponseWriter type, your own resp will be passed in
	c.Map(req)//Map requests into
	return c
}

Injector

Create Injector

// New returns a new Injector.
func New() Injector {
	return &injector{
		values: make(map[reflect.Type]reflect.Value),
	}
}

Call the invoke function of hanlder

Invoke

// Invoke attempts to call the interface{} provided as a function,
// providing dependencies for function arguments based on Type.
// Returns a slice of reflect.Value representing the returned values of the function.
// Returns an error if the injection fails.
// It panics if f is not a function
func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) {
   t := reflect.TypeOf(f)
   switch v := f.(type) {
   case FastInvoker:
      return inj.fastInvoke(v, t, t.NumIn())
   default:
      return inj.callInvoke(f, t, t.NumIn())
   }
}
  • Get the type of handler first. If it is the type processed earlier, it is the type of context. Call fastInvoke. If it is not the default callInvoke

fastInvoke

func (inj *injector) fastInvoke(f FastInvoker, t reflect.Type, numIn int) ([]reflect.Value, error) {
	var in []interface{}
	if numIn > 0 {
		in = make([]interface{}, numIn) // Panic if t is not kind of Func
		var argType reflect.Type
		var val reflect.Value
		for i := 0; i < numIn; i++ {
			argType = t.In(i)
			val = inj.GetVal(argType)//Traverse the incoming parameter, find the value corresponding to the parameter type from the map, and then assign a value to the parameter
			if !val.IsValid() {
				return nil, fmt.Errorf("Value not found for type %v", argType)
			}

			in[i] = val.Interface()
		}
	}
	return f.Invoke(in)
}
  • Call the f.Invoke assignment directly, get the parameters, and then call f.Invoke to make the function call, which is the method of ContextInvoker at registration time.

callInvoke

// callInvoke reflect.Value.Call
func (inj *injector) callInvoke(f interface{}, t reflect.Type, numIn int) ([]reflect.Value, error) {
	var in []reflect.Value
	if numIn > 0 {
		in = make([]reflect.Value, numIn)
		var argType reflect.Type
		var val reflect.Value
		for i := 0; i < numIn; i++ { //Traverse the incoming parameter, find the value corresponding to the parameter type from the map, and then assign a value to the parameter
			argType = t.In(i)
			val = inj.GetVal(argType)
			if !val.IsValid() {
				return nil, fmt.Errorf("Value not found for type %v", argType)
			}

			in[i] = val
		}
	}
	return reflect.ValueOf(f).Call(in), nil //Reflection calls handler and passes the obtained value as a parameter
}
  • Traverse the handler. If the handler parameter is > 0, then traverse the parameters and take out the value of this type from the map. The premise is to map it through the map method, then assign the parameter, and finally call the handler through reflection to transfer the obtained value. This can explain why there are many types of Middleware in the framework, which is more flexible

Map

map is responsible for mapping the type of value to the value

// Maps the concrete value of val to its dynamic type using reflect.TypeOf,
// It returns the TypeMapper registered in.
func (i *injector) Map(val interface{}) TypeMapper {
	i.values[reflect.TypeOf(val)] = reflect.ValueOf(val)
	return i
}

The following example describes how to use the map method. The following example maps T1 type values into the middleware and writes func(t1*T1) to obtain values, which is a bit dependent on injection

type T1 int

func main() {
   m := macaron.Classic()
   var t T1=9999
   m.Map(&t)

   m.Get("/hello", func(t1*T1) {
      fmt.Println(*t1) //Print 9999
   }, myHandler)

   log.Println("Server is running...")
   log.Println(http.ListenAndServe("0.0.0.0:5000", m))
}

Apply

This method maps the fields of the structure. If the tag behind the structure is inject

// Maps dependencies in the Type map to each field in the struct
// that is tagged with 'inject'.
// Returns an error if the injection fails.
func (inj *injector) Apply(val interface{}) error {
   v := reflect.ValueOf(val)

   for v.Kind() == reflect.Ptr {
      v = v.Elem()
   }

   if v.Kind() != reflect.Struct {
      return nil // Should not panic here ?
   }

   t := v.Type()

   for i := 0; i < v.NumField(); i++ {
      f := v.Field(i)
      structField := t.Field(i)
      if f.CanSet() && (structField.Tag == "inject" || structField.Tag.Get("inject") != "") {
         ft := f.Type()
         v := inj.GetVal(ft)
         if !v.IsValid() {
            return fmt.Errorf("Value not found for type %v", ft)
         }

         f.Set(v)
      }

   }

   return nil
}

Take an example to verify the application of apply

type Test struct {
	T  * macaron.Context
	T1  * T1 `inject:"xx"`
}
type T1 int

func main() {
	m := macaron.Classic()
	var t T1=9999
	m.Map(&t)
	var t2 =&Test{}
	err:=m.Apply(t2)
	if err!=nil{
		panic(err)
	}


	fmt.Println(*t2.T1)
	m.Get("/hello", func(ctx * macaron.Context) {
	}, myHandler)

	log.Println("Server is running...")
	log.Println(http.ListenAndServe("0.0.0.0:5000", m))
}

func myHandler(ctx *macaron.Context) string {
	return "the request path is: " + ctx.Req.RequestURI
}
  • Apply puts the structure in. If the structure field is marked tag: inject, the structure field will be assigned a value through f.Set(v). In the following example of grafana application, I will explain how to apply this function in the project.

Posted by ArmyNation.Net on Tue, 21 Sep 2021 00:28:55 -0700