原文地址

   再web开发的背景下,“中间件”通常意思是“包装原始应用并添加一些额外的功能的应用的一部分”。这个概念似乎总是不被人理解,但是我认为中间件非常棒。
   首先,一个好的中间件有一个责任就是可插拔并且自足。这就意味着你可以在接口级别嵌入你的中间件他就能直接运行。它不会影响你编码方式,不是框架,仅仅是你请求处理里面的一层而已。完全没必要重写你的代码,如果你想使用中间件的一个功能,你就帮他插入到那里,如果不想使用了,就可以直接移除。
   纵观Go语言,中间件是非常普遍的,即使在标准库中。虽然开始的时候不会那么明显,在标准库net/http中的函数StripText或者TimeoutHandler就是我们要定义和的中间件的样子,处理请求和相应的时候他们包装你的handler,并处理一些额外的步骤。
   我最近写的Go包nosurf同样也是个中间件。我特意将他从头开始设计。在大多数情况下,你不需要在应用层担心CSRF攻击,nosurf像其他的中间件一样可以自足,并且和net/http的接口无缝衔接。
   同样你还可以使用中间件做:

  • 隐藏长度防止缓冲攻击
  • 速度限制
  • 屏蔽爬虫
  • 提供调试信息
  • 添加HSTS,X-Frame-Options头
  • 从错误中恢复
  • 等等
编写一个简单的中间件

   我们的第一个例子是写一个只允许一个域名下的用户访问的中间件,通过HTTP的HOSTheader实现。这样的中间件可以防止主机欺骗攻击

类型的机构

   首先我们定义一个结构体,叫做SingleHost

type SingleHost struct {
handler http.Handler
allowedHost string
}

  它只包含两个field。

  • 如果是一个可用的Host,那么我们会调用嵌入的handler。

  • allowedHost 就是允许的Host。
       因为我们将其首字母小写,因此他们只对本包可见。我们需要给它定义已给构造函数。
func NewSingleHost(handler http.Handler, allowedHost string) *SingleHost {
return &SingleHost{handler: handler, allowedHost: allowedHost}
}
请求处理

   现在需要实现真正的逻辑功能了。想要实现http.Handler,我们只需要实现他的一个方法。

type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

实现如下:

func (s *SingleHost) ServeHTTP(w http.ResponseWriter, r *http.Request) {
host := r.Host
if host == s.allowedHost {
s.handler.ServeHTTP(w, r)
} else {
w.WriteHeader(403)
}
}

ServeHTTP只是检查请求的Host:

  • 如果Host和配置的allowed一直,那么调用handler的ServeHTTP。

  • 如果不一直返回403
      对于后一种情况,不仅不会得到应答,设置不知道有这个请求。
      现在我们已经开发哈了中间件,只需要将其插入到需要的地方。
singleHosted = NewSingleHost(myHandler, "example.com")
http.ListenAndServe(":8080", singleHosted)
另一种方式

   我们刚刚写的那个中间件很简单,它只有15行代码。写这样的中间件,可以使用样板方法。由于Go支持函数为一等公民和闭包,并且有http.HandlerFunc包装函数,我们可以通过他创建一个中间件,而不是将其放入到一个结构体中。下面是这个中间件的写法。

func SingleHost(handler http.Handler, allowedHost string) http.Handler {
ourFunc := func(w http.ResponseWriter, r *http.Request) {
host := r.Host
if host == allowedHost {
handler.ServeHTTP(w, r)
} else {
w.WriteHeader(403)
}
}
return http.HandlerFunc(ourFunc)
}

我们定义了一个简单的函数SingleHost,它包装了Handler和允许的Host,在其内部我们实现了一个跟上面中间件类似的功能。我们内部的函数就是一个闭包,因此他可以访问外部函数的变量。最终HandlerFunc让我们可以将其变为Handler。
   觉得是使用HandlerFunc还是自己实现一个http.Handler完全取决于你自己。对于简单的情况,一个简单的函数就完全够了。如果你的中间件越来越多,那么就可以考虑实现自己的结构并把它们分开。
   同时标准库同时使用了两种功能。StripPrefix使用的是HandlerFunc,TimeoutHandler使用的是自定义的结构体。

一个更复杂的例子

   我们的SingleHost并不重要,我们只检测一个属性,要么将他传递给其他的handler,要么直接返回。然而存在这种情况,我们的程序需要对处理完进行后续处理。

添加数据是简单的

   如果只是想简单的添加数据,那么使用Write就可以了。

type AppendMiddleware struct {
handler http.Handler
} func (a *AppendMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
a.handler.ServeHTTP(w, r)
w.Write([]byte("Middleware says hello."))
}

返回的结构肯定会包含Middleware says hello.

问题

   但是操作其他的数据有点困难。例如我们想要在它前面添加数据而不是后面追加。如果我们在原Handler之前调用Write,那么将会失去控制,因为第一个Write已经将他写入了。
   通过其他方法修改原始输出,例如替换字符串,改变响应header,或者设置状态码都不会起作用,因为当handler返回的时候数据已经返回到客户端了。
   为了实现这个功能,我们需要一个特殊的ResponseWriter,他可以想buffer一样工作,收集数据,存储以备使用和修改。然后我们将这个ResponseWriter传递给handler,而不是传递真是的RW,这样在其之前我们已经修改了它了。
   幸运的是在标准库中有这样的一个工具。在net/http/httptest包里的ResponseRecorder能做所有我们需要的:保存状态码,一个响应header的map,将body放入byte 缓冲中。虽然它是再测试中中使用的,但是很服务我们的情况。

type ModifierMiddleware struct {
handler http.Handler
} func (m *ModifierMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
rec := httptest.NewRecorder()
// passing a ResponseRecorder instead of the original RW
m.handler.ServeHTTP(rec, r)
// after this finishes, we have the response recorded
// and can modify it before copying it to the original RW // we copy the original headers first
for k, v := range rec.Header() {
w.Header()[k] = v
}
// and set an additional one
w.Header().Set("X-We-Modified-This", "Yup")
// only then the status code, as this call writes out the headers
w.WriteHeader(418) // The body hasn't been written (to the real RW) yet,
// so we can prepend some data.
data := []byte("Middleware says hello again. ") // But the Content-Length might have been set already,
// we should modify it by adding the length
// of our own data.
// Ignoring the error is fine here:
// if Content-Length is empty or otherwise invalid,
// Atoi() will return zero,
// which is just what we'd want in that case.
clen, _ := strconv.Atoi(r.Header.Get("Content-Length"))
clen += len(data)
r.Header.Set("Content-Length", strconv.Itoa(clen)) // finally, write out our data
w.Write(data)
// then write out the original body
w.Write(rec.Body.Bytes())
}

最后

我们中间件的输出:

HTTP/1.1 418 I'm a teapot
X-We-Modified-This: Yup
Content-Type: text/plain; charset=utf-8
Content-Length: 37
Date: Tue, 03 Sep 2013 18:41:39 GMT Middleware says hello again. Success!

 这样就开启了一种新的可能,包装的handler完全手控制。

和其他handler分享数据

   在其他例子中,中间件可能需要暴露一些信息给其他中间件或者应用本身。例如nosurf需要给其他用户访问CSRF token的权限。
   最简单是是使用一个map,但是通常不希望这样。它将http.Request 的指针作为key,其他数据作为value。下面是nosurf的例子,Go的map非线程安全,所以要自己是实现。

type csrfContext struct {
token string
reason error
} var (
contextMap = make(map[*http.Request]*csrfContext)
cmMutex = new(sync.RWMutex)
)

数据由Token设置:

func Token(req *http.Request) string {
cmMutex.RLock()
defer cmMutex.RUnlock() ctx, ok := contextMap[req]
if !ok {
return ""
} return ctx.token
}

源码可以再nosurf的项目的context.go中找到。

使用Go开发HTTP中间件的更多相关文章

  1. IdHTTPServer(indy10)开发REST中间件

    IdHTTPServer(indy10)开发REST中间件 浏览器通过“get”方式查询数据URL样例:http://127.0.0.1:7777/query?sql=select * from t1 ...

  2. 基于gin的golang web开发:中间件

    gin中间件(middleware)提供了类似于面向切面编程或路由拦截器的功能,可以在请求前和请求之后添加一些自定义逻辑.实际开发中有很多场景会用到中间件,例如:权限验证,缓存,错误处理,日志,事务等 ...

  3. 全栈项目|小书架|服务器开发-Koa2中间件机制洋葱模型了解一下

    KOA2 是什么? Koa是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小.更富有表现力.更健壮的基石. 通过利用 asyn ...

  4. 【Traefik二次开发】中间件 Middleware 开发

    本篇只讨论HTTP中间件 中间件定义 https://doc.traefik.io/traefik/middlewares/overview/ Attached to the routers, pie ...

  5. 混合式APP开发中中间件方案Rexsee

    发现Rexsee时,他已经一年多没有更新过了,最后版本是2012年的. 他的实现思路是通过Android自带的Java - Javascript 桥机制,在WebView中的JavaScript同Ja ...

  6. 巨蟒python全栈开发django13:中间件部分

    1.回顾昨日内容 2.session认证装饰器 3.django整个流程 4.中间件简单应用 5.简单统计访问次数 6.中间件其他方法 7.orm单表内容回顾

  7. python全栈开发day70-Django中间件

    参考个人博客 http://wuchengyi.com/post/13/ 三.预习和扩展

  8. ASP.NET Core 开发-中间件(StaticFiles)使用

    ASP.NET Core 开发,中间件(StaticFiles)的使用,我们开发一款简易的静态文件服务器. 告别需要使用文件,又需要安装一个web服务器.现在随时随地打开程序即可使用,跨平台,方便快捷 ...

  9. 咏南中间件支持DELPHI低版本开发的两层程序平稳升级到三层

    提供DELPHI中间件及中间件集群,有意请联系. N年前,我们用DELPHI低版本开发的两层程序(比如工厂ERP系统),现在仍然在企业广泛地得到使用,但老系统有些跟不上企业的发展需要了.主要表现在:虽 ...

随机推荐

  1. 【SGU】495. Kids and Prizes

    http://acm.sgu.ru/problem.php?contest=0&problem=495 题意:N个箱子M个人,初始N个箱子都有一个礼物,M个人依次等概率取一个箱子,如果有礼物则 ...

  2. usaco silver刷水~其实是回顾一下,补题解

    [BZOJ1606][Usaco2008 Dec]Hay For Sale 裸01背包 ;i<=n;i++) for(int j=m;j>=a[i];j--) f[j]=max(f[j], ...

  3. BZOJ4517: [Sdoi2016]排列计数

    Description 求有多少种长度为 n 的序列 A,满足以下条件: 1 ~ n 这 n 个数在序列中各出现了一次 若第 i 个数 A[i] 的值为 i,则称 i 是稳定的.序列恰好有 m 个数是 ...

  4. 深入浅出 - Android系统移植与平台开发(九)- JNI介绍

    作者:唐老师,华清远见嵌入式学院讲师. JNI是在学习Android HAL时必须要面临一个知识点,如果你不了解它的机制,不了解它的使用方式,你会被本地代码绕的晕头转向,JNI作为一个中间语言的翻译官 ...

  5. C#:实现快捷键自定义设置(转)

    项目开发过程中,需要实现类似有道词典的软件设置中的自定义快捷键功能,如下图所示: 当我们相继按下Ctrl+Alt+M的时候,软件就会自动将快捷键显示在文本框中. 最终的效果如下图所示: private ...

  6. Radar Installation

    Radar Installation 题目链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=86640#problem/C 题目: De ...

  7. c#语句 随堂练习2

    1.方程ax²+bx+c=0是一元二次方程,求根. 2.输入一个年份 ,判断是不是闰年.(能被4整除但不能被100整除的年份是闰年,有的世纪年也是闰年) 3.标准体重:男士体重=身高-100±3:女士 ...

  8. BizTalk 开发系列(四十二) 为BizTalk应用程序打包不同的环境Binding

    我们在使用微软或者其他公司提供的BizTalk应用程序MSI包的时候经常会有一个目标环境的选择选项.该选项可以在不同的环境下使用不同的绑定(BizTalk应用程序配置)感觉很高级. 其实这个非常的简单 ...

  9. Android课程---首学开发

    新建一个Activity2类: package com.hanqi.test; import android.app.Activity; import android.os.Bundle; impor ...

  10. Cocos2d-x环境搭建

    资源列表 官网上下载最新的cocos2d-x-3.3. 安装JDK,Eclipse,CDT插件,ADT插件. 下载Android SDK,更新.因为国内经常访问不了google,用vpn速度也有点慢. ...