Skip to content

Instantly share code, notes, and snippets.

@afdalwahyu
Last active February 18, 2024 02:05
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save afdalwahyu/d2d8374879ecdeea2bca92c97056efee to your computer and use it in GitHub Desktop.
Save afdalwahyu/d2d8374879ecdeea2bca92c97056efee to your computer and use it in GitHub Desktop.
golang http proxy server CONNECT method
package main
import (
"bufio"
"encoding/base64"
"flag"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"strings"
"time"
)
func transfer(destination io.WriteCloser, source io.ReadCloser) {
defer destination.Close()
defer source.Close()
io.Copy(destination, source)
}
func main() {
host := flag.String("host", ":80", "host proxy server")
auth := flag.String("auth", "", "authentication to for client to connect proxy, ex: username:password")
flag.Parse()
listener, err := net.Listen("tcp", *host)
if err != nil {
log.Fatal(err)
}
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal(err)
}
go func(c net.Conn) {
br := bufio.NewReader(c)
req, err := http.ReadRequest(br)
if err != nil {
log.Println("buffer: ", err)
return
}
if req.Method == http.MethodConnect {
if *auth != "" {
clientAuth := req.Header.Get("Proxy-Authorization")
if clientAuth == "" {
response := &http.Response{
StatusCode: http.StatusProxyAuthRequired,
ProtoMajor: 1,
ProtoMinor: 1,
}
response.Write(c)
c.Close()
return
}
serverAuth := "Basic " + base64.StdEncoding.EncodeToString([]byte(*auth))
if clientAuth != serverAuth {
response := &http.Response{
StatusCode: http.StatusUnauthorized,
ProtoMajor: 1,
ProtoMinor: 1,
}
response.Write(c)
c.Close()
return
}
}
response := &http.Response{
StatusCode: 200,
ProtoMajor: 1,
ProtoMinor: 1,
}
response.Write(c)
destConn, err := net.DialTimeout("tcp", req.URL.Host, 10*time.Second)
if err != nil {
response := &http.Response{
StatusCode: http.StatusRequestTimeout,
ProtoMajor: 1,
ProtoMinor: 1,
}
response.Write(c)
return
}
go transfer(destConn, c)
go transfer(c, destConn)
} else {
response := &http.Response{
StatusCode: http.StatusRequestTimeout,
ProtoMajor: 1,
ProtoMinor: 1,
Body: ioutil.NopCloser(strings.NewReader("hello world")),
}
response.Write(c)
c.Close()
return
}
}(conn)
}
}
@mpenkov
Copy link

mpenkov commented Feb 18, 2024

Your transfer function closes the input streams after it exits. You're calling the transfer function twice in separate goroutines. The faster goroutine will end up closing both streams, so the slower goroutine will fail, won't it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment