由于业务需要实现对多个web应用做同域二级目录代理,用NGINX的又感觉太重了,而且不好做配置页面,用golang来实现代理功能
- 支持正则表达式匹配机制
- 支持多应用多级目录代理。
- 支持应用子路由代理
- 支持webapi代理
- 支持websocket代理
- 支持禁用缓存设置
- 支持http、https混合使用
- 支持/dir/app 重定向为 /dir/app/
- 支持简单的路由热度升级
定义处理器选项并初始化默认数据记录
package main
type HandlerOptions struct {
Id int32
/* 处理器名称 */
Name string
/* 处理路径 */
Path string
/* 禁用缓存 */
DisableCache bool
/* 目标服务器 */
Target string
/* 子处理器列表 */
SubHandlers []*HandlerOptions
/* 是否启用 */
Enabled bool
}
var handlers = []*HandlerOptions{
{
Id: 1,
Name: "My First App",
Path: "^/app1",
Target: "https://192.168.1.20:8000/",
Enabled: true,
DisableCache: true,
SubHandlers: []*HandlerOptions{
{
Id: 3,
Name: "API接口",
Path: "^/app1/api",
Target: "http://192.168.1.30:8100/api",
Enabled: true,
DisableCache: true,
SubHandlers: []*HandlerOptions{},
},
{
Id: 4,
Name: "静态文件服务器",
Path: "^/app1/static/",
Target: "https://192.168.1.70:8200/static/",
Enabled: true,
DisableCache: false,
SubHandlers: []*HandlerOptions{},
},
{
Id: 7,
Name: "其他系统接口",
Path: "^/app1/others/api/",
Target: "http://192.168.1.50:8300/api/",
Enabled: true,
DisableCache: true,
SubHandlers: []*HandlerOptions{},
},
},
},
{
Id: 2,
Name: "My Second App",
Path: "/app2",
Target: "https://192.168.1.100:8000/",
Enabled: true,
DisableCache: true,
SubHandlers: []*HandlerOptions{},
},
}
处理器节点的实现
通过root节点的Options方法更新全部处理器节点。
package main
import (
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"regexp"
"strings"
"sync"
)
type ProxyHandlerNode struct {
// 节点选项
options *HandlerOptions
// 节点处理器列表
nodes []*ProxyHandlerNode
// 匹配正则
regxExpression *regexp.Regexp
// 匹配计数器, 用于热度排序
Popularity int
// 目标服务器URL
TargetURL *url.URL
// 目标服务器路径(这段路径需要放到请求Path前面)
TargetPath string
// 操作锁
locker sync.Mutex
}
// 配置节点选项
func (node *ProxyHandlerNode) Options(options []*HandlerOptions) {
node.locker.Lock()
defer node.locker.Unlock()
node.nodes = make([]*ProxyHandlerNode, 0)
for i := 0; i < len(options); i++ {
opt := options[i]
if !opt.Enabled {
continue
}
newNode := ProxyHandlerNode{}
newNode.Popularity = 0
newNode.options = opt
var err error
newNode.regxExpression, err = regexp.Compile(opt.Path)
if err != nil {
fmt.Printf("Regexp Compile Error:%v\n", err)
continue
}
newNode.TargetURL, err = url.Parse(opt.Target)
if err != nil {
fmt.Printf("URL Parse Error:%v\n", err)
continue
}
newNode.TargetPath = newNode.TargetURL.Path
newNode.TargetURL.Path = ""
newNode.Options(opt.SubHandlers)
node.nodes = append(node.nodes, &newNode)
}
}
/*
* 随着匹配次数热度爬升
*/
func popularityUp(nodes []*ProxyHandlerNode, node *ProxyHandlerNode, index int) {
node.Popularity++
if index > 0 {
if node.Popularity > nodes[index-1].Popularity {
nodes[index], nodes[index-1] = nodes[index-1], nodes[index]
}
}
}
/*
* 匹配请求URL的处理器
*/
func (node *ProxyHandlerNode) MatchNode(url string) *ProxyHandlerNode {
node.locker.Lock()
defer node.locker.Unlock()
for i := 0; i < len(node.nodes); i++ {
var cnode = node.nodes[i]
if cnode.regxExpression.Match([]byte(url)) {
popularityUp(node.nodes, cnode, i)
var ret = cnode.MatchNode(url)
if ret != nil {
return ret
}
return cnode
}
}
return nil
}
/*
* 代理请求
*/
func (node *ProxyHandlerNode) ProxyRequest(req *http.Request, res http.ResponseWriter) {
// 替换url
var requestPath = node.regxExpression.ReplaceAllString(req.URL.Path, "")
// http重定向
if len(requestPath) == 0 && len(req.URL.RawQuery) == 0 {
// 把 /dir/app 重定向为 /dir/app/
if !strings.HasSuffix(req.URL.Path, "/") {
http.Redirect(res, req, req.URL.Path+"/", http.StatusTemporaryRedirect)
return
}
}
// 修复路径
req.URL.Path = node.TargetPath + requestPath
// 代理
proxy := httputil.NewSingleHostReverseProxy(node.TargetURL)
proxy.ModifyResponse = func(r *http.Response) error {
if node.options.DisableCache {
r.Header.Set("Cache-Control", "no-store,no-cache")
r.Header.Set("Pragma", "no-store,no-cache")
r.Header.Set("Expires", "0")
r.Header.Del("Last-Modified")
}
r.Header.Del("Server")
return nil
}
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
fmt.Printf("Serve Error %v {%v}\n", r.URL, err)
}
if node.options.DisableCache {
req.Header.Set("Cache-Control", "no-store,no-cache")
req.Header.Set("Pragma", "no-store,no-cache")
req.Header.Del("if-modified-since")
req.Header.Del("if-none-match")
}
proxy.ServeHTTP(res, req)
}
食用方式
package main
import (
"crypto/tls"
"fmt"
"log"
"net/http"
)
var rootHandler = &ProxyHandlerNode{}
func handleRequestAndRedirect(res http.ResponseWriter, req *http.Request) {
//
var node = rootHandler.MatchNode(req.URL.Path)
if node != nil {
node.ProxyRequest(req, res)
return
}
fmt.Printf("Not Handle Request %v\n", req.RequestURI)
res.WriteHeader(http.StatusBadGateway)
res.Write([]byte("Proxy Error: No suitable forwarding processor matched."))
}
func main() {
// 初始化处理器
rootHandler.Options(handlers)
// 跳过tsl证书验证
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
http.HandleFunc("/", handleRequestAndRedirect)
// http
if err := http.ListenAndServe(":8888", nil); err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
标签:node,http,err,req,代理,Golang,Nginx,Path,nodes
From: https://www.cnblogs.com/l2030/p/17298447.html