问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

网络代理基础知识

创作时间:
作者:
@小白创作中心

网络代理基础知识

引用
CSDN
1.
https://blog.csdn.net/qq_36828822/article/details/140651322

代理服务器在网络通信中扮演着重要的角色,它不仅能帮助突破服务器的限制和封锁,还能实现系统安全、负载均衡等功能。本文将详细介绍代理服务器的分类、实现机制以及在实际项目中的应用。

代理的分类和实现机制

正向代理

当我们谈论代理服务器时,通常指正向代理。正向代理会向一个客户端或一组客户端提供代理服务,这些客户端通常属于同一个内部网络。当客户端尝试访问外部服务器时,请求必须首先通过正向代理。正向代理能够监控每个请求与回复,鉴权、控制访问权限并隐藏客户端实际地址。在隐藏了客户端的真实地址后,正向代理可以绕过一些防火墙的网络限制,这样一些开发者可以实现匿名。

一个简单的http正向代理服务如下所示。在这个例子中,代理服务器接受来自客户端的http请求,并通过handleHTTP函数对请求进行处理。处理的方式比较简单:当前代理服务器获取客户端的请求,并用自己的省份发送请求到服务器,代理服务器获取服务器的回复后利用io.Copy将回复发送至客户端。

package main
import (
    "io"
    "log"
    "net/http"
)
func main() {
    server := &http.Server{
        Addr: ":8888",
        Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            handleHTTP(w,r)
        }),
    }
    log.Fatal(server.ListenAndServe())
}
func handleHTTP(w http.ResponseWriter,req *http.Request)  {
    resp,_ := http.DefaultTransport.RoundTrip(req)
    defer resp.Body.Close()
    copyHeader(w.Header(),resp.Header)
    w.WriteHeader(resp.StatusCode)
    io.Copy(w,resp.Body)
}
func copyHeader(dst,src http.Header)  {
    for k, vv := range src {
        for _, v := range vv {
            dst.Add(k,v)
        }
    }
}

代理服务器除了要在客户端与服务器之间搭建一个管道,有时还需要处理一些特殊的http请求头——hop-by-hop请求头(不是给目标服务器使用的,而是专门给中间的代理服务器使用的)。代理服务器需要根据情况对hop-by-hop请求头做一些特殊处理,并在发送给目标服务器之前删除hop-by-hop请求头。

var hopHeaders = []string{
        "Connection",
        "Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
        "Keep-Alive",
        "Proxy-Authenticate",
        "Proxy-Authorization",
        "Te",      // canonicalized version of "TE"
        "Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522
        "Transfer-Encoding",
        "Upgrade",
    }

HTTP隧道代理

在上面的例子中,代理服务器是直接与目标服务器进行http通信的。但是在一些更复杂的情况下,客户端还希望与服务器进行https和http隧道形式的通信,以防止中间人攻击并隐藏http的特征。

在http隧道技术中,客户端会在第一次连接代理服务器时向代理服务器发送一个指令,通常是一个http请求,这里可将http请求头中的method设置为connect。

代理服务器收到指令后,将与目标服务器建立tcp连接。连接建立后,代理服务器会将之后收到的请求通过tcp连接转发给目标服务器。因此,只有初始连接请求是http请求,此后代理服务器将不再嗅探到任何数据,只是完成一个转发的动作。现在我们去查看其他开源的代理库,就会明白为什么要对connect方法进行单独处理了。

下面在前一个例子的基础上实现http隧道:

package main
import (
    "io"
    "log"
    "net"
    "net/http"
    "time"
)
func main() {
    server := &http.Server{
        Addr: ":8888",
        Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if r.Method == http.MethodConnect{
                
            }else{
                handleHTTP(w,r)
            }
        }),
    }
    log.Fatal(server.ListenAndServe())
}
func handleTunneling(w http.ResponseWriter,req *http.Request)  {
    dest_conn,err := net.DialTimeout("tcp",req.Host,10*time.Second)
    w.WriteHeader(http.StatusOK)
    hijacker,ok:=w.(http.Hijacker)
    client_conn,_,err :=hijacker.Hijack()//获取客户端与服务器之间的底层tcp连接
    if err != nil{
        http.Error(w,err.Error(),http.StatusServiceUnavailable)
    }
    go transfer(dest_conn,client_conn)
    go transfer(client_conn,dest_conn)
}
func transfer(dest io.WriteCloser,source io.ReadCloser)  {
    defer dest.Close()
    defer source.Close()
    io.Copy(dest,source)//一般不会这么粗暴,因为传输量巨大,会使用一个for循环,控制每次转发的数据包大小
}
func handleHTTP(w http.ResponseWriter,req *http.Request)  {
    resp,_ := http.DefaultTransport.RoundTrip(req)
    defer resp.Body.Close()
    copyHeader(w.Header(),resp.Header)
    w.WriteHeader(resp.StatusCode)
    io.Copy(w,resp.Body)
}
func copyHeader(dst,src http.Header)  {
    for k, vv := range src {
        for _, v := range vv {
            dst.Add(k,v)
        }
    }
}

MITM代理

除了上面提到的http隧道代理,代理服务器还可以使用https来处理数据,即让代理服务器直接与目标服务器建立https连接,同时在客户端与服务器之间建立另一个https连接。

但是https天然阻止了这种中间人攻击,而要突破这种封锁,就需要让客户端完全信任代理服务器颁布的证书,因此这种代理服务器也被称为MITM。MITM就像一个中间人,能够看到所有经过它的http和https流量,这也是一些代理软件(例如Charles)能够嗅探到https数据的原因。

透明代理

在上面的代理中,客户端需要感知到代理服务器的存在。还有一类代理,客户端不用感知到代理服务器,只需要直接向目标服务器中发送消息,通过os或路由设置强制将请求发送到代理服务器中。举一个例子,在macOS操作系统上就可以设置系统代理,这样,在浏览器上发送的所有http/https请求都会被转发到代理服务器的地址127.0.0.1:8888中,如下图所示。而在Linux服务器中,我们可以使用iptables、ipvs等技术强制将请求转发到代理服务器上。

反向代理

反向代理和正向代理的区别在于,反向代理位于服务器和防火墙(Firewall)之间,客户端不能直接与服务器通信,需要通过反向代理进行通信。我们比较熟悉的Nginx一般就是用于实现反向代理的。

反向代理有以下好处:

  • 负载均衡:对于大型分布式系统来说,反向代理可以提供一种负载均衡方案,在不同服务器之间平均分配传入流量,防止单个服务器过载。如果某台服务器完全无法运转,那么可以将流量转发给其他服务器。
  • 防范攻击:配备反向代理后,服务器无须暴露真实的IP地址,这就让攻击者难以进行针对性攻击(例如DDoS攻击),同时,反向代理通常拥有更高的安全性和更多抵御网络攻击的资源。
  • 缓存数据:代理服务器可以缓存服务器的响应数据,大大提升请求的速度。
  • SSL加密解密:反向代理可以对客户端发出的HTTPS请求进行解密,对服务器发出的http请求进行加密,从而节约目标服务器的资源。

在Go语言中,实现反向代理非常简单,Go语言标准库httputil提供了封装好的反向代理实现方式。

![](https://wy-static.wenxiaobai.com/chat-rag-image/15612207470921796710)
package main
import (
    "log"
    "net/http"
    "net/http/httputil"
    "net/url"
)
func main() {
    //初始化反向代理服务
    proxy, _ := NewProxy()
    http.HandleFunc("/", ProxyRequestHandler(proxy))
    log.Fatal(http.ListenAndServe(":8888", nil))
}
func NewProxy() (*httputil.ReverseProxy, error) {
    targetHost := "http://my-api-server.com"
    url, _ := url.Parse(targetHost)
    proxy := httputil.NewSingleHostReverseProxy(url)
    return proxy, nil
}
// 使用代理处理http请求
func ProxyRequestHandler(proxy *httputil.ReverseProxy) func(w http.ResponseWriter, r *http.Request) {
    return func(w http.ResponseWriter, r *http.Request) {
        proxy.ServeHTTP(w, r)
    }
}

在这个例子中,NewProxy函数借助了NewSingleHostReverseProxy函数生成一个反向代理服务器。NewSingleHostReverseProxy函数的参数是实际的后端服务器地址,如果后端有多个服务器,如果有多个服务器,那么可以用一些策略来选择某一个合适的后端服务地址,从而实现负载均衡。NewSingleHostReverseProxy内部封装了数据转发等操作,当客户端访问代理服务器时,请求会被转发到对应的目标服务器中。httputil对于反向代理的实现并不复杂,和正向代理的逻辑类似,主要包含了修改客户端的请求、处理特殊请求头、将请求转发到目标服务器,以及将目标服务器的数据转发回客户端等操作。

如何在实际项目中实现代理

我们在开发实际项目时可能使用自己搭建的代理服务器,也可能使用外部付费或免费的代理池。假设我们已经拥有了众多代理服务器地址,客户端应该如何实现对代理的访问呢?这里涉及两个问题,一个是如何访问代理服务器,二是在众多代理服务器中,怎样选择一个最合适的代理地址。

如何访问代理服务器

go的http标准库封装了代理访问的机制。在Transport结构体中,有一个Proxy函数用于返回当前应该使用的代理地址。

如何选择代理地址

代理地址的策略类似于调度策略,调度策略有很多,包括轮询、加权轮询、一致哈希算法等。下面使用轮询调度来实现对代理服务器的访问。

package proxy
import (
    "errors"
    "net/http"
    "net/url"
    "sync/atomic"
)
type ProxyFunc func(*http.Request) (*url.URL, error)
type roundRobinSwitcher struct {
    proxyURLs []*url.URL
    index     uint32
}
//取余算法实现轮询调度
func (r *roundRobinSwitcher) GetProxy(pr *http.Request) (*url.URL, error) {
    index := atomic.AddUint32(&r.index, 1) - 1
    u := r.proxyURLs[index%uint32(len(r.proxyURLs))]
    return u, nil
}
// RoundRobinProxySwitcher creates a proxy switcher function which rotates
// ProxyURLs on every request.
// The proxy type is determined by the URL scheme. "http", "https"
// and "socks5" are supported. If the scheme is empty,
// "http" is assumed.
func RoundRobinProxySwitcher(ProxyURLs ...string) (ProxyFunc, error) {
    if len(ProxyURLs) < 1 {
        return nil, errors.New("Proxy URL list is empty")
    }
    urls := make([]*url.URL, len(ProxyURLs))
    for i, u := range ProxyURLs {
        parsedU, err := url.Parse(u)
        if err != nil {
            return nil, err
        }
        urls[i] = parsedU
    }
    return (&roundRobinSwitcher{urls, 0}).GetProxy, nil
}
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号