三步实现一个golang web framework

本文通过三个步骤,从零开始构建一个简易的Go语言Web框架,涵盖URL路由、中间件支持及基本功能实现,适合初学者理解和实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

一般来说,对于入门某一门语言的web开发,都会从框架入手,但是,当你的使用场景越来越复杂的时候,框架本身会制约你的实现,这个时候,你需要自己实现一个框架才能满足你的需要。框架并不神秘,跟着笔者三步实现一个最基本的golang框架,然后就可以case-by-case完善专属于你的框架。所有代码都在github 3StepWebFramerork.

首先,这里说的web framework是指一个非常精简的框架,主要包括:

url路由以及url传参

路由middeleware中间件支持,一般web业务都需要中间件的支持,以便集中做auth/log/recovery(recovery是go一大特色)

其它非核心组件可以自行添加:

  • 更丰富的上下文

  • 序列化组件(json/yaml/pb)

  • ORM

针对以上要求,各个组件的解决方法如下

router: httprouter,比go原生的路由性能要高一些,详见对应benckmark。ps:刚开始用gin的时候gin的star才几kstar,现在已经二十几k了,时间过得好快。gin号称最快的框架之一,就是因为用了这个路由。

middleware: 自定义

序列化支持:以json为例,使用官方json包实现

下面跟着笔者step by step

Step1: 熟悉router

路由有两个基本的功能

  1. 根据url,把请求传到相应的处理函数

  2. 把url的某一段,作为一个参数获取,如下面demo的name参数


package main



import (

	"fmt"

	"github.com/julienschmidt/httprouter"

	"log"

	"net/http"

)



func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {

	fmt.Fprint(w, "Welcome!\n")

}



func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {

	fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))

}



func main() {

	router := httprouter.New()

	router.GET("/", Index)

	router.GET("/hello/:name", Hello)



	log.Fatal(http.ListenAndServe(":8082", router))

}



用curl测试一下输出,output:


> curl localhost:8082/

Welcome!

> curl localhost:8082/hello/half

hello, half!

Step2:middleware part1

第二步,要实现某一些业务(log/recovery)的统一处理,这个时候就要插入一些函数

  1. before业务处理

  2. 实际业务处理

  3. after业务处理

下面代码实现一个基本功能:

在所有的api实际处理函数之前, 打印一下日志,往header写一个Before:hello

在所有的api实际处理函数之前, 打印一下日志,往header写一个After:world(实际上这样是做是不对的)

先贴代码:


package main



import (

	"fmt"

	"github.com/julienschmidt/httprouter"

	"io"

	"log"

	"net/http"

)



func Index(w http.ResponseWriter, r *http.Request) {

	// get url param by GetParam

	work := GetParam(r, "work")

	fmt.Println("work:", work)



	fmt.Fprint(w, "Welcome!\n")

}



type MiddlewareFunc func(w http.ResponseWriter, req *http.Request)



type simpleRouter struct {

	BeforeMiddleware MiddlewareFunc

	AfterMiddleware  MiddlewareFunc

	r                *httprouter.Router

}



func NewSimpleRouter() (s *simpleRouter) {

	s = &simpleRouter{

		r: httprouter.New(),

	}

	return s

}



func (s *simpleRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {

	if s.BeforeMiddleware != nil {

		s.BeforeMiddleware(w, req)

	}



	s.r.ServeHTTP(w, req)



	if s.AfterMiddleware != nil {

		s.AfterMiddleware(w, req)

	}

}



// wrapper for httprouter GET

func (s *simpleRouter) GET(path string, handle http.HandlerFunc) {

	s.r.GET(path, wrap(handle))

}



func wrap(handler http.HandlerFunc) httprouter.Handle {

	return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {

		SetParam(r, params)

		handler(w, r)

	}

}



type contextReadCloser struct {

	io.ReadCloser

	params httprouter.Params

}



func SetParam(req *http.Request, params httprouter.Params) {

	crc := getContextReadCloser(req)

	crc.params = params

}



func GetParam(req *http.Request, key string) string {

	crc := getContextReadCloser(req)

	for _, v := range crc.params {

		if v.Key == key {

			return v.Value

		}

	}

	return ""

}



// add a simple context

func getContextReadCloser(req *http.Request) *contextReadCloser {

	crc, ok := req.Body.(*contextReadCloser)

	if !ok {

		crc = &contextReadCloser{

			ReadCloser: req.Body,

		}

		req.Body = crc

	}

	return crc

}



func main() {

	router := NewSimpleRouter()

	router.BeforeMiddleware = func(w http.ResponseWriter, req *http.Request) {

		fmt.Println("handle before")

		w.Header().Set("Before", "hello")

	}

	router.AfterMiddleware = func(w http.ResponseWriter, req *http.Request) {

		fmt.Println("handle after")

		w.Header().Set("After", "world") // 注意,因为实际处理函数在处理的时候一般会先写resp code, 这里是不会生效的

	}

	router.GET("/step2/:work", Index)



	log.Fatal(http.ListenAndServe(":8082", router))

}



运行代码

打开一个新的terminal:


curl -i localhost:8082/step2/test

HTTP/1.1 200 OK

Before: hello

Date: Sun, 14 Apr 2019 01:57:39 GMT

Content-Length: 9

Content-Type: text/plain; charset=utf-8



Welcome!

在运行代码的terminal输出:


handle before

work: test

handle after

已经可以实现before/after的逻辑

这里有一个结构体要说明一下,

由于httprouter的api接口形式是:func(w http.ResponseWriter, r *http.Request, params httprouter.Params)

跟golanghttp包的多了一个httprouter.Params 字段,那么为了把这个信息写共享,就封装了一个简单的上下文

Request是一个io.ReadCloser借口,所以要封装一个简单的上下文,


type contextReadCloser struct {

	io.ReadCloser

	params httprouter.Params

}

Step3

由于代码较多,就不贴了,

说一下几个比较重要的结构:

对于路由:


type MiddlewareFunc func(http.Handler) http.Handler

这里定义了一个中间件的处理函数,输出与输出的数据类型都是http.Handler,为链式调用服务

MiddleWare的实际处理逻辑在这里,最先注册进来的middleware[0]会在最后被调用,当你要做recoveryMiddleware的时候一定要注意先后顺序


func (r *QRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {

	handler := http.Handler(r.router)

	for i := len(r.middleware) - 1; i >= 0; i-- {

		handler = r.middleware[i](handler)

	}

	handler.ServeHTTP(w, req)

}

测试curl:

`

curl -i localhost/test/cat/1

curl -i localhost/

`

对于log_middleware

日志中间件的demo,为了实现获取Request body和Response body, 定义了一个数据结构


type bufferedWriter struct {

	http.ResponseWriter

	out        *bufio.Writer

	RespBuffer bytes.Buffer

}

但是对于实际业务处理,很多请求是不需要复制body的,非常损耗性能,这里只是实现了最简单的demo,可以按照实际情况修改。

回顾

这里只是实现了基本的路由个MiddleaWare,只包装了一个GET方法,其他POST/PUT/DELETE可以自行实现,这是一个最基本的框架,大家可以根据自己的实际需要改写

如果觉得有用,请给个star

https://github.com/miaomiao3/3StepWebFramerork

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值