4

如何使用 GO 实现一个简单的 HTTP(S) PROXY

 2 years ago
source link: https://www.v2ex.com/t/825720
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

V2EX  ›  程序员

如何使用 GO 实现一个简单的 HTTP(S) PROXY

  meiyoumingzi6 · 1 天前 · 986 次点击

想实现一个简单的 proxy,仅作为玩具使用, 当然其实现成的 lib 有很多, 但是目标很明确,学习一下基本原理

  • 是否有其他的代码参考

有的, 有一个 python 版本的, 在 github 上发现的 python proxy code

  • 是否有 GO 代码
package main

import (
	"errors"
	"fmt"
	"github.com/valyala/fasthttp"
	"io"
	"log"
	"net"
	"sync"
)

func main() {

	if err := fasthttp.ListenAndServe(":1234", requestHandler); err != nil {
		log.Fatalf("Error in ListenAndServe: %s", err)
	}
}

func processSocket(conn1, conn2 net.Conn, wg *sync.WaitGroup, s string) {
	defer func() {
		fmt.Println("END", s)
		wg.Done()
	}()
	fmt.Println(s)
	var buf []byte
	buf = make([]byte, 4096)
	i, err := conn1.Read(buf)
	buf = buf[:i]
	if err != nil {
		return
	}
	for {
		fmt.Println(s, string(buf))
		conn2.Write(buf)
		buf = buf[:]
		buf = make([]byte, 2<<10<<10) // 10m
		i, err := conn1.Read(buf)
		buf = buf[:i]
		if err != nil {
			if len(buf) > 0 {
				conn2.Write(buf)
			}
			fmt.Println(s, err)
			if errors.Is(err, io.EOF) {
				break

			}
		}
	}

}

func haddleHTTPS(ctx *fasthttp.RequestCtx) {
	h := string(ctx.Request.Host()) // host:port eg. www.baidu.com:443
	curConn := ctx.Conn()
	curConn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nfoo"))
	fmt.Println("haddle:", h)
	remotConn, err := net.Dial("tcp", h)
	if err != nil {
	}
	var wg sync.WaitGroup
	wg.Add(2)
	go processSocket(curConn, remotConn, &wg, "from local to remote")
	go processSocket(remotConn, curConn, &wg, "from remote to local")

	wg.Wait()

}

func haddleHTTP(ctx *fasthttp.RequestCtx) {
	req := fasthttp.AcquireRequest()
	req.SetRequestURIBytes(ctx.Request.RequestURI())
	//req.Header.SetMethodBytes(ctx.Method())
	req.Header = ctx.Request.Header
	req.SetBody(ctx.Request.Body())
	client := &fasthttp.Client{}
	resp := fasthttp.AcquireResponse()
	client.Do(req, resp)
	body := resp.Body()
	fmt.Println(body)
	ctx.Write(body)
}

func requestHandler(ctx *fasthttp.RequestCtx) {
	method := string(ctx.Method())
	if method == "CONNECT" {
		// https
		haddleHTTPS(ctx)
		return
	}
	// http
	haddleHTTP(ctx)
	return

}
  • 代码是否可以正常工作

不行, http 是没有问题的, https 存在问题

  • 问题日志 /描述
➜  ~ curl https://www.baidu.com -vvv
* Uses proxy env variable https_proxy == 'http://127.0.0.1:1234'
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 1234 (#0)
* allocate connect buffer!
* Establish HTTP proxy tunnel to www.baidu.com:443
> CONNECT www.baidu.com:443 HTTP/1.1
> Host: www.baidu.com:443
> User-Agent: curl/7.64.1
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 200 OK
< Content-Length: 6
* Ignoring Content-Length in CONNECT 200 response
<
* Proxy replied 200 to CONNECT request
* CONNECT phase completed!
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* CONNECT phase completed!
* CONNECT phase completed!
* error:1400410B:SSL routines:CONNECT_CR_SRVR_HELLO:wrong version number
* Closing connection 0
curl: (35) error:1400410B:SSL routines:CONNECT_CR_SRVR_HELLO:wrong version number
  • 是否自行 debug

有的, 发现 local to server 的时候, read 出现了 error, err: ECONNRESET (54) 然后 接下来就 EOF 了, 所以就退出了, 但是拿到这个 error 的时候, curl 就已经结束了, 所以拿到 EOF 也是正常行为, 主要在于不知道为啥会 curl 会断掉

  1. 想知道为什么 curl 会断掉
  2. 有没有什么解决办法[在上述代码中修改]
  3. 或者有其他代码编写方式

第 1 条附言  ·  1 天前

破案了 是因为没有遵循规范, 第一次相应修改成 `"HTTP/1.0 200 Connection Established\r\n\r\n"` 就可以解决了

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK