Skip to content

Commit b0fb7fb

Browse files
Added basic support for chunked responses
1 parent b34dbbd commit b0fb7fb

File tree

1 file changed

+90
-21
lines changed

1 file changed

+90
-21
lines changed

gohpts.go

Lines changed: 90 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net"
88
"net/http"
99
"os"
10+
"slices"
1011
"strings"
1112
"sync"
1213
"time"
@@ -19,6 +20,7 @@ const (
1920
readTimeout time.Duration = 10 * time.Second
2021
writeTimeout time.Duration = 10 * time.Second
2122
timeout time.Duration = 10 * time.Second
23+
flushTimeout time.Duration = 10 * time.Millisecond
2224
kbSize int64 = 1000
2325
)
2426

@@ -84,12 +86,40 @@ func isLocalAddress(addr string) bool {
8486

8587
type proxyApp struct {
8688
httpServer *http.Server
87-
sockServer *http.Client
89+
sockClient *http.Client
8890
httpClient *http.Client
8991
sockDialer proxy.Dialer
9092
logger *zerolog.Logger
9193
}
9294

95+
func (p *proxyApp) doReq(w http.ResponseWriter, r *http.Request, socks bool) *http.Response {
96+
var (
97+
resp *http.Response
98+
err error
99+
msg string
100+
client *http.Client
101+
)
102+
if socks {
103+
client = p.sockClient
104+
msg = "Connection to SOCKS5 server failed"
105+
} else {
106+
client = p.httpClient
107+
msg = "Connection failed"
108+
}
109+
resp, err = client.Do(r)
110+
if err != nil {
111+
p.logger.Error().Err(err).Msg(msg)
112+
w.WriteHeader(http.StatusServiceUnavailable)
113+
return nil
114+
}
115+
if resp == nil {
116+
p.logger.Error().Msg(msg)
117+
w.WriteHeader(http.StatusServiceUnavailable)
118+
return nil
119+
}
120+
return resp
121+
}
122+
93123
func (p *proxyApp) handleForward(w http.ResponseWriter, r *http.Request) {
94124

95125
req, err := http.NewRequest(r.Method, r.URL.String(), r.Body)
@@ -106,33 +136,69 @@ func (p *proxyApp) handleForward(w http.ResponseWriter, r *http.Request) {
106136
appendHostToXForwardHeader(req.Header, clientIP)
107137
}
108138
var resp *http.Response
139+
var chunked bool
140+
p.httpClient.Timeout = timeout
141+
p.sockClient.Timeout = timeout
109142
if isLocalAddress(r.Host) {
110-
resp, err = p.httpClient.Do(req)
111-
if err != nil {
112-
p.logger.Error().Err(err).Msg("Connection failed")
113-
w.WriteHeader(http.StatusServiceUnavailable)
114-
return
115-
}
143+
resp = p.doReq(w, req, false)
116144
if resp == nil {
117-
p.logger.Error().Err(err).Msg("Connection failed")
118-
w.WriteHeader(http.StatusServiceUnavailable)
119145
return
120146
}
121-
} else {
122-
resp, err = p.sockServer.Do(req)
123-
if err != nil {
124-
p.logger.Error().Err(err).Msg("Connection to SOCKS5 server failed")
125-
w.WriteHeader(http.StatusServiceUnavailable)
126-
return
147+
if slices.Contains(resp.TransferEncoding, "chunked") {
148+
chunked = true
149+
p.httpClient.Timeout = 0
150+
p.sockClient.Timeout = 0
151+
resp.Body.Close()
152+
resp = p.doReq(w, req, false)
153+
if resp == nil {
154+
return
155+
}
127156
}
157+
} else {
158+
resp = p.doReq(w, req, true)
128159
if resp == nil {
129-
p.logger.Error().Err(err).Msg("Connection to SOCKS5 server failed")
130-
w.WriteHeader(http.StatusServiceUnavailable)
131160
return
132161
}
162+
if slices.Contains(resp.TransferEncoding, "chunked") {
163+
chunked = true
164+
p.httpClient.Timeout = 0
165+
p.sockClient.Timeout = 0
166+
resp.Body.Close()
167+
resp = p.doReq(w, req, true)
168+
if resp == nil {
169+
return
170+
}
171+
}
133172
}
134173
defer resp.Body.Close()
135-
174+
done := make(chan struct{})
175+
if chunked {
176+
rc := http.NewResponseController(w)
177+
go func() {
178+
for {
179+
select {
180+
case <-time.Tick(flushTimeout):
181+
err := rc.Flush()
182+
if err != nil {
183+
p.logger.Error().Err(err)
184+
return
185+
}
186+
err = rc.SetReadDeadline(time.Now().Add(readTimeout))
187+
if err != nil {
188+
p.logger.Error().Err(err)
189+
return
190+
}
191+
err = rc.SetWriteDeadline(time.Now().Add(writeTimeout))
192+
if err != nil {
193+
p.logger.Error().Err(err)
194+
return
195+
}
196+
case <-done:
197+
return
198+
}
199+
}
200+
}()
201+
}
136202
delConnectionHeaders(resp.Header)
137203
delHopHeaders(resp.Header)
138204
copyHeader(w.Header(), resp.Header)
@@ -149,7 +215,12 @@ func (p *proxyApp) handleForward(w http.ResponseWriter, r *http.Request) {
149215
} else {
150216
written = fmt.Sprintf("%dKB", n/kbSize)
151217
}
218+
if chunked {
219+
written = fmt.Sprintf("%s - chunked", written)
220+
}
152221
p.logger.Debug().Msgf("%s - %s - %s - %d - %s", r.Proto, r.Method, r.Host, resp.StatusCode, written)
222+
done <- struct{}{}
223+
close(done)
153224
}
154225

155226
func (p *proxyApp) handleTunnel(w http.ResponseWriter, r *http.Request) {
@@ -268,7 +339,6 @@ func New(conf *Config) *proxyApp {
268339
CheckRedirect: func(req *http.Request, via []*http.Request) error {
269340
return http.ErrUseLastResponse
270341
},
271-
Timeout: timeout,
272342
}
273343
hs := &http.Server{
274344
Addr: conf.AddrHTTP,
@@ -286,9 +356,8 @@ func New(conf *Config) *proxyApp {
286356
CheckRedirect: func(req *http.Request, via []*http.Request) error {
287357
return http.ErrUseLastResponse
288358
},
289-
Timeout: timeout,
290359
}
291360
logger.Info().Msgf("SOCKS5 Proxy: %s", conf.AddrSOCKS)
292361
logger.Info().Msgf("HTTP Proxy: %s", conf.AddrHTTP)
293-
return &proxyApp{httpServer: hs, sockServer: socks, httpClient: hc, sockDialer: dialer, logger: &logger}
362+
return &proxyApp{httpServer: hs, sockClient: socks, httpClient: hc, sockDialer: dialer, logger: &logger}
294363
}

0 commit comments

Comments
 (0)