9mlcy7 2024-05-14 11:24 采纳率: 0%
浏览 4
已结题

关于Go中io.Reader问题

在 parseHeaders 方法中,如果使用下面的方法拿请求头的数据( req, err := http.ReadRequest(reader)),就会导致一直请求中,但是如果把两个变量写死就不会出现问题

reader := bufio.NewReader(conn)
req, err := http.ReadRequest(reader)
if err != nil {
return "", "", err
}

realIP := req.Header.Get("X-Real-IP")
serverAddr := req.Header.Get("X-Server-Addr")

PS:透传的 table 、回环啥的都配置好了

完整代码如下:

package main

import (
    "bufio"
    "fmt"
    "io"
    "log"
    "net"
    "net/http"
    "os"
    "strconv"
    "syscall"
)

func main() {
    listen, err := net.Listen("tcp", "0.0.0.0:9920")
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("waiting for incomming connection...")
    defer listen.Close()

    for {
        conn, err := listen.Accept()
        if err != nil {
            log.Println(err)
            continue
        }
        go clientHandler(conn)
    }
}

func clientHandler(client net.Conn) {
    log.Printf("proxy %s accepted", client.RemoteAddr().String())
    defer client.Close()

    //获取需要透代的客户端IP和服务端IP
    clientIP, serverAddr, err := parseHeaders(client)
    if err != nil {
        log.Println(err)
        return
    }
    log.Printf("clientIP: %s serverAddr: %s", clientIP, serverAddr)

    //建立透明连接
    server, err := createTransparentSocket(clientIP, serverAddr)
    if err != nil {
        log.Println(err)
        return
    }
    defer server.Close()

    streamCopy := func(dst io.Writer, src io.Reader, direction string) {
        n, _ := read2Write(dst, src)
        log.Printf("%s %d bytes copied", direction, n)
        client.Close()
        server.Close()
    }

    go streamCopy(client, server, "server to client")
    streamCopy(server, client, "client to server")
}

func createTransparentSocket(clientIP, serverAddr string) (net.Conn, error) {
    var socketFD int
    var file *os.File
    var server net.Conn
    var errorPrefix string
    var err error

    // Create transparent socket
    if socketFD, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP); err != nil {
        errorPrefix = "syscall.socket()"
        goto ReturnError
    }

    // Set the send timeout, here set to 3 seconds
    if err = syscall.SetsockoptTimeval(socketFD, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Sec: 3}); err != nil {
        errorPrefix = "syscall.SetsockoptTimeval()"
        goto CloseSocket
    }

    // Set the transparent transmission flag, allowing binding to IP addresses already in use by other processes
    if err = syscall.SetsockoptInt(socketFD, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
        errorPrefix = "syscall.SetsockoptInt()"
        goto CloseSocket
    }

    // Use client's IP address as socket local address
    if err = syscall.Bind(socketFD, parseClientIP(clientIP)); err != nil {
        errorPrefix = "syscall.Bind()"
        goto CloseSocket
    }

    // Connect to server
    if err = syscall.Connect(socketFD, parseServerAddr(serverAddr)); err != nil && err.Error() != "operation now in progress" {
        errorPrefix = "syscall.Connect()"
        goto CloseSocket
    }

    file = os.NewFile(uintptr(socketFD), serverAddr)
    server, err = net.FileConn(file)
    if err == nil {
        // net.FileConn() has already duplicated this file descriptor
        syscall.Close(socketFD)
        return server, nil
    }
    errorPrefix = "net.FileConn()"
CloseSocket:
    syscall.Close(socketFD)
ReturnError:
    return nil, fmt.Errorf("%s: %s", errorPrefix, err)
}

func read2Write(dst io.Writer, src io.Reader) (written int64, err error) {
    if wt, ok := src.(io.WriterTo); ok {
        return wt.WriteTo(dst)
    }
    if rt, ok := dst.(io.ReaderFrom); ok {
        return rt.ReadFrom(src)
    }
    buf := make([]byte, 4096)
    return io.CopyBuffer(dst, src, buf)
}

func parseHeaders(conn net.Conn) (string, string, error) {
    reader := bufio.NewReader(conn)
    req, err := http.ReadRequest(reader)
    if err != nil {
        return "", "", err
    }

    realIP := req.Header.Get("X-Real-IP")
    serverAddr := req.Header.Get("X-Server-Addr")

    //realIP := "192.168.0.126"
    //serverAddr := "192.168.0.135:8000"

    if realIP == "" || serverAddr == "" {
        return "", "", fmt.Errorf("missing required headers")
    }

    return realIP, serverAddr, nil
}

func parseClientIP(clientIP string) syscall.Sockaddr {
    ip := net.ParseIP(clientIP).To4()
    return &syscall.SockaddrInet4{
        // 客户端端口随机
        Port: 0,
        Addr: [4]byte{ip[0], ip[1], ip[2], ip[3]},
    }
}

func parseServerAddr(serverAddr string) syscall.Sockaddr {
    host, port, _ := net.SplitHostPort(serverAddr)
    ip := net.ParseIP(host).To4()
    destinationPort, _ := strconv.Atoi(port)
    return &syscall.SockaddrInet4{
        Port: destinationPort,
        Addr: [4]byte{ip[0], ip[1], ip[2], ip[3]},
    }
}
  • 写回答

3条回答 默认 最新

  • 笑面虎 2024-05-14 11:24
    关注

    以下内容由CHATGPT及笑面虎共同生成、有用望采纳:


    【您想要解决的问题】: 您提问的目的是解决在使用 Go 语言编写的 HTTP 代理服务器中遇到的一个问题。具体来说,您在 parseHeaders 方法中尝试从客户端连接中读取 HTTP 请求头,但是遇到了一个阻塞问题,导致程序无法继续执行。当您将某些变量写死(硬编码)时,问题消失,这表明问题可能与动态读取请求头有关。

    【问题出现原因】: 问题出现的可能原因有几个,包括但不限于:

    1. 客户端发送的 HTTP 请求头格式不正确或不完整,导致 http.ReadRequest(reader) 无法正确解析。
    2. 网络延迟或连接问题导致客户端发送的请求头数据未能及时到达服务器。
    3. bufio.NewReader(conn) 创建的 reader 可能没有正确地读取到来自 conn 的数据。

    【问题解决方案】: 为了解决这个问题,您可以尝试以下几个步骤:

    1. 确保客户端发送的 HTTP 请求头格式正确且完整。
    2. 在读取请求头之前,添加超时处理,以避免无限期地等待数据。
    3. 检查网络连接,确保客户端和服务器之间的通信畅通无阻。

    【提供代码】: 以下是修改后的 parseHeaders 函数,增加了超时处理:

    func parseHeaders(conn net.Conn) (string, string, error) {
    	conn.SetReadDeadline(time.Now().Add(5 * time.Second)) // 设置5秒的读取超时
    	reader := bufio.NewReader(conn)
    	req, err := http.ReadRequest(reader)
    	conn.SetReadDeadline(time.Time{}) // 清除超时设置,以便后续操作
    	if err != nil {
    		return "", "", err
    	}
    
    	realIP := req.Header.Get("X-Real-IP")
    	serverAddr := req.Header.Get("X-Server-Addr")
    
    	if realIP == "" || serverAddr == "" {
    		return "", "", fmt.Errorf("missing required headers")
    	}
    
    	return realIP, serverAddr, nil
    }
    

    【代码运行方式】: 您需要在一台支持 Go 语言的开发环境中运行这段代码。首先,将上述代码保存为 .go 文件,然后使用 go run 命令来运行程序。

    【代码预期运行结果】: 如果代码正确运行,并且客户端发送了格式正确且完整的 HTTP 请求头,那么程序将能够成功读取请求头中的 X-Real-IPX-Server-Addr 字段,而不会出现阻塞。

    【推荐相关链接】:

    评论 编辑记录

报告相同问题?

问题事件

  • 已结题 (查看结题原因) 5月14日
  • 修改了问题 5月14日
  • 创建了问题 5月14日

悬赏问题

  • ¥15 Kylin-Desktop-V10-GFB-Release-JICAI_02- 2207-Build14-ARM64.iso有没有这个版本的系统啊
  • ¥15 能不能通过蓝牙将传感器数据传送到手机上
  • ¥20 100元python和数据科学实验项目
  • ¥15 根据时间在调用出列表
  • ¥15 FastReport.community 编辑,table只显示“每页”最顶部和底部横线
  • ¥15 R 包chipseeker 安装失败
  • ¥15 Veeam Backup & Replication 9.5 还原问题
  • ¥15 vue-print-nb
  • ¥15 winfrom的datagridview下拉框变成了黑色,渲染不成功
  • ¥20 利用ntfy实现短信推送