首页 > 系统相关 >使用Golang实现Nginx代理功能

使用Golang实现Nginx代理功能

时间:2023-04-08 14:11:58浏览次数:34  
标签:node http err req 代理 Golang Nginx Path nodes

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

相关文章

  • golang中的substring
    发现golangstrings包中没有javastring.substring方法,于是尝试了下:想要在java中实现提取“世”的需求//java代码Strings="Hello,世界";System.out.println(s.substring(7,8));//世在golang中尝试这样://golang代码s1:="Hello,世界"fmt.Println(s1[7:8])//�因为......
  • 巧如范金,精比琢玉,一分钟高效打造精美详实的Go语言技术简历(Golang1.18)
    转自刘悦研发少闲月,九月人倍忙。又到了一年一度的“金九银十”秋招季,又到了写简历的时节,如果你还在用传统的Word文档寻找模板,然后默默耕耘,显然就有些落后于时代了,本次我们尝试使用云平台flowcv高效打造一份巧如范金、精比琢玉的高品质Golang技术简历。首先来到云平台:flowcv.com......
  • Nginx配置——反向代理
    Nginx配置——反向代理https://huaweicloud.csdn.net/63560389d3efff3090b58adb.html?spm=1001.2101.3001.6650.7&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Eactivity-7-102508463-blog-127776029.235%5Ev28%5Epc_relevant_defaul......
  • ASEMI代理AD8226ARZ-R7原装ADI车规级AD8226ARZ-R7
    编辑:llASEMI代理AD8226ARZ-R7原装ADI车规级AD8226ARZ-R7型号:AD8226ARZ-R7品牌:ADI/亚德诺封装:SOIC-8批号:2023+引脚数量:8安装类型:表面贴装型AD8226ARZ-R7汽车芯片AD8226ARZ-R7特征带1个外部电阻器的增益设置增益范围:1至1000输入电压低于接地电压电源以外的输入保护非常宽的电源范围单......
  • ASEMI代理AD8226ARZ-R7原装ADI车规级AD8226ARZ-R7
    编辑:llASEMI代理AD8226ARZ-R7原装ADI车规级AD8226ARZ-R7型号:AD8226ARZ-R7品牌:ADI/亚德诺封装:SOIC-8批号:2023+引脚数量:8安装类型:表面贴装型AD8226ARZ-R7汽车芯片AD8226ARZ-R7特征带1个外部电阻器的增益设置增益范围:1至1000输入电压低于接地电压电源以外的输入保护非......
  • Nginx 集群搭建
    Nginx集群搭建https://huaweicloud.csdn.net/63560e7dd3efff3090b591dc.html?spm=1001.2101.3001.6650.14&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Eactivity-14-117463438-blog-127292749.235%5Ev28%5Epc_relevant_default......
  • ASEMI代理AD8210YRZ-REEL7原装ADI车规级AD8210YRZ-REEL7
    编辑:llASEMI代理AD8210YRZ-REEL7原装ADI车规级AD8210YRZ-REEL7型号:AD8210YRZ-REEL7品牌:ADI/亚德诺封装:SOP-8批号:2023+安装类型:表面贴装型引脚数量:8类型:车规级芯片AD8210YRZ-REEL7特性4000伏每分钟静电放电高共模电压范围-2V至+65V操作-5V至+68V存活缓冲输出电压......
  • ASEMI代理AD8210YRZ-REEL7原装ADI车规级AD8210YRZ-REEL7
    编辑:llASEMI代理AD8210YRZ-REEL7原装ADI车规级AD8210YRZ-REEL7型号:AD8210YRZ-REEL7品牌:ADI/亚德诺封装:SOP-8批号:2023+安装类型:表面贴装型引脚数量:8类型:车规级芯片AD8210YRZ-REEL7特性4000伏每分钟静电放电高共模电压范围-2V至+65V操作-5V至+68V存活缓冲输出电压5mA输出驱动能......
  • 51、K8S-流量调度-Ingress、Ingress-nginx、Dashboard
    Kubernetes学习目录1、Ingress基础1.1、简介在实际的应用中,kubenetes接受的不仅仅有内部的流量,还有外部流量,我们可以通过两种方式实现将集群外部的流量引入到集群的内部中来,从而实现外部客户的正常访问。service方式: nodePort、externalIP等service对象方式,借助于names......
  • 可以替代Nginx的web服务软件
    1.Nginx(https://nginx.org/en/)2.HAProxy(https://www.haproxy.org/)3.Caddy2(https://caddyserver.com/)4.lighttpd (https://www.lighttpd.net/)5.Traefik(https://traefik.cn/)6.ApacheTomcat(https://tomcat.apache.org/)7.envoy(https://www.envoyprox......