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

相关文章

Redis连接失败:客户端IP不在白名单中的问题分析与解决方案

《Redis连接失败:客户端IP不在白名单中的问题分析与解决方案》在现代分布式系统中,Redis作为一种高性能的内存数据库,被广泛应用于缓存、消息队列、会话存储等场景,然而,在实际使用过程中,我们可能... 目录一、问题背景二、错误分析1. 错误信息解读2. 根本原因三、解决方案1. 将客户端IP添加到Re

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专

Spring中Bean有关NullPointerException异常的原因分析

《Spring中Bean有关NullPointerException异常的原因分析》在Spring中使用@Autowired注解注入的bean不能在静态上下文中访问,否则会导致NullPointerE... 目录Spring中Bean有关NullPointerException异常的原因问题描述解决方案总结

python中的与时间相关的模块应用场景分析

《python中的与时间相关的模块应用场景分析》本文介绍了Python中与时间相关的几个重要模块:`time`、`datetime`、`calendar`、`timeit`、`pytz`和`dateu... 目录1. time 模块2. datetime 模块3. calendar 模块4. timeit

python-nmap实现python利用nmap进行扫描分析

《python-nmap实现python利用nmap进行扫描分析》Nmap是一个非常用的网络/端口扫描工具,如果想将nmap集成进你的工具里,可以使用python-nmap这个python库,它提供了... 目录前言python-nmap的基本使用PortScanner扫描PortScannerAsync异

MyBatis框架实现一个简单的数据查询操作

《MyBatis框架实现一个简单的数据查询操作》本文介绍了MyBatis框架下进行数据查询操作的详细步骤,括创建实体类、编写SQL标签、配置Mapper、开启驼峰命名映射以及执行SQL语句等,感兴趣的... 基于在前面几章我们已经学习了对MyBATis进行环境配置,并利用SqlSessionFactory核

Oracle数据库执行计划的查看与分析技巧

《Oracle数据库执行计划的查看与分析技巧》在Oracle数据库中,执行计划能够帮助我们深入了解SQL语句在数据库内部的执行细节,进而优化查询性能、提升系统效率,执行计划是Oracle数据库优化器为... 目录一、什么是执行计划二、查看执行计划的方法(一)使用 EXPLAIN PLAN 命令(二)通过 S

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置