The Goproxy extension limiter fails to propagate the context Done signal correctly during HTTPS/CONNECT requests. This causes the extension to block SSL connections, resulting in undesired behavior. Steps to reproduce and potential fixes would be appreciated.
Here's an example of a minimal but sufficient piece of code to reproduce the issue:
package main
import (
"log"
"net/http"
"net/url"
"time"
"crypto/tls"
"context"
"github.com/elazarl/goproxy"
)
const maxConcurrentRequests = 1
func concurrentRequests(limit int) goproxy.ReqHandler {
if limit <= 0 {
return goproxy.FuncReqHandler(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
return req, nil
})
}
limitation := make(chan struct{}, limit)
return goproxy.FuncReqHandler(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
log.Printf("Request received: %s %s - queue size: %d", req.Method, req.URL.String(), len(limitation))
reqCtx, cancel := context.WithTimeout(req.Context(), 10*time.Second)
req = req.WithContext(reqCtx)
select {
case limitation <- struct{}{}:
go func() {
select {
case <-reqCtx.Done():
log.Printf("Request finished or timed out: %s %s", req.Method, req.URL.String())
<-limitation
cancel()
}
}()
return req, nil
case <-reqCtx.Done():
log.Printf("Request timed out while waiting: %s %s", req.Method, req.URL.String())
cancel()
return req, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusServiceUnavailable, "Request timed out while waiting for available slot")
}
})
}
func startProxyServer() {
proxy := goproxy.NewProxyHttpServer()
proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
proxy.Verbose = false
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
DisableKeepAlives: true,
ForceAttemptHTTP2: false,
}
proxy.Tr = tr
proxy.OnRequest().Do(concurrentRequests(maxConcurrentRequests))
log.Println("Starting proxy server on port 8099")
log.Fatal(http.ListenAndServe(":8099", proxy))
}
func simulateClientRequests(proxyURL string) {
proxy, err := url.Parse(proxyURL)
if err != nil {
log.Fatalf("Error parsing proxy URL: %v", err)
}
client := &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
Proxy: http.ProxyURL(proxy),
MaxConnsPerHost: 1,
DisableKeepAlives: true,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
for i := 1; i <= 5; i++ {
go func(i int) {
req, err := http.NewRequest("GET", "https://google.com/get", nil)
if err != nil {
log.Fatalf("Error creating request: %v", err)
}
log.Printf("Client sending request %d", i)
resp, err := client.Do(req)
if err != nil {
log.Printf("Request %d failed: %v", i, err)
return
}
defer resp.Body.Close()
log.Printf("Client received response for request %d: %d", i, resp.StatusCode)
}(i)
time.Sleep(500 * time.Millisecond)
}
}
func main() {
go startProxyServer()
time.Sleep(2 * time.Second)
proxyURL := "http://localhost:8099"
simulateClientRequests(proxyURL)
time.Sleep(500 * time.Second)
}
The Goproxy extension limiter fails to propagate the context Done signal correctly during HTTPS/CONNECT requests. This causes the extension to block SSL connections, resulting in undesired behavior. Steps to reproduce and potential fixes would be appreciated.
Here's an example of a minimal but sufficient piece of code to reproduce the issue: