教你如何搭建自己的go-gin框架
1、使用http包默认启动web服务
- 基于go1.14 开启modules模式
- 模仿gin框架的简单优雅
- 介绍net/http库的简单使用
- 搭建框架雏形
- 代码地址 https://github.com/18211167516/go-Ebb/tree/master/day1-base
Go语言内置了 net/http库,封装了HTTP网络编程的基础的接口,我们实现的ebb Web 框架便是基于net/http的。我们接下来通过一个例子,简单介绍下这个库的使用。
go mode init explame
package main
import (
"fmt"
"net/http"
)
func indexHandler(w http.ResponseWriter,r *http.Request){
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
}
func helloHandler(w http.ResponseWriter,r *http.Request){
fmt.Fprintf(w,"URL.PATH=%q\n Header=%q\n",r.URL.Path,r.Header)
}
func main(){
http.HandleFunc("/",indexHandler)
http.HandleFunc("/hello",helloHandler)
http.ListenAndServe(":8080",nil)
}
go build .
./examplde.exe
启动web服务
访问 127.0.0.1:8080/
访问 127.0.0.1:8080/hello
main 函数的最后一行,是用来启动 Web 服务的,第一个参数是地址,:9999表示在 9999 端口监听。而第二个参数则代表处理所有的HTTP请求的实例,nil 代表使用标准库中的实例处理。第二个参数,则是我们基于net/http标准库实现Web框架的入口
2、接下来我们需要自己实现
- 新建examplde2目录
- 新建ebb目录
- 在ebb目录 执行 go mod init ebb
package ebb
import (
"net/http"
"log"
)
//定义框架请求处理方法
type HandlerFunc func(w http.ResponseWriter,req *http.Request)
//核心结构体
type Engine struct{
router map[string]HandlerFunc //简单使用map记录路由信息
}
//实例化结构体
func New() *Engine{
engine := &Engine{
router : make(map[string]HandlerFunc),
}
return engine
}
//添加到结构体路由
func (engine *Engine) addRoute(mothod string,pattern string,handler HandlerFunc){
key := mothod+"-"+pattern
engine.router[key] = handler
}
func (engine *Engine) GET(pattern string,handler HandlerFunc){
engine.addRoute("GET",pattern,handler)
}
func (engine *Engine) POST(pattern string,handler HandlerFunc){
engine.addRoute("POST",pattern,handler)
}
//启动服务
func (engine *Engine) Run(addr string) (err error){
return http.ListenAndServe(addr,engine)
}
//engine 实现ServeHTTP接口(所有的请求都会走到这)
//查找是否路由映射表存在,如果存在则调用,否则返回404
func (engine *Engine) ServeHTTP(w http.ResponseWriter,req *http.Request){
key := req.Method + "-" + req.URL.Path
if handler, ok := engine.router[key]; ok {
handler(w, req)
} else {
log.Printf("404 NOT FOUND: %s\n", req.URL)
}
}
在exmpale2目录下新建main.go 并且执行 go mod init example
编辑go.mod(在 go.mod 中使用 replace 将 ebb 指向 ./ebb)
module example
go 1.14
require ebb v0.0.0
replace ebb => ./ebb
- 编辑main.go
package main
import (
"ebb"
"fmt"
"net/http"
)
func main(){
r := ebb.New()
r.GET("/index",func(w http.ResponseWriter,req *http.Request){
fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
})
r.GET("/hello", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintln(w, req.Header)
})
r.Run(":8080")
}
框架流程
- 定义HandlerFunc(框架的请求方法)
- 定义核心结构体Engine 目前只存储路由信息,路由信息由映射表实现(路由只支持静态路由)
- GET、POST提供路由注册
- Run封装http.ListenAndServe
- 核心结构体Engine实现ServeHTTP接口(所有请求),查找路由并执行
到目前为止,一个简易的框架已经OK、后续我们会将HandlerFunc方法优化,也就是Context.期待下一篇文章
封装context
简单说下本章的重点
- 1、抽离路由
- 2、将writer和request封装成Context,以方便后续扩展,封装了返回类型
- 3、优化框架源码,抽离封装了handleHTTPRequest方法
- 4、代码地址 https://github.com/18211167516/go-Ebb/tree/master/day2-context
抽离路由
- 新建router.go
package ebb
type router struct{
handlers map[string]HandlerFunc
}
func newRouter() *router{
return &router{handlers:make(map[string]HandlerFunc)}
}
func (r *router) addRoute(method string,pattern string,handler HandlerFunc){
key := method+"-"+pattern
r.handlers[key] = handler
}
设计Context的必要性
- 对外接口简化调用 (封装了writer和request)
- 提供了扩展性(比如支持动态参数、支持中间件等等)
package ebb
import (
"net/http"
"fmt"
"encoding/json"
)
type H map[string]interface{}
type Context struct{
//write and request
Writer http.ResponseWriter
Request *http.Request
//request info
Method string
Path string
//response
HttpCode int
}
func newContext(w http.ResponseWriter,r *http.Request) *Context{
context := &Context{
Writer:w,
Request:r,
Path: r.URL.Path,
Method: r.Method,
}
return context
}
func (c *Context) PostForm(key string) string {
return c.Request.FormValue(key)
}
func (c *Context) Query(key string) string {
return c.Request.URL.Query().Get(key)
}
func (c *Context) Status(code int) {
c.HttpCode = code
c.Writer.WriteHeader(code)
}
func (c *Context) SetHeader(key string, value string) {
c.Writer.Header().Set(key, value)
}
func (c *Context) Write(data []byte){
c.Writer.Write(data)
}
func (c *Context) String(code int,message string,v ...interface{}){
c.SetHeader("Content-Type", "text/plain")
c.Status(code)
c.Write([]byte(fmt.Sprintf(message, v...)))
}
func (c *Context) JSON(code int, obj interface{}) {
c.SetHeader("Content-Type", "application/json")
c.Status(code)
data,err:= json.Marshal(obj)
if err!=nil {
http.Error(c.Writer, err.Error(), 500)
}
c.Write(data)
}
func (c *Context) HTML(code int,html string){
c.SetHeader("Content-Type", "text/html")
c.Status(code)
c.Write([]byte(html))
}
- 修改ebb.go
package ebb
import (
"net/http"
)
//重新定义框架请求处理方法
type HandlerFunc func(*Context)
//核心结构体
type Engine struct{
router *router
}
//实例化结构体
func New() *Engine{
engine := &Engine{
router : newRouter(),
}
return engine
}
//添加到结构体路由
func (engine *Engine) addRoute(mothod string,pattern string,handler HandlerFunc){
engine.router.addRoute(mothod,pattern,handler)
}
func (engine *Engine) GET(pattern string,handler HandlerFunc){
engine.addRoute("GET",pattern,handler)
}
func (engine *Engine) POST(pattern string,handler HandlerFunc){
engine.addRoute("POST",pattern,handler)
}
//启动服务
func (engine *Engine) Run(addr string) (err error){
return http.ListenAndServe(addr,engine)
}
//engine 实现ServeHTTP接口(所有的请求都会走到这)
//查找是否路由映射表存在,如果存在则调用,否则返回404
func (engine *Engine) ServeHTTP(w http.ResponseWriter,req *http.Request){
c := newContext(w, req)
engine.handleHTTPRequest(c)
}
//v2 新增
func (engine *Engine) handleHTTPRequest(c *Context){
key := c.Method + "-" + c.Path
if handler, ok := engine.router.handlers[key]; ok {
handler(c)
} else {
c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path)
}
}
入口文件应用
package main
import (
"ebb"
)
func main(){
r := ebb.New()
r.GET("/", func(c *ebb.Context) {
c.HTML(200, "<h1>Hello ebb</h1>")
})
r.GET("/hello", func(c *ebb.Context) {
c.String(200, "hello %s, you from %s\n", c.Query("name"), c.Path)
})
r.POST("/login", func(c *ebb.Context) {
c.JSON(200, ebb.H{
"name": c.PostForm("name"),
})
})
r.Run(":8080")
}
动态路由设计
简单说下本章的重点
- 1、修改路由存储数据结构(由字典变成前缀树)
- 2、路由支持动态参数
- 3、优化Context,将动态参数的键值对存储起来
- 4、编写单元测试、执行案例测试
- 5、代码地址 https://github.com/18211167516/go-Ebb/tree/master/day3-router
1、Trie 前缀树实现
package ebb
import (
//"fmt"
"strings"
)
type node struct {
pattern string // 待匹配路由,例如 /p/:lang
part string // 路由中的一部分,例如 :lang
children []*node // 子节点,例如 [doc, tutorial, intro]
isWild bool // 是否精确匹配,part 含有 : 或 * 时为true
}
func (n *node) matchChild(part string) *node {
for _, child := range n.children {
if child.part == part || child.isWild {
return child
}
}
return nil
}
// 所有匹配成功的节点,用于查找
func (n *node) matchChildren(part string) []*node {
nodes := make([]*node, 0)
for _, child := range n.children {
if child.part == part || child.isWild {
nodes = append(nodes, child)
}
}
return nodes
}
func parsePattern(pattern string) []string {
vs := strings.Split(pattern, "/")
parts := make([]string, 0)
for _, item := range vs {
if item != "" {
parts = append(parts, item)
if item[0] == '*' {
break
}
}
}
return parts
}
func (n *node) insert(pattern string, parts []string, height int) {
if len(parts) == height {
n.pattern = pattern
return
}
part := parts[height]
child := n.matchChild(part)
if child == nil {
child = &node{part: part, isWild: part[0] == ':' || part[0] == '*'}
n.children = append(n.children, child)
}
child.insert(pattern, parts, height+1)
}
func (n *node) search(parts []string, height int) *node {
if len(parts) == height || strings.HasPrefix(n.part, "*") {
if n.pattern == "" {
return nil
}
return n
}
part := parts[height]
children := n.matchChildren(part)
for _, child := range children {
result := child.search(parts, height+1)
if result != nil {
return result
}
}
return nil
}
2、优化路由类
package ebb
import (
"strings"
)
type router struct{
roots map[string]*node
handlers map[string]HandlerFunc
}
func newRouter() *router{
return &router{
roots:make(map[string]*node),
handlers:make(map[string]HandlerFunc),
}
}
func (r *router) addRoute(method string,pattern string,handler HandlerFunc){
parts := parsePattern(pattern)
key := method + "-" + pattern
_, ok := r.roots[method]
if !ok {
r.roots[method] = &node{}
}
r.roots[method].insert(pattern, parts, 0)
r.handlers[key] = handler
}
func (r *router) getRoute(method string, pattern string) (*node, map[string]interface{}) {
searchParts := parsePattern(pattern)
params := make(map[string]interface{})
root, ok := r.roots[method]
if !ok {
return nil, nil
}
n := root.search(searchParts, 0)
if n != nil {
parts := parsePattern(n.pattern)
for index, part := range parts {
if part[0] == ':' {
params[part[1:]] = searchParts[index]
}
if part[0] == '*' && len(part) > 1 {
params[part[1:]] = strings.Join(searchParts[index:], "/")
break
}
}
return n, params
}
return nil, nil
}
3、优化核心ebb的handleHTTPRequest
func (engine *Engine) handleHTTPRequest(c *Context){
node,params := engine.router.getRoute(c.Method,c.Path)
if node != nil {
c.Params = params //将params的值存储到Context
key := c.Method + "-" + node.pattern
//调用实际的方法
engine.router.handlers[key](c)
}else{
c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path)
}
}
4、优化Context将动态参数存储以及获取
package ebb
import (
"net/http"
"fmt"
"encoding/json"
)
type H map[string]interface{}
type Context struct{
//write and request
Writer http.ResponseWriter
Request *http.Request
//request info
Method string
Path string
Params map[string]interface{}
}
func newContext(w http.ResponseWriter,r *http.Request) *Context{
context := &Context{
Writer:w,
Request:r,
Path: r.URL.Path,
Method: r.Method,
Params: make(map[string]interface{}),
}
return context
}
func (c *Context) Param(key string) string{
value, _ := c.Params[key].(string)
return value
}
func (c *Context) PostForm(key string) string {
return c.Request.FormValue(key)
}
func (c *Context) Query(key string) string {
return c.Request.URL.Query().Get(key)
}
func (c *Context) Status(code int) {
c.Writer.WriteHeader(code)
}
func (c *Context) SetHeader(key string, value string) {
if value == "" {
c.Writer.Header().Del(key)
return
}
c.Writer.Header().Set(key, value)
}
func (c *Context) GetHeader(key string) string{
return c.Request.Header.Get(key)
}
func (c *Context) Write(data []byte){
c.Writer.Write(data)
}
func (c *Context) String(code int,message string,v ...interface{}){
c.SetHeader("Content-Type", "text/plain")
c.Status(code)
c.Write([]byte(fmt.Sprintf(message, v...)))
}
func (c *Context) JSON(code int, obj interface{}) {
c.SetHeader("Content-Type", "application/json")
c.Status(code)
data,err:= json.Marshal(obj)
if err!=nil {
http.Error(c.Writer, err.Error(), 500)
}
c.Write(data)
}
func (c *Context) HTML(code int,html string){
c.SetHeader("Content-Type", "text/html")
c.Status(code)
c.Write([]byte(html))
}
5、编写单元测试
package ebb
import (
"net/http/httptest"
"testing"
)
func PerformRequest(mothod string,url string ,body io.Reader,e *Engine) (w *httptest.ResponseRecorder){
w = httptest.NewRecorder()
r := httptest.NewRequest(mothod, url, body)
r.Header.Set("Content-Type", "application/json")
e.ServeHTTP(w,r)
return w
}
package ebb
import (
"testing"
"bytes"
"encoding/json"
"github.com/stretchr/testify/assert"
)
func TestGetRoute(t *testing.T) {
r := newRouter()
r.addRoute("GET", "/", nil)
r.addRoute("GET", "/hello/:name", nil)
r.addRoute("GET", "/hello/b/c", nil)
n, params := r.getRoute("GET", "/index/baibai")
if assert.NotNil(t, n,"404 not found ") {
assert.Equal(t,n.pattern,"/hello/:name","should match /hello/:name")
assert.Equal(t,params["name"],"baibai","name should be equel to 'baibai'")
}
}
func TestParsePattern(t *testing.T) {
assert.Equal(t,parsePattern("/p/:name"),[]string{"p",":name"},"not parsePattern :name")
assert.Equal(t,parsePattern("/p/*"),[]string{"p","*"},"not parsePattern *")
assert.Equal(t,parsePattern("/p/*name/*"),[]string{"p","*name"},"parsePattern not truncation")
}
func TestRouter(t *testing.T){
r := New()
r.POST("/login/*filepath", func(c *Context) {
c.JSON(200, H{
"name": c.PostForm("name"),
})
})
param := `{"name":"56789","state":3}`
w := PerformRequest("POST","/login/123213?name=1233",bytes.NewBufferString(param),r)
s := struct{
Name string `json:"name"`
}{}
json.Unmarshal([]byte(w.Body.String()),&s)
assert.Equal(t,s.Name,"1233","PostForm error")
}
6、在本章节我们主要优化了路由,并支持动态参数
接下来我们会陆续实现路由的分组、中间件等等
路由分组
简单说下本章的重点
- 1、优化路由支持分组
- 2、编写单元测试、执行案例测试
- 3、代码地址 https://github.com/18211167516/go-Ebb/tree/master/day4-routerGroup
1、路由分组的概念
如果没有路由分组,我们需要针对每一个路由进行控制。但是真实的业务场景中,往往某一组路由需要相似的处理,大部分的分组是由前缀来区分的、并且可以支持分组的嵌套
2、定义路由分组
package ebb
type RouterGroup struct {
prefix string
middlewares []HandlerFunc // support middleware
parent *RouterGroup // support nesting
engine *Engine // all groups share a Engine instance
}
func (group *RouterGroup) Group(prefix string) *RouterGroup {
engine := group.engine
newGroup := &RouterGroup{
prefix: group.prefix + prefix,//支持分组嵌套
parent: group,
engine: engine,
}
engine.groups = append(engine.groups, newGroup)
return newGroup
}
func (group *RouterGroup) addRoute(method string, comp string, handler HandlerFunc) {
pattern := group.prefix + comp
group.engine.router.addRoute(method, pattern, handler)
}
// GET defines the method to add GET request
func (group *RouterGroup) GET(pattern string, handler HandlerFunc) {
group.addRoute("GET", pattern, handler)
}
// POST defines the method to add POST request
func (group *RouterGroup) POST(pattern string, handler HandlerFunc) {
group.addRoute("POST", pattern, handler)
}
2、优化engine结构
//核心结构体
type Engine struct{
*RouterGroup //v4新增 顶级路由组
router *router
groups []*RouterGroup // v4新增
}
//实例化结构体
func New() *Engine{
engine := &Engine{
router : newRouter(),
}
engine.RouterGroup = &RouterGroup{engine: engine}
engine.groups = []*RouterGroup{engine.RouterGroup}
return engine
}
3、单元测试
package ebb
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRouteGroup(t *testing.T) {
r := New()
v2 := r.Group("/v2")
{
v2.GET("/hello/:name", func(c *Context) {
// expect /hello/ebbktutu
c.String(200, "hello %s, you're at %s\n", c.Param("name"), c.Path)
})
v2.POST("/login", func(c *Context) {
c.JSON(200, H{
"username": c.PostForm("username"),
"password": c.PostForm("password"),
})
})
}
n, params := r.router.getRoute("GET", "v2/hello/baibai")
if assert.NotNil(t, n,"404 not found ") {
assert.Equal(t,n.pattern,"/v2/hello/:name","should match /hello/:name")
assert.Equal(t,params["name"],"baibai","name should be equel to 'baibai'")
}
}
4、分组使用案例
package main
import (
"ebb"
)
func main(){
r := ebb.New()
r.GET("/index", func(c *ebb.Context) {
c.HTML(200, "<h1>Index Page</h1>")
})
v1 := r.Group("/v1")
{
v1.GET("/", func(c *ebb.Context) {
c.HTML(200, "<h1>Hello ebb</h1>")
})
v1.GET("/hello", func(c *ebb.Context) {
c.String(200, "hello %s, you're at %s\n", c.Query("name"), c.Path)
})
}
v2 := r.Group("/v2")
{
v2.GET("/hello/:name", func(c *ebb.Context) {
// expect /hello/ebbktutu
c.String(200, "hello %s, you're at %s\n", c.Param("name"), c.Path)
})
v2.POST("/login", func(c *ebb.Context) {
c.JSON(200, ebb.H{
"username": c.PostForm("username"),
"password": c.PostForm("password"),
})
})
}
r.Run(":8080")
}
5、本章节我们实现了路由分组(路由加前缀)
接下来到了重头戏,中间件的实现以及具体案例
路由支持中间件
简单说下本章的重点
- 1、路由支持中间件
- 2、新增日志中间件
- 4、新增异常错误中间件
- 3、代码地址 https://github.com/18211167516/go-Ebb/tree/master/day5-middleware
1、重点
- 中间件的目的是为了让web框架灵活支持用户某些自定义行为,对外开放一个接口
- 核心的点在什么时候插入、以及中间件传入的支持参数
2、思路
- 1、作用于每一条路由规则
- 2、作用于Context、依次执行该路由说匹配的中间件
- 3、中间件也和控制器层一样传入Context、接入点是框架接收到请求初始化Context对象后,允许用户使用自己定义的中间件做一些额外的处理、另外通过调用(*Context).Next()函数,中间件可等待用户自己定义的 Handler处理结束后,再做一些额外的操作
3、Context支持
package ebb
import (
"net/http"
"fmt"
"encoding/json"
)
type Context struct{
//write and request
Writer http.ResponseWriter
Request *http.Request
//request info
Method string
Path string
Params map[string]interface{}
HttpCode int
//middleware 新增
handlers HandlersChain
index int
}
func newContext(w http.ResponseWriter,r *http.Request) *Context{
context := &Context{
Writer:w,
Request:r,
Path: r.URL.Path,
Method: r.Method,
Params: make(map[string]interface{}),
index: -1,
}
return context
}
//本次新增
func (c *Context) Next() {
c.index++
for c.index < len(c.handlers) {
c.handlers[c.index](c)
c.index++
}
}
4、routerGroup支持
package ebb
type HandlersChain []HandlerFunc //新增
type RouterGroup struct {
prefix string
middlewares HandlersChain // support middleware
parent *RouterGroup // support nesting
engine *Engine // all groups share a Engine instance
}
func (group *RouterGroup) Group(prefix string) *RouterGroup {
engine := group.engine
newGroup := &RouterGroup{
prefix: group.prefix + prefix,//支持分组嵌套
parent: group,
engine: engine,
}
engine.groups = append(engine.groups, newGroup)
return newGroup
}
//添加中间件 v5新增
func (group *RouterGroup) Use(middlewares ...HandlerFunc){
group.middlewares = append(group.middlewares, middlewares...)
}
//合并handlers
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.middlewares) + len(handlers)
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.middlewares)
copy(mergedHandlers[len(group.middlewares):], handlers)
return mergedHandlers
}
func (group *RouterGroup) addRoute(method string, comp string, handler ...HandlerFunc) {
pattern := group.prefix + comp
//添加的路由的时候将中间件合并到 路由handles
handlers := group.combineHandlers(handler)
group.engine.router.addRoute(method, pattern, handlers)
}
// GET defines the method to add GET request
func (group *RouterGroup) GET(pattern string, handler ...HandlerFunc) {
group.addRoute("GET", pattern, handler...)
}
// POST defines the method to add POST request
func (group *RouterGroup) POST(pattern string, handler ...HandlerFunc) {
group.addRoute("POST", pattern, handler...)
}
5、路由支持
package ebb
import (
"strings"
)
type router struct{
roots map[string]*node
handlers map[string]HandlersChain //新增
}
func newRouter() *router{
return &router{
roots:make(map[string]*node),
handlers:make(map[string]HandlersChain),
}
}
func (r *router) addRoute(method string,pattern string,handlers HandlersChain){
parts := parsePattern(pattern)
key := method + "-" + pattern
_, ok := r.roots[method]
if !ok {
r.roots[method] = &node{}
}
r.roots[method].insert(pattern, parts, 0)
r.handlers[key] = append(r.handlers[key],handlers...) //合并中间件和handlers
}
6、ebb支持
package ebb
import (
"net/http"
)
//定义框架请求处理方法
type HandlerFunc func(*Context)
//核心结构体
type Engine struct{
*RouterGroup //v4新增 顶级路由组
router *router
groups []*RouterGroup // v4新增
}
//实例化结构体
func New() *Engine{
engine := &Engine{
router : newRouter(),
}
engine.RouterGroup = &RouterGroup{engine: engine}
engine.groups = []*RouterGroup{engine.RouterGroup}
return engine
}
//新增
func Default() *Engine{
engine := New()
engine.Use(Logger(),Recovery())
return engine
}
//启动服务
func (engine *Engine) Run(addr string) (err error){
return http.ListenAndServe(addr,engine)
}
//engine 实现ServeHTTP接口(所有的请求都会走到这)
//查找是否路由映射表存在,如果存在则调用,否则返回404
func (engine *Engine) ServeHTTP(w http.ResponseWriter,req *http.Request){
c := newContext(w, req)
engine.handleHTTPRequest(c)
}
//v2 新增
func (engine *Engine) handleHTTPRequest(c *Context){
node,params := engine.router.getRoute(c.Method,c.Path)
if node != nil {
c.Params = params
key := c.Method + "-" + node.pattern
//赋值
c.handlers = engine.router.handlers[key]
}else{
c.handlers = append(c.handlers, func(c *Context) {
c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path)
})
}
//真正调度执行中间件以及具体handler
c.Next()
}
7、新增logger中间件
package ebb
import (
"time"
"log"
)
func Logger() HandlerFunc {
return func(c *Context) {
// Start timer
t := time.Now()
// Process request
c.Next()
// Calculate resolution time
log.Printf("[%d] %s in %v", c.HttpCode, c.Request.RequestURI, time.Since(t))
}
}
8、新增错误异常中间件
package ebb
import (
"fmt"
"log"
"net/http"
"runtime"
"strings"
)
// print stack trace for debug
func trace(message string) string {
var pcs [32]uintptr
n := runtime.Callers(3, pcs[:]) // skip first 3 caller
var str strings.Builder
str.WriteString(message + "\nTraceback:")
for _, pc := range pcs[:n] {
fn := runtime.FuncForPC(pc)
file, line := fn.FileLine(pc)
str.WriteString(fmt.Sprintf("\n\t%s:%d", file, line))
}
return str.String()
}
func Recovery() HandlerFunc {
return func(c *Context) {
defer func() {
if err := recover(); err != nil {
message := fmt.Sprintf("%s", err)
log.Printf("%s\n\n", trace(message))
c.JSON(http.StatusInternalServerError, "Internal Server Error")
}
}()
c.Next()
}
}
9、使用案例
package main
import (
"ebb"
)
func main(){
r := ebb.Default()
r.GET("/panic",func(c *ebb.Context) {
panic("err")
})
r.POST("/login/*name",func(c *ebb.Context) {
c.JSON(200, ebb.H{
"name": c.Param("name"),
})
})
r.Run(":8080")
}
到目前为止,框架的基本架构已确立,后续就是一些补充性的功能,以及优化点 看起来是不是和gin框架非常类似 下一章节我们实现对go模板的使用封装
模板处理
简单说下本章的重点
- 1、静态服务器支持(映射静态文件路由)
- 2、模板渲染
- 3、代码地址 https://github.com/18211167516/go-Ebb/tree/master/day6-template
1、支持静态服务器
func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc {
absolutePath := joinPaths(group.prefix, relativePath)
fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
return func(c *Context) {
file := c.Param("filepath")
// Check if file exists and/or if we have permission to access it
f, err := fs.Open(file);
defer f.Close()
if err != nil {
c.Status(http.StatusNotFound)
return
}
fileServer.ServeHTTP(c.Writer, c.Request)
}
}
// serve static files
func (group *RouterGroup) Static(relativePath string, root string) {
handler := group.createStaticHandler(relativePath, http.Dir(root))
urlPattern := joinPaths(relativePath, "/*filepath")
// Register GET handlers
group.GET(urlPattern, handler)
}
func joinPaths(absolutePath, relativePath string) string {
if relativePath == "" {
return absolutePath
}
finalPath := path.Join(absolutePath, relativePath)
return finalPath
}
2、单元测试
func TestStaticFile(t *testing.T){
r := Default()
r.Static("/assets", "./")
w := PerformRequest("GET","/assets/go.mod",bytes.NewBufferString(""),r)
fmt.Printf(w.Body.String())
}
3、模板渲染
type Engine struct{
*RouterGroup //v4新增 顶级路由组
router *router
groups []*RouterGroup // v4新增
//html/template
HTMLRender *template.Template
HTMLdelimsLeft string
HTMLdelimsRight string
funcMap template.FuncMap
}
func (engine *Engine) SetFuncMap(funcName string,f interface{}) {
funcMap := template.FuncMap{
funcName: f,
}
engine.funcMap = funcMap
}
func (engine *Engine) LoadHTMLGlob(pattern string) {
engine.HTMLdelimsLeft = "{"
engine.HTMLdelimsRight= "}"
engine.HTMLRender = template.Must(template.New("").Delims(engine.HTMLdelimsLeft,engine.HTMLdelimsRight).Funcs(engine.funcMap).ParseGlob(pattern))
}
func (engine *Engine) ServeHTTP(w http.ResponseWriter,req *http.Request){
c := newContext(w, req)
c.engine = engine //新增
engine.handleHTTPRequest(c)
}
4、改造Context HTML方法
func (c *Context) HTML(code int,TmpName string,data interface{}){
c.SetHeader("Content-Type", "text/html")
c.Status(code)
if err := c.engine.HTMLRender.ExecuteTemplate(c.Writer, TmpName, data); err != nil {
c.JSON(500, err.Error())
}
}
5、demo
package main
import (
"ebb"
"time"
"fmt"
)
type student struct {
Name string
Age int8
}
func FormatDate(t time.Time) string {
year, month, day := t.Date()
Hour,Minute,Second := t.Hour(),t.Minute(),t.Second()
return fmt.Sprintf("%d-%02d-%02d %d:%d:%d", year, month, day,Hour,Minute,Second)
}
func main(){
r := ebb.Default()
r.GET("/panic",func(c *ebb.Context) {
panic("err")
})
r.POST("/login/*name",func(c *ebb.Context) {
c.JSON(200, ebb.H{
"name": c.Param("name"),
})
})
r.Static("/assets", "./static")
r.SetFuncMap("formatDate",FormatDate)
r.LoadHTMLGlob("templates/*")
stu1 := &student{Name: "Geektutu", Age: 20}
stu2 := &student{Name: "Jack", Age: 22}
r.GET("/students", func(c *ebb.Context) {
c.HTML(200, "students.tmpl", ebb.H{
"title": "ebb students",
"now": time.Now(),
"stuArr": [2]*student{stu1, stu2},
})
})
r.Run(":8080")
}
<html>
<body>
<p>hello, {.title}</p>
<p>now : {.now |formatDate}</p>
{range $index, $ele := .stuArr }
<p>{ $index }: { $ele.Name } is { $ele.Age } years old</p>
{ end }
</body>
</html>
自此带着大家搭建gin已经完结,希望大家有所收获,不白看,多动手
来源:https://studygolang.com/user/18211167516/articles
评论