Go微服务网关

本文详细介绍了网络代理的概念,包括正向代理和反向代理的原理与区别。重点讲解了Go语言中如何利用http.ReverseProxy实现HTTP代理,探讨了连接、负载均衡、错误处理等相关知识,并提到了HTTPS代理、WebSocket代理和TCP代理的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是网络代理

  1. 用户通过代理请求信息
  2. 请求通过网络代理完成转发达到目标服务器
  3. 目标服务器响应后在通过网络代理回传给用户

在这里插入图片描述

网络代理和网络转发区别:

网络代理:用户不直接连接服务器,网络代理去连接,获取数据后返回给用户

网络转发:是路由器对报文的转发操作,中间可能对数据包修改

代理类型

正向代理

原理: 正向代理是从客户端的角度出发,服务于特定用户(比如说一个局域网内的客户)无法访问的服务资源,可以隐藏用户真实IP,比如:浏览器web代理,VPN;

实现一个web浏览器代理:

  1. 代理接收客户端请求,复制原请求对象,并根据数据配置新请求各种参数
  2. 把新请求发送到真实的服务端,并接收到服务端返回
  3. 代理服务器对相应做一些处理,然后返回给客户端

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ckJl48cM-1648220235094)(C:\Users\咩咩\AppData\Roaming\Typora\typora-user-images\1648026912630.png)]

正向代理的代码:

type Pxy struct{}

func (p *Pxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	fmt.Printf("Received request %s %s %s\n", req.Method, req.Host, req.RemoteAddr)
	//panic("implement me")
	transport := http.DefaultTransport //定义一个默认的链接池
	// 1.浅拷贝对象,然后就在新增属性数据
	outReq := new(http.Request)
	*outReq = *req //拷贝一份新的,下文发生改变
	if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
		if prior, ok := outReq.Header["X-Forwarded-For"]; ok {
			clientIP = strings.Join(prior, ",") + "," + clientIP
		}
		outReq.Header.Set("X-Forwarded-For", clientIP)
	}
	//2.请求下游
	resp, err := transport.RoundTrip(outReq)

	// resp 里面的内容进行输出
	if err != nil {
		rw.WriteHeader(http.StatusBadGateway) //状态不良网关
		return
	}
	//3.把下游的请求返回给上游
	for key, value := range resp.Header { //头的写入
		for _, v := range value {
			rw.Header().Add(key, v)
		}
	}
	rw.WriteHeader(resp.StatusCode)
	io.Copy(rw, resp.Body) // 拷贝body的内容
	resp.Body.Close()
}

//web 的正向代理
func main() {
	fmt.Println("server on :8080")
	http.Handle("/", &Pxy{})
	http.ListenAndServe("127.0.0.1:800", nil)
}

反向代理

原理: 是一种服务端的代理技术,帮助服务器做负载均衡,缓存,提供安全校验等,可以隐藏服务器真实IP,比如:LVS技术,nginx proxy_pass等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vPrseKNA-1648220235095)(C:\Users\咩咩\AppData\Roaming\Typora\typora-user-images\1648039067065.png)]

如何实现一个反向代理:

  • 代理接收客户端请求,更改请求结构体信息
  • 通过一定的均衡负载算法获取下游的服务地址
  • 把请求都送到下游服务器,并获取返回的内容
  • 对返回的内容做一些处理,然后返回给客户端

反向代理:

  • 先构建好下游服务器
//模拟反向代理的真实服务器 
type RealServer struct {
	Addr string
}

func (r *RealServer) Run() {
	log.Println("start httpserver at " + r.Addr)
	mux := http.NewServeMux()           //多路服用
	mux.HandleFunc("/", r.HelloHandler) //启动路由将方法设置好
	mux.HandleFunc("/base/error", r.ErrorHandler)
	server := &http.Server{
		Addr:         r.Addr,
		WriteTimeout: 3 * time.Second,
		Handler:      mux,
	}
	go func() {
		log.Fatal(server.ListenAndServe())
	}()
}

// HelloHandler 方法
func (r *RealServer) HelloHandler(w http.ResponseWriter, req *http.Request) {
	//127.0.0.1:8080/abc?sdsdsa=11
	//r.Addr = 127.0.0.1:8080
	//  req.URL.Path= abc
	upath := fmt.Sprintf("http://%s%s\n", r.Addr, req.URL.Path)
	fmt.Println("upPath:", upath)
	io.WriteString(w, upath)
}

func (r *RealServer) ErrorHandler(w http.ResponseWriter, req *http.Request) {
	upath := "error handler"
	w.WriteHeader(500)
	io.WriteString(w, upath)
}

// 模拟反向代理的真实服务器
func main() {
	//创建两个真实的realServer
	rs1 := &RealServer{Addr: "127.0.0.1:2003"}
	rs1.Run()
	rs2 := &RealServer{Addr: "127.0.0.1:2004"}
	rs2.Run()

	// 监听关闭信号
	quit := make(chan os.Signal)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
}

  • 再看真是代理服务器的设置

//reverse_proxy  反向代理的实现
func main() {
	http.HandleFunc("/", handler)
	log.Println("start serving on port" + port)
	err := http.ListenAndServe(":"+port, nil)
	if err != nil {
		log.Fatal(err)
	}
}

var (
	proxy_addr = "http://127.0.0.1:2003"
	port       = "2002"
)

func handler(w http.ResponseWriter, r *http.Request) {
	// 1. 解析代理地址,并更改请求体的协议和主机
	proxy, err := url.Parse(proxy_addr)
	r.URL.Scheme = proxy.Scheme //http
	r.URL.Host = proxy.Host     // 127.0.0.1:2003

	//2. 请求下游
	transport := http.DefaultTransport
	resp, err := transport.RoundTrip(r)
	if err != nil {
		log.Fatal(err)
		return
	}
	//3. 把下游的请求的内容返回给上游
	for k, vv := range resp.Header {
		for _, v := range vv {
			w.Header().Add(k, v)
		}
	}
	defer resp.Body.Close()
	bufio.NewReader(resp.Body).WriteTo(w) // 将resp.Body的内容输出到http.ResponseWriter,
}

上面的简版http代理,不具备以下功能:

  • 错误回调及错误日志处理
  • 更改代理返回内容
  • 负载均衡
  • url重写
  • 限流,熔断,降级
  • 数据统计
  • 权限认证

http代理,重点详解

用官方的ReverseProxy 实现一个代理http代理

  • ReverseProxy 功能点
    • 更改内容支持
    • 错误信息回调
    • 支持自定义的负载均衡
    • url重写功能
    • 连接池功能
    • 支持websocket服务
    • 支持https代理
  • ReverseProxy 示例
  • ReverseProxy 源码分析

拓展 ReverseProxy 功能

  • 4种负载轮询类型实现及接口封装
  • 拓展中间件的支持:限流,熔断实现,权限,数据统计

用ReverseProxy 实现一个HTTP代理

官方自带的 ReverseProxy 结构体
地址:https://golang.google.cn/pkg/net/http/httputil/
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d2vp4vt2-1648220235095)(C:\Users\咩咩\AppData\Roaming\Typora\typora-user-images\1648136422502.png)]

type ReverseProxy struct {
    // 控制器必须是一个函数,函数内部可以对请求进行修改
	Director func(*http.Request)
    	// 连接池,如果为nil,则使用 http.DefaultTransport。
	Transport http.RoundTripper
    ErrorLog *log.Logger
    	//返回内容 修改response的函数
	ModifyResponse func(*http.Response) error
    	// 错误处理回调函数,如果为零,则默认记录提供的错误并返回 502
	ErrorHandler func(http.ResponseWriter, *http.Request, error)
}

先实例化一个*ReverseProxy 然后实现ServerHTTP方法就行

var addr = "127.0.0.1:2002" // 	请求的转发的地址
//用官方的reverseProxy来代理http
func main() {
	rs1 := "http://127.0.0.1:2003/base"  //转发到rs1
	url1, err := url.Parse(rs1)
	fmt.Println("url1:", url1)
	if err != nil {
		log.Println(err)
	}
	proxy := httputil.NewSingleHostReverseProxy(url1)
	log.Println("Starting httpserver at " + addr)
	log.Fatal(http.ListenAndServe(addr, proxy))
}

先启动模拟反向代理的真实服务器 在启动reverseProxy反向代理**

在这里插入图片描述

reverseProxy更改内容实现(modifyFunc):

代码:


//reverseProxy 更改内容的支持  主要实在reverseProxy结构体中的modifyFunc模块里面修改内容
var addr = "127.0.0.1:2002" // 	请求的转发的地址
func main() {
	//127.0.0.1:2002/xxx
	//127.0.0.1:2003/base/xxx
	rs1 := "http://127.0.0.1:2003/base"
	url1, err1 := url.Parse(rs1)
	if err1 != nil {
		log.Println(err1)
	}
	proxy := NewSingleHostReverseProxy(url1)
	log.Println("Starting httpserver at " + addr)
	log.Fatal(http.ListenAndServe(addr, proxy))
}

func NewSingleHostReverseProxy(target *url.URL) *httputil.ReverseProxy {
	//http://127.0.0.1:2002/dir?name=123
	//RayQuery: name=123
	//Scheme: http
	//Host: 127.0.0.1:2002
	targetQuery := target.RawQuery
	director := func(req *http.Request) {
		//url_rewrite
		//127.0.0.1:2002/dir/abc ==> 127.0.0.1:2003/base/abc ??
		//127.0.0.1:2002/dir/abc ==> 127.0.0.1:2002/abc
		//127.0.0.1:2002/abc ==> 127.0.0.1:2003/base/abc
		re, _ := regexp.Compile("^/dir(.*)")
		req.URL.Path = re.ReplaceAllString(req.URL.Path, "$1")

		req.URL.Scheme = target.Scheme
		req.URL.Host = target.Host

		//target.Path : /base
		//req.URL.Path : /dir
		req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
		if targetQuery == "" || req.URL.RawQuery == "" {
			req.URL.RawQuery = targetQuery + req.URL.RawQuery
		} else {
			req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
		}
		if _, ok := req.Header["User-Agent"]; !ok {
			req.Header.Set("User-Agent", "")
		}
	}
	//更改内容
	modifyFunc := func(res *http.Response) error {
		if res.StatusCode != 200 {
			//return errors.New("error statusCode")
			// 获取内容
			oldPayload, err := ioutil.ReadAll(res.Body)
			if err != nil {
				log.Println(err)
				return nil
			}
			// 追加内容
			newPayLoad := []byte("hello" + string(oldPayload))                              //在原来的内容基础的前面添加“hello”
			res.Body = ioutil.NopCloser(bytes.NewBuffer(newPayLoad))                        //将新的内容(newPayLoad)回写到resp.Body里面
			res.ContentLength = int64(len(newPayLoad))                                      // 将新的内容长度重新赋值
			res.Header.Set("Content_Length", strconv.FormatInt(int64(len(newPayLoad)), 10)) //提示客户端去读这个内容
			// 上面或者写成: resp.Header.Set("Content_Length",fmt.Sprint(newPayLoad))
		}
		return nil
	}
	errorHandler := func(res http.ResponseWriter, req *http.Request, err error) {
		res.Write([]byte(err.Error()))
	}
	return &httputil.ReverseProxy{Director: director, ModifyResponse: modifyFunc, ErrorHandler: errorHandler}
}

func singleJoiningSlash(a, b string) string {
	aslash := strings.HasSuffix(a, "/")
	bslash := strings.HasPrefix(b, "/")
	switch {
	case aslash && bslash:
		return a + b[1:]
	case !aslash && !bslash:
		return a + "/" + b
	}
	return a + b
}

Connection

  • 标记请求发起方与第一代理的状态
    在这里插入图片描述

HTTP请求发送之前,需要先建立TCP连接,只有TCP连接建立,才可以发送HTTP请求。
当HTTP请求发送并响应完成,有两种情况,

在这里插入图片描述

在浏览器中,ConnectionID 表示的就是TCP建立连接的ID

在这里插入图片描述

Connection 头(header) 决定当前的事务完成后,是否会关闭网络连接。如果该值是“keep-alive”,网络连接就是持久的,不会关闭,使得对同一个服务器的请求可以继续在该连接上完成。

特定于连接的标头字段(例如Connection)不得与HTTP / 2一起使用

除去标准的逐段传输(hop-by-hop)头Keep-Alive, Transfer-Encoding, TE, Connection, Trailer, Upgrade (en-US), Proxy-Authorization and Proxy-Authenticate),任何逐段传输头都需要在 Connection 头中列出,这样才能让第一个代理知道必须处理它们且不转发这些头。标准的逐段传输头也可以列出(常见的例子是 Keep-Alive,但这不是必须的)。

  • 决定当前事务完成后,是否会关闭网络

    Conection:keep-alive 不关闭网络,长连接
    
    Conection:close  关闭网络
    
    Conection:Upgrade 协议升级
    

“TE” “Trailer”

  • TE 是request_header,表示希望传输的编码类型

如:TE:trailers,deflate;q= 0.5 表示,期望在采用分块传输编码响应中接收挂载字段,zlib编码,0.5优先级排序

  • Trailer 是response header ,允许发送方在消息后面添加额外的元信息,超时时间等。

如: Trailer :Expires (超时时间的标记),表示,EXpires 将出现在分块信息的结尾

第一代理除去标准的逐段传输头(hop-by-hop)

  • 逐段传输头都需要在 Connection 头中列出
  • 第一个代理知道必须处理(如协议升级)它们且不转发它
  • 逐段传输头:Keep_alive ,Transfer_encoding,TE,Connection,Trailer,Upgrade,Proxy-Authorization,proxy-Authenticate(验证)

ReverseProxy 补充知识-

-特殊的StatusCode

  • 100 表示目前一切正常,客户端可以继续请求
  • 101 服务端发送客户端升级协议的请求

100-continue 状态:

  • 客户端要post 的数据大于1024字节的时候
  • 客户端不会直接就发起post请求,二十分两步
    1. 发送一个请求,包含一个(期望)Expect:100-continue,询问Server是否愿意接收数据
    2. 接收到server返回的100-continue应答以后,返回100状态,才把数据post给server

特殊的Header头

  • “X-Forwarded-For” :标记客户端的ip,每一个反向代理的服务器的ip做个列表的呈现
  • “X-Real-IP”:实际请求的ip标记
  • “Connection” :请求的时候,这个连接的状态,(关闭,长连接,升级的协议)连接
  • “TE” : 希望传输的类型是什么,请求的header
  • “Trailer”: 返回的header头,允许发送方在后面添加的一些元信息,超时时间等等

X-Forwarded-For

记录代理服务器的地址,每经过一个代理,该字段会加上一个记录,由于是记录来源地址,所以该字段不会保存最后一个真实服务器的地址

  • 存储客户端 ip 和反向代理 IP 列表,以逗号+空格分隔
  • 记录最后直连实际服务器之前的整个 代理过程
  • 可能会被伪造 ip,但是直连实际服务器这段不会被伪造

图示:

在这里插入图片描述

可以看到,第一层代理,其存储了客户端的 IP,第二层 代理 追加了 第一层的代理的IP(proxy_ip1),但并没有保存自己的proxy_ip2。

X-Real-IP

也是用来记录服务器的地址,但是和上面的不同,它不把记录添加到结尾,而是直接替换。

  • 请求实际服务器的 IP,不会被伪造,

  • 每过一层 代理都会被覆盖掉,只需第一层代理设置转发

  • IP可以被伪造,但如果存在一级以上的 代理,它就不会收到影响,因为每经过一次代理,它就会被覆盖

在这里插入图片描述

可以通过:request.RemoteAddr 拿到IP地址

第一层 代理已经拿到了真实的 ip,并在 header 中设置了 X-Real-IP,因此第二层 代理不需要再去设置 X-Real-IP,只需要做转发就可以

remote_addr

表示上一个客户端连接的地址,不存在 dai li 就表示客户端的地址,存在 dai li 就表示最后一个 dai li 服务器的地址

  • remote_addr无法伪造,
  • remote_addr字段不是通过请求头来决定的,而是服务端在建立tcp连接时获取的的客户端地址

可以这样理解,因为建立 TCP 连接需要三次握手,如果伪造了源 IP,无法建立 TCP 连接,更不会有后面的 HTTP 请求

Go 中可以通过 req.RemoteAddr代码获取请求地址的 ip,

End-to-end 和 Hop-by-hop
End-to-end "端到端"头部

  • 此类头部字段会转发给 请求/响应 的最终接收目标。
  • 必须保存在由缓存生成的响应头部。
  • 必须被转发。

Hop-by-hop 逐跳首部

  • 此类头部字段只对单次转发有效。会因为转发给缓存dai li 服务器而失效。
  • HTTP 1.1 版本之后,如果要使用 Hop-by-hop 头部字段则需要提供 Connection 字段。

除了一下 8 个字段为逐跳字段,其余均为端到端字段。

  • Connection
  • Keep-Alive
  • Proxy-Authenticate
  • Proxy-Authenrization
  • Trailer
  • TE
  • Tranfer-Encoding
  • Upgrade

其他 header 头

  • Connection——标记请求连接是长连接还是短连接,或者说已关闭;http1.1 后默认是从长连接,该字段的值为:keep-alive
  • TE——HTTP 请求头字段,传输编码的优先级,固定值:TE: trailers,deflate
  • Trailer——HTTP 响应头字段,用户说明传输中分块编码的编码信息,固定值 Trailer: Max-Forwards

https代理

websocket代理

tcp代理

到结尾,而是直接替换。

  • 请求实际服务器的 IP,不会被伪造,

  • 每过一层 代理都会被覆盖掉,只需第一层代理设置转发

  • IP可以被伪造,但如果存在一级以上的 代理,它就不会收到影响,因为每经过一次代理,它就会被覆盖

    [外链图片转存中…(img-bMwhEVog-1648220235100)]

可以通过:request.RemoteAddr 拿到IP地址

第一层 代理已经拿到了真实的 ip,并在 header 中设置了 X-Real-IP,因此第二层 代理不需要再去设置 X-Real-IP,只需要做转发就可以

remote_addr

表示上一个客户端连接的地址,不存在 dai li 就表示客户端的地址,存在 dai li 就表示最后一个 dai li 服务器的地址

  • remote_addr无法伪造,
  • remote_addr字段不是通过请求头来决定的,而是服务端在建立tcp连接时获取的的客户端地址

可以这样理解,因为建立 TCP 连接需要三次握手,如果伪造了源 IP,无法建立 TCP 连接,更不会有后面的 HTTP 请求

Go 中可以通过 req.RemoteAddr代码获取请求地址的 ip,

End-to-end 和 Hop-by-hop
End-to-end "端到端"头部

  • 此类头部字段会转发给 请求/响应 的最终接收目标。
  • 必须保存在由缓存生成的响应头部。
  • 必须被转发。

Hop-by-hop 逐跳首部

  • 此类头部字段只对单次转发有效。会因为转发给缓存dai li 服务器而失效。
  • HTTP 1.1 版本之后,如果要使用 Hop-by-hop 头部字段则需要提供 Connection 字段。

除了一下 8 个字段为逐跳字段,其余均为端到端字段。

  • Connection
  • Keep-Alive
  • Proxy-Authenticate
  • Proxy-Authenrization
  • Trailer
  • TE
  • Tranfer-Encoding
  • Upgrade

其他 header 头

  • Connection——标记请求连接是长连接还是短连接,或者说已关闭;http1.1 后默认是从长连接,该字段的值为:keep-alive
  • TE——HTTP 请求头字段,传输编码的优先级,固定值:TE: trailers,deflate
  • Trailer——HTTP 响应头字段,用户说明传输中分块编码的编码信息,固定值 Trailer: Max-Forwards
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值