From be610af56ee7fbb2764781ec078531cf4c92bf7a Mon Sep 17 00:00:00 2001 From: shadowy-pycoder <35629483+shadowy-pycoder@users.noreply.github.com> Date: Sat, 7 Jun 2025 06:24:38 +0300 Subject: [PATCH 1/2] improved logging messages, std logging from http pkg now matches zerolog format --- gohpts.go | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/gohpts.go b/gohpts.go index d0b88e0..e5565d5 100644 --- a/gohpts.go +++ b/gohpts.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "fmt" "io" + "log" "net" "net/http" "os" @@ -182,17 +183,17 @@ func (p *proxyApp) handleForward(w http.ResponseWriter, r *http.Request) { case <-time.Tick(flushTimeout): err := rc.Flush() if err != nil { - p.logger.Error().Err(err) + p.logger.Error().Err(err).Msg("Failed flushing buffer") return } err = rc.SetReadDeadline(time.Now().Add(readTimeout)) if err != nil { - p.logger.Error().Err(err) + p.logger.Error().Err(err).Msg("Failed setting read deadline") return } err = rc.SetWriteDeadline(time.Now().Add(writeTimeout)) if err != nil { - p.logger.Error().Err(err) + p.logger.Error().Err(err).Msg("Failed setting write deadline") return } case <-done: @@ -249,12 +250,14 @@ func (p *proxyApp) handleTunnel(w http.ResponseWriter, r *http.Request) { if isLocalAddress(r.Host) { dstConn, err = net.DialTimeout("tcp", r.Host, timeout) if err != nil { + p.logger.Error().Err(err).Msgf("Failed connecting to %s", r.Host) http.Error(w, err.Error(), http.StatusServiceUnavailable) return } } else { dstConn, err = p.sockDialer.Dial("tcp", r.Host) if err != nil { + p.logger.Error().Err(err).Msgf("Failed connecting to %s", r.Host) http.Error(w, err.Error(), http.StatusServiceUnavailable) return } @@ -264,11 +267,13 @@ func (p *proxyApp) handleTunnel(w http.ResponseWriter, r *http.Request) { hj, ok := w.(http.Hijacker) if !ok { + p.logger.Error().Msg("webserver doesn't support hijacking") http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError) return } srcConn, _, err := hj.Hijack() if err != nil { + p.logger.Error().Err(err).Msg("Failed hijacking src connection") http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -335,12 +340,30 @@ type Config struct { CertFile string KeyFile string } +type logWriter struct { +} + +func (writer logWriter) Write(bytes []byte) (int, error) { + return fmt.Print(fmt.Sprintf("%s | ERROR | %s", time.Now().Format(time.RFC3339), string(bytes))) +} + +type jsonLogWriter struct { +} + +func (writer jsonLogWriter) Write(bytes []byte) (int, error) { + return fmt.Print(fmt.Sprintf("{\"level\":\"error\",\"time\":\"%s\",\"message\":\"%s\"}\n", + time.Now().Format(time.RFC3339), strings.TrimRight(string(bytes), "\n"))) +} func New(conf *Config) *proxyApp { var logger zerolog.Logger if conf.Json { + log.SetFlags(0) + log.SetOutput(new(jsonLogWriter)) logger = zerolog.New(os.Stdout).With().Timestamp().Logger() } else { + log.SetFlags(0) + log.SetOutput(new(logWriter)) output := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339, NoColor: true} output.FormatLevel = func(i interface{}) string { return strings.ToUpper(fmt.Sprintf("| %-6s|", i)) From 25f2829b809483f47f995c717f660c1e337e4933 Mon Sep 17 00:00:00 2001 From: shadowy-pycoder <35629483+shadowy-pycoder@users.noreply.github.com> Date: Sat, 7 Jun 2025 07:01:36 +0300 Subject: [PATCH 2/2] added graceful shutdown to server, bumped to v1.4.1 --- README.md | 2 +- gohpts.go | 27 ++++++++++++++++++++++----- version.go | 2 +- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1555bb7..ebc576a 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ You can download the binary for your platform from [Releases](https://github.com Example: ```shell -HPTS_RELEASE=v1.3.4; wget -v https://github.com/shadowy-pycoder/go-http-proxy-to-socks/releases/download/$HPTS_RELEASE/gohpts-$HPTS_RELEASE-linux-amd64.tar.gz -O gohpts && tar xvzf gohpts && mv -f gohpts-$HPTS_RELEASE-linux-amd64 gohpts && gohpts -h +HPTS_RELEASE=v1.4.1; wget -v https://github.com/shadowy-pycoder/go-http-proxy-to-socks/releases/download/$HPTS_RELEASE/gohpts-$HPTS_RELEASE-linux-amd64.tar.gz -O gohpts && tar xvzf gohpts && mv -f gohpts-$HPTS_RELEASE-linux-amd64 gohpts && gohpts -h ``` Alternatively, you can install it using `go install` command (requires Go [1.24](https://go.dev/doc/install) or later): diff --git a/gohpts.go b/gohpts.go index e5565d5..8ae1185 100644 --- a/gohpts.go +++ b/gohpts.go @@ -1,6 +1,7 @@ package gohpts import ( + "context" "crypto/tls" "fmt" "io" @@ -8,6 +9,7 @@ import ( "net" "net/http" "os" + "os/signal" "slices" "strings" "sync" @@ -174,7 +176,7 @@ func (p *proxyApp) handleForward(w http.ResponseWriter, r *http.Request) { } } defer resp.Body.Close() - done := make(chan struct{}) + done := make(chan bool) if chunked { rc := http.NewResponseController(w) go func() { @@ -217,7 +219,6 @@ func (p *proxyApp) handleForward(w http.ResponseWriter, r *http.Request) { n, err := io.Copy(w, resp.Body) if err != nil { p.logger.Error().Err(err).Msgf("Error during Copy() %s: %s", r.URL.String(), err) - done <- struct{}{} close(done) return } @@ -240,7 +241,6 @@ func (p *proxyApp) handleForward(w http.ResponseWriter, r *http.Request) { w.Header().Add(key, v) } } - done <- struct{}{} close(done) } @@ -318,16 +318,33 @@ func (p *proxyApp) handler() http.HandlerFunc { } func (p *proxyApp) Run() { + done := make(chan bool) + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + go func() { + <-quit + p.logger.Info().Msg("Server is shutting down...") + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + + defer cancel() + p.httpServer.SetKeepAlivesEnabled(false) + if err := p.httpServer.Shutdown(ctx); err != nil { + p.logger.Fatal().Err(err).Msg("Could not gracefully shutdown the server") + } + close(done) + }() p.httpServer.Handler = p.handler() if p.certFile != "" && p.keyFile != "" { - if err := p.httpServer.ListenAndServeTLS(p.certFile, p.keyFile); err != nil { + if err := p.httpServer.ListenAndServeTLS(p.certFile, p.keyFile); err != nil && err != http.ErrServerClosed { p.logger.Fatal().Err(err).Msg("Unable to start HTTPS server") } } else { - if err := p.httpServer.ListenAndServe(); err != nil { + if err := p.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { p.logger.Fatal().Err(err).Msg("Unable to start HTTP server") } } + <-done + p.logger.Info().Msg("Server stopped") } type Config struct { diff --git a/version.go b/version.go index e6ebbfc..3f31143 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package gohpts -const Version string = "gohpts v1.4.0" +const Version string = "gohpts v1.4.1"