martini框架源码阅读分析

2024-03-13 21:18

本文主要是介绍martini框架源码阅读分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Martini核心部分Injector

Injector模块总体构造


injector对象:
   
type injector struct {
values map[reflect.Type]reflect.Value // 保存<类型,值>对
parent Injector
}

TypeMaper接口
   
type TypeMapper interface {
Map(interface{}) TypeMapper // 类型映射
MapTo(interface{}, interface{}) TypeMapper //将值映射为指定的类型
Set(reflect.Type, reflect.Value) TypeMapper // 设置类型值
Get(reflect.Type) reflect.Value
}

Injectorde主体是一个Injecotr接口,Injector接口组合了Application,Invoker,以及TypeMaper接口。核心函数Invoker,通过传入一个interface,进行后续的处理。interface的具体类型必须为函数,否则会panic。通过reflect进行反射获取interface的具体函数类型。
   
func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) {
t := reflect.TypeOf(f)
 
var in = make([]reflect.Value, t.NumIn()) //如果传入的interface不是函数,则panic
for i := 0; i < t.NumIn(); i++ {
argType := t.In(i)
val := inj.Get(argType) // 通过函数参数类型获取函数值,injector注册器通过map保存<类型,值>对。
if !val.IsValid() {
return nil, fmt.Errorf("Value not found for type %v", argType)
}
 
in[i] = val
}
 
return reflect.ValueOf(f).Call(in), nil
}
injector使用例子:
   
package main
 
import (
"fmt"
"github.com/codegangsta/inject"
)
 
type myString interface{}
 
func test1(st string, myst myString) {
fmt.Println(st, myst)
}
 
var (
st1 = "hello"
st2 = "WORLD"
    myst myString =="haha"
)
 
func main() {
 
inj := inject.New() \\生成injector实例
inj.Map(st1) \\ st1注册为默认的类型
inj.MapTo(st2, (*myString)(nil))\\ st2注册为指定的myStrig类型
inj.Invoke(test1) \\ 函数test1通过参数了类型获取参数具体的值
    fmt.Println(inject.InterfaceOf(&myst)) // myst必须为指向interface的指针,否则panic。 }
}
输出:

martini服务模块

Martini结构:


使用Matrini框架时,可以自己直接使用框架内置的classicMartini。使用方法:
   
package main
 
import (
"github.com/go-martini/martini"
"fmt"
"reflect"
)
 
func test1(st string) string {
return st
}
func test2(num int) int {
fmt.Println(num)
return num
}
 
type INT interface{}
 
func test3(num INT, st string) string {
st = st + fmt.Sprint(reflect.TypeOf(num))
return st
}
func main() {
m := martini.Classic() //生成martini实例
m.Get("/hello", test2, test1)// 注册路由
m.Get("/hi", test3)
m.Map("a") // 注册函数参数值
m.Map(1)
m.MapTo("HI", (*INT)(nil))
m.Run() //启动监听服务
}
工作流程:


1.使用Classic生成martini实例:
   
func Classic() *ClassicMartini {
r := NewRouter()
m := New() //生成martini实例
m.Use(Logger()) //注册Handler,实际是往martini.handlers里append一个Handler
m.Use(Recovery())
m.Use(Static("public"))
m.MapTo(r, (*Routes)(nil))
m.Action(r.Handle) // 注册路由处理函数,m.action=r.Handler
return &ClassicMartini{m, r}
}
2.注册路由函数
路由器实现:


Router接口:
   
type Router interface {
Routes
 
// Group adds a group where related routes can be added.
Group(string, func(Router), ...Handler)
// Get adds a route for a HTTP GET request to the specified matching pattern.
Get(string, ...Handler) Route
// Patch adds a route for a HTTP PATCH request to the specified matching pattern.
Patch(string, ...Handler) Route
// Post adds a route for a HTTP POST request to the specified matching pattern.
Post(string, ...Handler) Route
// Put adds a route for a HTTP PUT request to the specified matching pattern.
Put(string, ...Handler) Route
// Delete adds a route for a HTTP DELETE request to the specified matching pattern.
Delete(string, ...Handler) Route
// Options adds a route for a HTTP OPTIONS request to the specified matching pattern.
Options(string, ...Handler) Route
// Head adds a route for a HTTP HEAD request to the specified matching pattern.
Head(string, ...Handler) Route
// Any adds a route for any HTTP method request to the specified matching pattern.
Any(string, ...Handler) Route
// AddRoute adds a route for a given HTTP method request to the specified matching pattern.
AddRoute(string, string, ...Handler) Route
 
// NotFound sets the handlers that are called when a no route matches a request. Throws a basic 404 by default.
NotFound(...Handler)
 
// Handle is the entry point for routing. This is used as a martini.Handler
Handle(http.ResponseWriter, *http.Request, Context)
}
例子中使用Get注册路由,Martini中注册路由与其他框架不同的地方在于,路由的函数可以是任意类型,而且同一个路劲可以注册多个处理函数。
注册路由时,实际内部都是使用router.addroute()
   
func (r *router) addRoute(method string, pattern string, handlers []Handler) *route {
if len(r.groups) > 0 {
groupPattern := ""
h := make([]Handler, 0)
for _, g := range r.groups {
groupPattern += g.pattern
h = append(h, g.handlers...)
}
 
pattern = groupPattern + pattern
h = append(h, handlers...) //将注册的方法append进handlers ,请求路劲到此路由的时候会遍历调用该handlers,直到函数有返回值
handlers = h
}
fmt.Println("handlers:", reflect.ValueOf(handlers))
route := newRoute(method, pattern, handlers) // 生成指定路径方法的路由
fmt.Println("route:", reflect.ValueOf(route.handlers))
route.Validate()
r.appendRoute(route)
return route
}
addRoute生成一个route并添加进router.routes。当客户端请求时,遍历改routes获取对应路由。
addRoute方法调用了newRoute返回一个*route
route结构
   
type route struct {
method string
regex *regexp.Regexp
handlers []Handler
pattern string
name string
}
newRoute将注册的方法添加进handlers。
例子中的 m . Get ( "/hello" , test2 , test1 )及将test1,test2这两个函数添加去route.handlers.
当请求路径为/hello时,就会调用这两个方法。
3.map为函数参数注入参数值
4.run启动服务器监听请求
   
func (m *Martini) ServeHTTP(res http.ResponseWriter, req *http.Request) {
//res.Write([]byte("hello"))
 
m.createContext(res, req).run()
}
ServerHTTP是每一个request请求实例的入口。
对于每一个req实例,createContext生成一个上下文。
   
func (m *Martini) createContext(res http.ResponseWriter, req *http.Request) *context {
c := &context{inject.New(), m.handlers, m.action, NewResponseWriter(res), 0} //生成context实例
c.SetParent(m)
c.MapTo(c, (*Context)(nil)) //注入请求参数值
c.MapTo(c.rw, (*http.ResponseWriter)(nil))
c.Map(req)
return c
}
run进行路由处理。
   
func (c *context) run() {
 
for c.index <= len(c.handlers) { // 遍历handlers,handlers实际为例子中使用USE注册的logger,recovery。当左右相等时,则调用m.action,及路由处理器
 
_, err := c.Invoke(c.handler())
if err != nil {
panic(err)
}
c.index += 1 // 计数器,用来遍历注册的路由器
 
if c.Written() {
return
}
}
}
run首先调用第一个注册的Logger。
   
func Logger() Handler {
return func(res http.ResponseWriter, req *http.Request, c Context, log *log.Logger) {
start := time.Now()
 
addr := req.Header.Get("X-Real-IP")
if addr == "" {
addr = req.Header.Get("X-Forwarded-For")
if addr == "" {
addr = req.RemoteAddr
}
}
 
log.Printf("Started %s %s for %s", req.Method, req.URL.Path, addr)
 
rw := res.(ResponseWriter)
c.Next() //调用第二个注册的recovery
 
log.Printf("Completed %v %s in %v\n", rw.Status(), http.StatusText(rw.Status()), time.Since(start))
}
}
Martini一个神奇的地方在于Next这个函数,log,recovery记忆Handle通过next实现嵌套调用。
其次调用Recovery
   
func Recovery() Handler {
return func(c Context, log *log.Logger) {
defer func() { // 捕获路由处理panic
if err := recover(); err != nil {
stack := stack(3)
log.Printf("PANIC: %s\n%s", err, stack)
 
// Lookup the current responsewriter
val := c.Get(inject.InterfaceOf((*http.ResponseWriter)(nil)))
res := val.Interface().(http.ResponseWriter)
 
// respond with panic message while in development mode
var body []byte
if Env == Dev {
res.Header().Set("Content-Type", "text/html")
body = []byte(fmt.Sprintf(panicHtml, err, err, stack))
} else {
body = []byte("500 Internal Server Error")
}
 
res.WriteHeader(http.StatusInternalServerError)
if nil != body {
res.Write(body)
}
}
}()
 
c.Next() // 调用Handle进行请求路由处理
}
}
router.Handle路由处理
   
 
func (r *router) Handle(res http.ResponseWriter, req *http.Request, context Context) {
fmt.Println(reflect.TypeOf(r.getRoutes()))
bestMatch := NoMatch
var bestVals map[string]string
var bestRoute *route
for _, route := range r.getRoutes() {
 
match, vals := route.Match(req.Method, req.URL.Path)
fmt.Println(match, vals)
if match.BetterThan(bestMatch) {
bestMatch = match // 遍历查找匹配的路径路由
bestVals = vals
bestRoute = route
if match == ExactMatch {
break
}
}
}
if bestMatch != NoMatch {
fmt.Println("bestvals", bestVals)
params := Params(bestVals)
context.Map(params)
bestRoute.Handle(context, res) // 路由器逻辑处理
return
}
 
// no routes exist, 404
c := &routeContext{context, 0, r.notFounds}
context.MapTo(c, (*Context)(nil))
c.run()
}
路由业务逻辑处理route.Handle
   
func (r *route) Handle(c Context, res http.ResponseWriter) {
context := &routeContext{c, 0, r.handlers} // 初始化context,例子中对于/hello路径的请求r.handlers =[test2,test1]
c.MapTo(context, (*Context)(nil))
c.MapTo(r, (*Route)(nil))
context.run() // 路由业务逻辑处理,遍历r.handlers获取业务逻辑方法
}
run业务逻辑处理
   
func (r *routeContext) run() {
for r.index < len(r.handlers) {// 遍历handlers,实际实现时可以重写这一方法,自定义要调用方法的顺序。通过设置index可以自定义调用
handler := r.handlers[r.index]
vals, err := r.Invoke(handler)
if err != nil {
panic(err)
}
r.index += 1
 
// if the handler returned something, write it to the http response
//如果r.handlers的函数有返回值,则把返回值写入resp并设置r.writern()的返回值为true
if len(vals) > 0 {
ev := r.Get(reflect.TypeOf(ReturnHandler(nil)))
handleReturn := ev.Interface().(ReturnHandler)
handleReturn(r, vals)
}
 
if r.Written() {
return
}
}
}


实际使用Martini框架时,也可以根据需要自定义重写martini结果。重写martini结构后续的流程处理也是类似的。

这篇关于martini框架源码阅读分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/806230

相关文章

GSON框架下将百度天气JSON数据转JavaBean

《GSON框架下将百度天气JSON数据转JavaBean》这篇文章主要为大家详细介绍了如何在GSON框架下实现将百度天气JSON数据转JavaBean,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录前言一、百度天气jsON1、请求参数2、返回参数3、属性映射二、GSON属性映射实战1、类对象映

Android 缓存日志Logcat导出与分析最佳实践

《Android缓存日志Logcat导出与分析最佳实践》本文全面介绍AndroidLogcat缓存日志的导出与分析方法,涵盖按进程、缓冲区类型及日志级别过滤,自动化工具使用,常见问题解决方案和最佳实... 目录android 缓存日志(Logcat)导出与分析全攻略为什么要导出缓存日志?按需过滤导出1. 按

解决若依微服务框架启动报错的问题

《解决若依微服务框架启动报错的问题》Invalidboundstatement错误通常由MyBatis映射文件未正确加载或Nacos配置未读取导致,需检查XML的namespace与方法ID是否匹配,... 目录ruoyi-system模块报错报错详情nacos文件目录总结ruoyi-systnGLNYpe

Linux中的HTTPS协议原理分析

《Linux中的HTTPS协议原理分析》文章解释了HTTPS的必要性:HTTP明文传输易被篡改和劫持,HTTPS通过非对称加密协商对称密钥、CA证书认证和混合加密机制,有效防范中间人攻击,保障通信安全... 目录一、什么是加密和解密?二、为什么需要加密?三、常见的加密方式3.1 对称加密3.2非对称加密四、

MySQL中读写分离方案对比分析与选型建议

《MySQL中读写分离方案对比分析与选型建议》MySQL读写分离是提升数据库可用性和性能的常见手段,本文将围绕现实生产环境中常见的几种读写分离模式进行系统对比,希望对大家有所帮助... 目录一、问题背景介绍二、多种解决方案对比2.1 原生mysql主从复制2.2 Proxy层中间件:ProxySQL2.3

python使用Akshare与Streamlit实现股票估值分析教程(图文代码)

《python使用Akshare与Streamlit实现股票估值分析教程(图文代码)》入职测试中的一道题,要求:从Akshare下载某一个股票近十年的财务报表包括,资产负债表,利润表,现金流量表,保存... 目录一、前言二、核心知识点梳理1、Akshare数据获取2、Pandas数据处理3、Matplotl

python panda库从基础到高级操作分析

《pythonpanda库从基础到高级操作分析》本文介绍了Pandas库的核心功能,包括处理结构化数据的Series和DataFrame数据结构,数据读取、清洗、分组聚合、合并、时间序列分析及大数据... 目录1. Pandas 概述2. 基本操作:数据读取与查看3. 索引操作:精准定位数据4. Group

MySQL中EXISTS与IN用法使用与对比分析

《MySQL中EXISTS与IN用法使用与对比分析》在MySQL中,EXISTS和IN都用于子查询中根据另一个查询的结果来过滤主查询的记录,本文将基于工作原理、效率和应用场景进行全面对比... 目录一、基本用法详解1. IN 运算符2. EXISTS 运算符二、EXISTS 与 IN 的选择策略三、性能对比

MySQL 内存使用率常用分析语句

《MySQL内存使用率常用分析语句》用户整理了MySQL内存占用过高的分析方法,涵盖操作系统层确认及数据库层bufferpool、内存模块差值、线程状态、performance_schema性能数据... 目录一、 OS层二、 DB层1. 全局情况2. 内存占js用详情最近连续遇到mysql内存占用过高导致

深度解析Nginx日志分析与499状态码问题解决

《深度解析Nginx日志分析与499状态码问题解决》在Web服务器运维和性能优化过程中,Nginx日志是排查问题的重要依据,本文将围绕Nginx日志分析、499状态码的成因、排查方法及解决方案展开讨论... 目录前言1. Nginx日志基础1.1 Nginx日志存放位置1.2 Nginx日志格式2. 499