(转) Golang实现HTTP(s)代理服务

声明:内容源自网络,版权归原作者所有。若有侵权请在网页聊天中联系我

原文地址:https://www.perfcode.com/p/how-to-create-a-http-proxy-service.html

http已经逐渐被抛弃,其实应该https,但,从它开始吧。 代理的一种使用场景是在内网,只允许通过代理上网,可以照顾到内网安全,作访问记录、禁入等。

package main
import (
    "io"
    "log"
    "net/http"

)

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() {

    server := &http.Server{
        Addr: "127.0.0.1:8080",
        Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            handleHTTP(w, r)
        }),
    }

    log.Fatal(server.ListenAndServe())
}

一段python代码,用于测试代理服务程序

import requests

url = "http://baidu.com"

proxies = {'http':"127.0.0.1:8080"}

resp = requests.get(url,proxies=proxies)

print(resp.status_code)
print(resp.text)

网上搜索,找到一个库,虽然是两年前的,但这个原本就是以前的技术。代码量也不大,复制到下面。做了一点点修改。
Github: https://github.com/Zartenc/httpsproxy

main.go

package main

import (
	"flag"
	"httpsproxy/httpsserve"
	"log"
	"net"
	"os"
)

var logger = log.New(os.Stderr, "httpsproxy:", log.Llongfile|log.LstdFlags)

func main() {
	var listenAdress string
	flag.StringVar(&listenAdress, "L", "0.0.0.0:8080", "listen address.eg: 127.0.0.1:8080")
	flag.Parse()

	if !checkAdress(listenAdress) {
		logger.Fatal("-L listen address format incorrect.Please check it")
	}

	log.Print("开始服务...")
	httpsserve.Serve(listenAdress)

}

func checkAdress(adress string) bool {
	_, err := net.ResolveTCPAddr("tcp", adress)
	if err != nil {
		return false
	}
	return true

}

httpsserve/server.go

package httpsserve

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"httpsproxy/proxy"
	"log"
	"math/big"
	"net/http"
	"os"
	"time"
)

var logger = log.New(os.Stderr, "httpsproxy:", log.Llongfile|log.LstdFlags)

func Serve(listenAdress string){
	cert, err := genCertificate()
	if err != nil {
		logger.Fatal(err)
	}

	server := &http.Server{
		Addr: listenAdress,
		TLSConfig: 	&tls.Config{Certificates: []tls.Certificate{cert},},
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			proxy.Serve(w, r)
		}),
	}

	logger.Fatal(server.ListenAndServe())

}

func genCertificate() (cert tls.Certificate, err error){
	rawCert, rawKey, err := generateKeyPair()
	if err != nil {
		return
	}
	return tls.X509KeyPair(rawCert, rawKey)

}

func generateKeyPair() (rawCert, rawKey []byte, err error) {
	// Create private key and self-signed certificate
	// Adapted from https://golang.org/src/crypto/tls/generate_cert.go

	priv, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return
	}
	validFor := time.Hour * 24 * 365 * 10 // ten years
	notBefore := time.Now()
	notAfter := notBefore.Add(validFor)
	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	template := x509.Certificate{
		SerialNumber: serialNumber,
		Subject: pkix.Name{
			Organization: []string{"Zarten"},
		},
		NotBefore: notBefore,
		NotAfter:  notAfter,

		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
	}
	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
	if err != nil {
		return
	}

	rawCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
	rawKey = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})

	return
}

proxy/server.go

package proxy

import (
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"time"
)

var logger = log.New(os.Stderr, "httpsproxy:", log.Llongfile|log.LstdFlags)

func Serve(w http.ResponseWriter, r *http.Request) {
	info := fmt.Sprintf("[%7s] %-20s %s", r.Method, r.RemoteAddr, r.RequestURI)
	log.Print(info)
	if r.Method == http.MethodConnect {
		handleHttps(w, r)
	} else {
		handleHttp(w, r)
	}

}

func handleHttps(w http.ResponseWriter, r *http.Request) {
	destConn, err := net.DialTimeout("tcp", r.Host, 60*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
	}

	clientConn, _, err := hijacker.Hijack()
	if err != nil {
		http.Error(w, err.Error(), http.StatusServiceUnavailable)
	}
	go transfer(destConn, clientConn)
	go transfer(clientConn, destConn)

}

func handleHttp(w http.ResponseWriter, r *http.Request) {
	resp, err := http.DefaultTransport.RoundTrip(r)
	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 transfer(destination io.WriteCloser, source io.ReadCloser) {
	defer destination.Close()
	defer source.Close()
	io.Copy(destination, source)
}

func copyHeader(dst, src http.Header) {
	for k, vv := range src {
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}

其实这里关键是通过HiJack进行接管,完成数据中转。
看看另一个基本相同的示例。

package main
 
 import (
  "httpsProxy/handle"
  "net/http"
 )
 
 func main() {
  server := &http.Server{Addr: "127.0.0.1:8090", Handler: &handle.HttpsHandle{}}
  server.ListenAndServe()
 } 

 // ---------------------------------

 package handle
 
 import (
 	"fmt"
 	"io"
 	"net"
 	"net/http"
 )
 
 type HttpsHandle struct {
 }
 
 func (h *HttpsHandle) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 	if req.Method == "CONNECT" {
 		fmt.Print("Connect请求")
 	} else {
 		fmt.Println("异常")
 		return
 	}
 	remote, err := net.Dial("tcp", req.URL.Host)
 	if err != nil {
 		fmt.Println("连接目标错误")
 		return
 	}
 
 	hj, _ := rw.(http.Hijacker)
 	client, _, err := hj.Hijack()
 	if err != nil {
 		fmt.Print("获取tcp连接错误")
 	}
 	var HTTP200 = []byte("HTTP/1.1 200 Connection Established\r\n\r\n")
 	client.Write(HTTP200)
 	go io.Copy(remote, client)
 	go io.Copy(client, remote)
 
 }

2024.3.18

通过一个库,快速实现http/https代理。

package main

import (
    "github.com/elazarl/goproxy"
    "log"
    "net/http"
)

func main() {
    proxy := goproxy.NewProxyHttpServer()
    proxy.Verbose = true
    log.Fatal(http.ListenAndServe(":8080", proxy))
}

相关文章