由于业务需要实现对多个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)
	}
}

总结

以上是如意编程网为你收集整理的使用Golang实现Nginx代理功能的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得如意编程网网站内容还不错,欢迎将如意编程网推荐给好友。