refactor http proxy request handling and improve performance

This commit significantly optimizes the HTTP proxy implementation by:

1. Replacing standard http.ReadRequest with manual HTTP header parsing
   - Avoids unnecessary allocations from full request parsing
   - Adds 3-second timeout for initial header reading

2. Removing HttpProxyWriter abstraction
   - Directly construct and send rewritten Host header
   - Simplify data forwarding logic

3. Unifying WebSocket and regular HTTP handling
   - Use single read loop for both cases
   - Always use buffer pool for reads

4. Adding proper timeouts
   - Set deadlines for header reading
   - Reset timeout after headers are processed

These changes reduce memory allocations, improve performance, and simplify the proxy logic.

Signed-off-by: Jianhui Zhao <zhaojh329@gmail.com>
This commit is contained in:
Jianhui Zhao
2025-08-10 21:55:00 +08:00
parent 68828c889e
commit d2e73ae6f7
2 changed files with 92 additions and 73 deletions

View File

@@ -92,18 +92,14 @@
const translations = {
en: {
'Device Unavailable': 'Device Unavailable',
'Invalid Request': 'Invalid Request',
'Unauthorized Access': 'Unauthorized Access',
'Device offline message': 'The device is currently offline. Please check the device status and try again.',
'Invalid request message': 'The request is invalid or malformed',
'Unauthorized request message': 'You are not authorized to access this resource. Please check your session and try again.'
},
'zh-CN': {
'Device Unavailable': '设备不可用',
'Invalid Request': '无效请求',
'Unauthorized Access': '未授权访问',
'Device offline message': '设备当前离线请检查设备状态后重试',
'Invalid request message': '请求无效或格式错误',
'Unauthorized request message': '您无权访问此资源请检查您的会话并重试'
}
};
@@ -123,10 +119,6 @@
title = t('Device Unavailable', lang);
message = t('Device offline message', lang);
break;
case 'invalid':
title = t('Invalid Request', lang);
message = t('Invalid request message', lang);
break;
case 'unauthorized':
title = t('Unauthorized Access', lang);
message = t('Unauthorized request message', lang);

157
http.go
View File

@@ -18,6 +18,7 @@ import (
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
@@ -120,30 +121,16 @@ func doHttpProxy(srv *RttyServer, c net.Conn) {
defer logPanic()
defer c.Close()
head := bytebufferpool.Get()
defer bytebufferpool.Put(head)
br := bufio.NewReader(c)
req, err := http.ReadRequest(br)
if err != nil {
ses := httpReadRequest(c, br, head)
if ses == nil {
return
}
cookie, err := req.Cookie("rtty-http-sid")
if err != nil {
log.Debug().Msgf(`not found cookie "rtty-http-sid"`)
sendHTTPErrorResponse(c, "invalid")
return
}
sid := cookie.Value
sesVal, ok := httpProxySessions.Load(sid)
if !ok {
log.Debug().Msgf(`not found httpProxySession "%s"`, sid)
sendHTTPErrorResponse(c, "unauthorized")
return
}
ses := sesVal.(*HttpProxySession)
dev := srv.GetDevice(ses.group, ses.devid)
if dev == nil {
log.Debug().Msgf(`device "%s" group "%s" offline`, ses.devid, ses.group)
@@ -151,16 +138,11 @@ func doHttpProxy(srv *RttyServer, c net.Conn) {
return
}
destAddr, hostHeaderRewrite := genDestAddrAndHost(ses.destaddr, ses.https)
destAddr, host := genDestAddrAndHost(ses.destaddr, ses.https)
hpw := &HttpProxyWriter{
destAddr: destAddr,
hostHeaderRewrite: hostHeaderRewrite,
dev: dev,
https: ses.https,
}
var srcAddr [18]byte
tcpAddr2Bytes(c.RemoteAddr().(*net.TCPAddr), hpw.srcAddr[:])
tcpAddr2Bytes(c.RemoteAddr().(*net.TCPAddr), srcAddr[:])
ctx, cancel := context.WithCancel(ses.ctx)
defer cancel()
@@ -169,38 +151,101 @@ func doHttpProxy(srv *RttyServer, c net.Conn) {
<-ctx.Done()
c.Close()
log.Debug().Msgf("http proxy conn closed: %s", ses)
dev.https.Delete(hpw.srcAddr)
dev.https.Delete(srcAddr)
}()
log.Debug().Msgf("new http proxy conn: %s", ses)
dev.https.Store(hpw.srcAddr, c)
dev.https.Store(srcAddr, c)
req.Host = hostHeaderRewrite
hpw.WriteRequest(req)
// Rewrite the HTTP host
head.WriteString("Host: " + host + "\r\n")
if req.Header.Get("Upgrade") == "websocket" {
hb := httpBufPool.Get().(*HttpBuf)
defer httpBufPool.Put(hb)
sendHttpReq(dev, ses.https, srcAddr[:], destAddr, head.B)
for {
n, err := c.Read(hb.buf)
if err != nil {
return
}
sendHttpReq(dev, ses.https, hpw.srcAddr[:], destAddr, hb.buf[:n])
ses.Expire()
hb := httpBufPool.Get().(*HttpBuf)
defer httpBufPool.Put(hb)
for {
n, err := br.Read(hb.buf)
if err != nil {
return
}
} else {
for {
req, err := http.ReadRequest(br)
sendHttpReq(dev, ses.https, srcAddr[:], destAddr, hb.buf[:n])
ses.Expire()
}
}
func httpReadRequest(conn net.Conn, br *bufio.Reader, head *bytebufferpool.ByteBuffer) *HttpProxySession {
conn.SetReadDeadline(time.Now().Add(3 * time.Second))
// First line: GET /index.html HTTP/1.0
line, err := br.ReadString('\n')
if err != nil {
return nil
}
head.WriteString(line)
sid := ""
for {
conn.SetReadDeadline(time.Now().Add(3 * time.Second))
line, err := br.ReadString('\n')
if err != nil {
return nil
}
if line == "\r\n" {
break
}
k, v, ok := strings.Cut(line, ":")
if !ok {
break
}
k = strings.ToLower(k)
v = strings.TrimLeft(v, " \t")
if k == "host" {
continue
}
head.WriteString(line)
if k == "cookie" {
cookies, err := http.ParseCookie(v)
if err != nil {
return
break
}
hpw.WriteRequest(req)
ses.Expire()
for _, cookie := range cookies {
if cookie.Name == "rtty-http-sid" {
sid = cookie.Value
break
}
}
break
}
}
if sid == "" {
log.Debug().Msgf(`not found cookie "rtty-http-sid"`)
sendHTTPErrorResponse(conn, "unauthorized")
return nil
}
ses, ok := httpProxySessions.Load(sid)
if !ok {
log.Debug().Msgf(`not found httpProxySession "%s"`, sid)
sendHTTPErrorResponse(conn, "unauthorized")
return nil
}
conn.SetReadDeadline(time.Time{})
return ses.(*HttpProxySession)
}
func httpProxyRedirect(a *APIServer, c *gin.Context, group string) {
@@ -382,24 +427,6 @@ func httpProxyVaildAddr(addr string, https bool) (net.IP, uint16, error) {
return ip, uint16(port), nil
}
type HttpProxyWriter struct {
destAddr []byte
srcAddr [18]byte
hostHeaderRewrite string
dev *Device
https bool
}
func (rw *HttpProxyWriter) Write(p []byte) (n int, err error) {
sendHttpReq(rw.dev, rw.https, rw.srcAddr[:], rw.destAddr, p)
return len(p), nil
}
func (rw *HttpProxyWriter) WriteRequest(req *http.Request) {
req.Host = rw.hostHeaderRewrite
req.Write(rw)
}
func sendHTTPErrorResponse(conn net.Conn, errorType string) {
fs, _ := fs.Sub(staticFs, "assets")