开发一个带缓存的代理服务器

开发一个带缓存的代理服务器 #

背景 #

前端页面中经常会有很多的请求,而开发环境的服务器资源比较差,就会经常出现页面加载两三面的情况,并且开发时也经常需要刷新页面,这就是一个降低了工作效率的点。

为了解决该问题,我就打算开发一个可以缓存请求的代理服务器。

原来的工作流程是,前端启动项目时,后端服务器地址可以是线上的测试环境,或者本地的Go服务器。

使用缓存服务后,就将服务器地址改为本地的缓存服务地址,比如localhost:7000。

缓存服务有web管理页面,可以配置源地址,可以设置忽略缓存的条件、清理和查看缓存。

实现 #

1. 服务器 #

先是一个普通的服务器,监听 7001 端口

1
2
3
4
5
// Handle the requests
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    proxyHandler(w, r, proxy)
})
log.Fatal(http.ListenAndServe("localhost:7000", nil))

3. 处理缓存 #

缓存保存在内存中,是一个全局唯一的结构体

1
2
3
4
type MemoryCache struct {
	cache map[string]string
	queue chan []string
}

缓存的主体是一个原生的map。key是由url和请求体计算而来的,value是返回体。

queue是为了解决map并发不安全的报错。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
func NewMemoryCache() ICache {
	ins := &MemoryCache{
		cache: make(map[string]string),
		queue: make(chan []string),
	}
	// Concurrency safe set
	go func() {
		for i := range ins.queue {
			ins.cache[i[0]] = i[1]
		}
	}()
	return ins
}
func (c *MemoryCache) Set(key string, value string) {
	//	 Concurrency safe set
	c.queue <- []string{key, value}
}

处理请求 #

如果没有命中缓存,就要发起一个请求到源。要分别处理请header、body,要根据配置设置对应的host地址。

利用标准库的 httputil.NewSingleHostReverseProxy 方法发起请求,

返回的结果既要返回前端,又要进行缓存。

使用效果 #

首先缓存没有做持久化,感觉没必要,因为第一次请求也就两三秒,持久化的意义不大。

所以第一次进入系统或页面,还是要进行正常的请求。后面在刷新页面的时间都能控制在100-200ms的范围。

使用方法 #

下载代码直接运行,或者 go build 出可执行文件,或者下载打包好的文件即可使用。

运行服务后会在本地7000端口开启一个缓存代理服务器和一个管理服务器,通过 localhost:8080 可以进入管理页面,设置源服务器地址,忽略缓存的条件、查看缓存的内容等。

遇到的问题 #

  1. map并发问题
  2. NewSingleHostReverseProxy需要再Director回调中设置请求的header、url等信息
  3. 返回的数据是在ResponseWriter中,好像不能被读取两次,不能同时返回给前端和保存到缓存,要进行一些特殊处理。