golang 实现轻量web框架
经常看到很多同学在打算使用go做开发的时候会问用什么http框架比较好。其实go的 http package 非常强大,对于一般的 http rest api 开发,完全可以不用框架就可以实现想要的功能。
我们开始尝试用不到100行代码定制出基本的功能框架。
首先思考下基本功能需求:
- 输出访问日子,需要知道:
- Method
- status code
- url
- 响应消耗时间
- response content-length
- 错误捕获,当http请求出现异常时捕获错误,返回异常信息
以上是几个基本需求,未来可能还会有很多,所以应该尝试设计成中间件的形式来应对未来需求的变化,让功能根据需求增减。
我们可以把 http 框架中间件的设计比做洋葱,一层一层的,到最中间就进入业务层,再一层一层的出来。
把流程画出来是这个样子的:
Http:
| LogRequst
| ErrCatch
| Cookie
| Handler
| cookie
| ErrCatch
V LogRequst
调用过程类似于每个中间件逐层包裹,这样的过程很符合函数栈层层调用的过程。
注意:因为这个小框架最终是要被 http.Serve 或 http.ListenAndServe 调用的,所以需要实现 http.Handler 接口,接收到的参数为 http.ResponseWriter 和 *http.Request
好啦!目标确定了下面我们开始想办法实现它
首先需要定义一个 struct 结构,其中需要保存中间件,和最终要执行的 http.Handler
// MiddlewareFunc filter type
type MiddlewareFunc func(ResponseWriteReader, *http.Request, func())
// MiddlewareServe server struct
type MiddlewareServe struct {
middlewares []MiddlewareFunc
Handler http.Handler
}
这里有个问题,因为默认接收到的参数 http.ResponseWriter 接口是一个只能写入不能读取的接口,但我们又需要能读取 status code 和 content-length 。这个时候接口设计的神奇之处就体现出来啦,重新定义一个接口且包涵 http.ResponseWriter ,加入读取 status code 和 content-length 的功能
// ResponseWriteReader for middleware
type ResponseWriteReader interface {
StatusCode() int
ContentLength() int
http.ResponseWriter
}
定义一个 warp struct 实现 ResponseWriteReader 接口
// WrapResponseWriter implement ResponseWriteReader interface
type WrapResponseWriter struct {
status int
length int
http.ResponseWriter
}
// NewWrapResponseWriter create wrapResponseWriter
func NewWrapResponseWriter(w http.ResponseWriter) *WrapResponseWriter {
wr := new(WrapResponseWriter)
wr.ResponseWriter = w
wr.status = 200
return wr
}
// WriteHeader write status code
func (p *WrapResponseWriter) WriteHeader(status int) {
p.status = status
p.ResponseWriter.WriteHeader(status)
}
func (p *WrapResponseWriter) Write(b []byte) (int, error) {
n, err := p.ResponseWriter.Write(b)
p.length += n
return n, err
}
// StatusCode return status code
func (p *WrapResponseWriter) StatusCode() int {
return p.status
}
// ContentLength return content length
func (p *WrapResponseWriter) ContentLength() int {
return p.length
}
接下来,MiddlewareServe 本身需要符合 http.Handler, 所以我们需要定义 ServeHTTP。
// ServeHTTP for http.Handler interface
func (p *MiddlewareServe) ServeHTTP(w http.ResponseWriter, r *http.Request) {
i := 0
// warp http.ResponseWriter 可以让中间件读取到 status code
wr := NewWrapResponseWriter(w)
var next func() // next 函数指针
next = func() {
if i < len(p.middlewares) {
i++
p.middlewares[i-1](wr, r, next)
} else if p.Handler != nil {
p.Handler.ServeHTTP(wr, r)
}
}
next()
}
再加入一个插入中间件的方法
// Use push MiddlewareFunc
func (p *MiddlewareServe) Use(funcs ...MiddlewareFunc) { // 可以一次插入一个或多个
for _, f := range funcs {
p.middlewares = append(p.middlewares, f)
}
}
到这里,一个支持中间件的小框架就定义好了,加上注释一共也不到80行代码
下面开始实现几个中间件测试一下。
// LogRequest print a request status
func LogRequest(w ResponseWriteReader, r *http.Request, next func()) {
t := time.Now()
next()
log.Printf("%v %v %v use time %v content-length %v",
r.Method,
w.StatusCode(),
r.URL.String(),
time.Now().Sub(t).String(),
w.ContentLength())
}
这个函数会打印出 http request Method, status code, url, 处理请求消耗时间, response content-length
测试一下
package main
import (
"fmt"
"net/http"
)
func helloHandle(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, " hello ! this's a http request \n method %v \n request url is %v ", r.Method, r.URL.String())
}
func main() {
// create middleware server
s := new(MiddlewareServe)
s.Handler = http.HandlerFunc(helloHandle)
s.Use(LogRequest)
// start server
fmt.Println(http.ListenAndServe(":3000", s))
}
运行
$ go run *.go
$ curl 127.0.0.1:3000
> hello ! this's a http request
> method GET
> request url is
# 输出日志
> 2016/04/24 02:28:12 GET 200 / use time 61.717µs content-length 64
$ curl 127.0.0.1:3000/hello/go
> hello ! this's a http request
> method GET
> request url is /hello/go
# 输出日志
> 2016/04/24 02:31:36 GET 200 /hello/go use time 28.207µs content-length 72
或者用浏览器请求地址查看效果
再加一个错误捕获中间件:
// ErrCatch catch and recover
func ErrCatch(w ResponseWriteReader, r *http.Request, next func()) {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
debug.PrintStack()
w.WriteHeader(http.StatusInternalServerError) // 500
}
}()
next()
}
测试
package main
import (
"fmt"
"net/http"
)
func helloHandle(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, " hello ! this's a http request \n method %v \n request url is %v \n", r.Method, r.URL.String())
}
func panicHandle(w http.ResponseWriter, r *http.Request) {
panic("help me !")
}
func main() {
// create middleware server
s := new(MiddlewareServe)
route := http.NewServeMux()
route.Handle("/hello", http.HandlerFunc(helloHandle))
route.Handle("/panic", http.HandlerFunc(panicHandle))
s.Handler = route
s.Use(LogRequest, ErrCatch)
// start server
fmt.Println(http.ListenAndServe(":3000", s))
}
运行
$ curl -i 127.0.0.1:3000/panic
> HTTP/1.1 500 Internal Server Error
> Date: Sat, 23 Apr 2016 18:51:12 GMT
> Content-Length: 0
> Content-Type: text/plain; charset=utf-8
# log
> help me !
> ... # debug.Stack
> 2016/04/24 02:51:12 GET 500 /panic use time 142.885µs content-length 0
$ curl -i 127.0.0.1:3000/hello/go
> HTTP/1.1 404 Not Found
> Content-Type: text/plain; charset=utf-8
> X-Content-Type-Options: nosniff
> Date: Sat, 23 Apr 2016 18:55:30 GMT
> Content-Length: 19
>
> 404 page not found
# log
2016/04/24 02:55:30 GET 404 /hello/go use time 41.14µs content-length 19
到这里,一个灵活的核心就实现出来了。我尽量只使用标准包,没有引入第三方包,希望这样可能帮助刚学习go的同学更加了解 net.http package,当然在真实使用中可以根据需要引入其他的符合 http.Handler 接口的 router 替代 ServeMux
有些同学可能已经看出来了,既然 MiddlewareServe 实现了 http.Handler 那就可以挂到 router 中。没错,这样就相当于可以为某个路径下单独定制 MiddlewareServe 了,比如某些接口需要权限校验,我会在下一篇中来尝试写权限校验。
涉及模版的调用我就不写了,因为这个确实是脚本语言更佳适合
全部代码的github地址: https://github.com/ifanfan/golearn/tree/master/websrv
自己写的第一篇博客希望以后可以坚持写下去
golang 实现轻量web框架的更多相关文章
- Python轻量Web框架Flask使用
http://blog.csdn.net/jacman/article/details/49098819 目录(?)[+] Flask安装 Python开发工具EclipsePyDev准备 Flask ...
- 初识python轻量web框架flask
1.使用pip安装Python包 大多数Python包都使用pip实用工具安装,使用pyvenv创建的虚拟环境会自动安装pip. 1.使用pip安装Flask(其它Python包同理) pip ins ...
- go语言,golang学习笔记2 web框架选择
go语言,golang学习笔记2 web框架选择 用什么go web框架比较好呢?能不能推荐个中文资料多的web框架呢? beego框架用的人最多,中文资料最多 首页 - beego: 简约 & ...
- 动手打造轻量web服务器(二)路由
tomcat启动慢?自己动手打造轻量web服务器(一) 上篇讲了怎么做一个最简单的web服务器,这篇就是在上篇加上URL路由功能(什么是路由?) 首先,根据http获得请求行 val scanner ...
- Cardinal:一个用于移动项目开发的轻量 CSS 框架
Cardinal 是一个适用于移动项目的 CSS 框架,包含很多有用的默认样式.矢量字体.可重用的模块以及一个简单的响应式模块系统.Cardinal 提供了一种在多种移动设备上实现可伸缩的字体和布局的 ...
- Android轻量缓存框架--ASimpleCache
[转] 大神真面目 稀土掘金,这是一个针对技术开发者的一个应用,你可以在掘金上获取最新最优质的技术干货,不仅仅是Android知识.前端.后端以至于产品和设计都有涉猎,想成为全栈工程师的朋友不要错过! ...
- golang基础学习及web框架
golang的web框架 web框架百花齐放:对比 Go Web 编程 Go Web Examples Golang 适合做 Web 开发吗? beego beego简介 go-restful gol ...
- OSCHina技术导向:Java轻量web开发框架——JFinal
JFinal 是基于 Java 语言的极速 WEB + ORM 框架,其核心设计目标是开发迅速.代码量少.学习简单.功能强大.轻量级.易扩展.Restful.在拥有Java语言所有优势的同时再拥有ru ...
- Spring Boot & Cloud 轻量替代框架 Solon 1.3.37 发布
Solon 是一个微型的Java开发框架.强调,克制 + 简洁 + 开放的原则:力求,更小.更快.更自由的体验.支持:RPC.REST API.MVC.Micro service.WebSocket. ...
随机推荐
- java基础32 List集合下的ArrayList集合
单例集合体系: ---------| collection 单例集合的根接口--------------| List 如果实现了list接口的集合类,具备的特点:有序,可重复 注:集合 ...
- 最近公共祖先(LCA)模板
以下转自:https://www.cnblogs.com/JVxie/p/4854719.html 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖 ...
- Unix IPC之Posix信号量实现生产者消费者
采用多生产者,多消费者模型. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /** * 生产者 */ P(nempty); P(mutex); // 写入一个 ...
- Linux学习笔记:644、755、777权限详解
一.问题 1.在Linux或者Android系统下用命令ll或者ls -la的时候会看到前面-rw-rw-r--一串字符,不知道代表什么? 2.新建vi一个文件之后,经常需要chmod 755 fil ...
- java通过POI和easypoi实现Excel的导出
前言 在工作经常会遇到excel导出报表的功能,自己也做过一些,然后在项目里看到同事封装的一个excel导出工具类,着实不错,拿来分享一下.然后,又在网上看到一个使用easypoi实现cxcel导出的 ...
- 如何解决vuex因浏览器刷新数据消失,保持数据持久化问题?
vuex的一个全局状态管理的插件,但是在浏览器刷新的时候,内存中的state会释放.通常的解决办法就是用本地存储的方式保存数据,然后再vuex初始化的时候再赋值给state,此过程有点麻烦.因此可以使 ...
- 一步一步学习IdentityServer3 (8)
IdentityServer3结合Hangfire及Cookies中间件实现授权 Idr3数据库Token过期管理 GlobalConfiguration.Configuration.UseSqlSe ...
- hdu 1398 整数划分变形 (母函数)
有1,4,9,16,25.....2^17这么多面值的硬币,问任意给定一个不大于300的正整数面额,用这些硬币来组成此面额总共有多少种组合种数 比如10全14 + 6个 14+4+1+19+1 求(1 ...
- html中<b>标签和<Strong>标签的区别
关于html标签中b和strong两个的区别,我也是今早上才注意的,以前都是混着用的,早上看书的时候才注意到这两个标签的区别. 用在网页上,默认情况下它们起的均是加粗字体的作用,二者所不同的是,< ...
- C#实现盛大盛付通充值卡状态查询
今天有这样一需求,要求能够查询盛付通卡的状态,官网如下 http://www.801335.com/status/index.htm 刚一打开网址,发现两个输入框加一个验证码,心中一喜不是小 cas ...