golang Golang http代理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang Golang http代理相关的知识,希望对你有一定的参考价值。

package main
​
import (
	"crypto/tls"
	"flag"
	"io"
	"log"
	"net"
	"net/http"
	"time"
)
​
func handleTunneling(w http.ResponseWriter, r *http.Request) {
	log.Printf("Tunneling to %s", r.Host)
	dest_conn, err := net.DialTimeout("tcp", r.Host, 10*time.Second)
	if err != nil {
		http.Error(w, err.Error(), http.StatusServiceUnavailable)
		return
	}
	w.WriteHeader(http.StatusOK)
	hijacker, ok := w.(http.Hijacker)
	if !ok {
		http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
		return
	}
	client_conn, _, err := hijacker.Hijack()
	if err != nil {
		http.Error(w, err.Error(), http.StatusServiceUnavailable)
	}
	go transfer(dest_conn, client_conn)
	go transfer(client_conn, dest_conn)
}
func transfer(destination io.WriteCloser, source io.ReadCloser) {
	defer destination.Close()
	defer source.Close()
	io.Copy(destination, source)
}
func handleHTTP(w http.ResponseWriter, req *http.Request) {
	resp, err := http.DefaultTransport.RoundTrip(req)
	if err != nil {
		http.Error(w, err.Error(), http.StatusServiceUnavailable)
		return
	}
	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)
		}
	}
}
func main() {
	var pemPath string
	flag.StringVar(&pemPath, "pem", "server.pem", "path to pem file")
	var keyPath string
	flag.StringVar(&keyPath, "key", "server.key", "path to key file")
	var proto string
	flag.StringVar(&proto, "proto", "http", "Proxy protocol (http or https)")
	flag.Parse()
	if proto != "http" && proto != "https" {
		log.Fatal("Protocol must be either http or https")
	}
	server := &http.Server{
		Addr: ":8888",
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Method == http.MethodConnect {
				handleTunneling(w, r)
			} else {
				handleHTTP(w, r)
			}
		}),
		// Disable HTTP/2.
		TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
	}
	if proto == "http" {
		log.Fatal(server.ListenAndServe())
	} else {
		log.Fatal(server.ListenAndServeTLS(pemPath, keyPath))
	}
}

5分钟用Go语言(Golang)实现一个HTTP代理(Proxy)

我们在软件开发的过程中,会遇到很多需要代理的地方,比如抓包,了解HTTP内容的传输,比如Nginx反向代理等。

以前在Linux下的时候,会安装一个Privoxy把Socket代理转换为HTTP代理,开机启动,也比较方便。但是Mac下使用Brew安装的Privoxy就很难用,所以想试试一个软件搞定Socket和HTTP代理,这样就不用安装一个单独的软件做转换了。

想着就开始做吧,以前基本上没有搞过太多的网络编程,最近也正好在研究Go,正好练练手。

我们这里主要讲使用HTTP/1.1协议中的CONNECT方法建立起来的隧道连接,实现的HTTP Proxy。这种代理的好处就是不用知道客户端请求的数据,只需要原封不动的转发就可以了,对于处理HTTPS的请求就非常方便了,不用解析他的内容,就可以实现代理。

启动代理监听

要想做一个HTTP Proxy,我们需要启动一个服务器,监听一个端口,用于接收客户端的请求。Golang给我们提供了强大的net包供我们使用,我们启动一个代理服务器监听非常方便。

    l, err := net.Listen("tcp", ":8080")    if err != nil {
        log.Panic(err)
    }

监听接收代理请求

启动了代理服务器,就可以开始接受不了代理请求了,有了请求,我们才能做进一步的处理。

    for {
        client, err := l.Accept()        if err != nil {
            log.Panic(err)
        }        go handleClientRequest(client)
    }

Listener接口的Accept方法,会接受客户端发来的连接数据,这是一个阻塞型的方法,如果客户端没有连接数据发来,他就是阻塞等待。接收来的连接数据,会马上交给handleClientRequest方法进行处理,这里使用一个go关键字开一个goroutine的目的是不阻塞客户端的接收,代理服务器可以马上接收下一个连接请求。

解析请求,获取要访问的IP和端口

有了客户端的代理请求了,我们还得从请求里提取客户端要访问的远程主机的IP和端口,这样我们的代理服务器才可以建立和远程主机的连接,代理转发。

HTTP协议的头信息里就包含有我们需要的主机名(IP)和端口信息,并且是明文的,协议很规范,类似于:

CONNECT www.google.com:443 HTTP/1.1
Host: www.google.com:443
Proxy-Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36

可以看到我们需要的在第一行,第一个行的信息以空格分开,第一部分CONNECT是请求方法,这里是CONNECT,除此之外还有GET,POST等,都是HTTP协议的标准方法。

第二部分是URL,https的请求只有host和port,http的请求是一个完成的url,等下会看个样例,就明白了。

第三部是HTTP的协议和版本,这个我们不用太关注。

以上是一个https的请求,我们看下http的:

GET http://www.flysnow.org/ HTTP/1.1
Host: www.flysnow.org
Proxy-Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36

可以看到htt的,没有端口号(默认是80);比https多了schame—http://。

有了分析,下面我们就可以从HTTP头信息中获取请求的url和method信息了。

    var b [1024]byte
    n, err := client.Read(b[:])    if err != nil {
        log.Println(err)        return
    }    var method, host, address string
    fmt.Sscanf(string(b[:bytes.IndexByte(b[:], '\n')]), "%s%s", &method, &host)
    hostPortURL, err := url.Parse(host)    if err != nil {
        log.Println(err)        return
    }

然后需要进一步对url进行解析,获取我们需要的远程服务器信息

    if hostPortURL.Opaque == "443" { //https访问
        address = hostPortURL.Scheme + ":443"
    } else { //http访问
        if strings.Index(hostPortURL.Host, ":") == -1 { //host不带端口, 默认80
            address = hostPortURL.Host + ":80"
        } else {
            address = hostPortURL.Host
        }
    }

这样就完整了获取了要请求服务器的信息,他们可能是以下几种格式

ip:port
hostname:port
domainname:port

就是有可能是ip(v4orv6),有可能是主机名(内网),有可能是域名(dns解析)

代理服务器和远程服务器建立连接

有了远程服务器的信息了,就可以进行拨号建立连接了,有了连接,才可以通信。

    //获得了请求的host和port,就开始拨号吧
    server, err := net.Dial("tcp", address)    if err != nil {
        log.Println(err)        return
    }

数据转发

拨号成功后,就可以进行数据代理传输了

if method == "CONNECT" {
        fmt.Fprint(client, "HTTP/1.1 200 Connection established\r\n")
    } else {
        server.Write(b[:n])
    }    //进行转发
    go io.Copy(server, client)
    io.Copy(client, server)

其中对CONNECT方法有单独的回应,客户端说要建立连接,代理服务器要回应建立好了,然后才可以像HTTP一样请求访问。

完整代码

到这里,我们的代理服务器全部开发完成了,下面是完整的源代码:

package mainimport (    "bytes"
    "fmt"
    "io"
    "log"
    "net"
    "net/url"
    "strings")func main() {
    log.SetFlags(log.LstdFlags|log.Lshortfile)
    l, err := net.Listen("tcp", ":8081")    if err != nil {
        log.Panic(err)
    }    for {
        client, err := l.Accept()        if err != nil {
            log.Panic(err)
        }        go handleClientRequest(client)
    }
}func handleClientRequest(client net.Conn) {    if client == nil {        return
    }    defer client.Close()    var b [1024]byte
    n, err := client.Read(b[:])    if err != nil {
        log.Println(err)        return
    }    var method, host, address string
    fmt.Sscanf(string(b[:bytes.IndexByte(b[:], '\n')]), "%s%s", &method, &host)
    hostPortURL, err := url.Parse(host)    if err != nil {
        log.Println(err)        return
    }    if hostPortURL.Opaque == "443" { //https访问
        address = hostPortURL.Scheme + ":443"
    } else { //http访问
        if strings.Index(hostPortURL.Host, ":") == -1 { //host不带端口, 默认80
            address = hostPortURL.Host + ":80"
        } else {
            address = hostPortURL.Host
        }
    }    //获得了请求的host和port,就开始拨号吧
    server, err := net.Dial("tcp", address)    if err != nil {
        log.Println(err)        return
    }    if method == "CONNECT" {
        fmt.Fprint(client, "HTTP/1.1 200 Connection established\r\n")
    } else {
        server.Write(b[:n])
    }    //进行转发
    go io.Copy(server, client)
    io.Copy(client, server)
}

把源代码编译,放在自己的电脑上,测试一下吧。


以上是关于golang Golang http代理的主要内容,如果未能解决你的问题,请参考以下文章

golang 实现HTTP代理和反向代理

golang如何创建反向代理

[golang] fasthttp 使用http代理

golang 反向代理reverseproxy源码分析

5分钟用Go语言(Golang)实现一个HTTP代理(Proxy)

『GCTT 出品』用不到 100 行的 Golang 代码实现 HTTP(S) 代理