Skip to content

Instantly share code, notes, and snippets.

@owulveryck
Last active July 26, 2023 17:00
Show Gist options
  • Save owulveryck/d8d4e37cb4a905b70b080d441acf9b9a to your computer and use it in GitHub Desktop.
Save owulveryck/d8d4e37cb4a905b70b080d441acf9b9a to your computer and use it in GitHub Desktop.
http proxy that logs...
package main
import (
"flag"
"fmt"
"io"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"time"
"github.com/kelseyhightower/envconfig"
)
type endpoint struct {
Host string `default:"http://localhost:8080" required:"true"`
Bind string `default:":8181" required:"true"`
}
// Set the proxied request's host to the destination host (instead of the
// source host). e.g. http://foo.com proxying to http://bar.com will ensure
// that the proxied requests appear to be coming from http://bar.com
//
// For both this function and queryCombiner (below), we'll be wrapping a
// Handler with our own HandlerFunc so that we can do some intermediate work
func sameHost(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.Host = r.URL.Host
next.ServeHTTP(w, r)
})
}
// logMdw middleware
func logMdw(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rlogger := &requestLogger{
r: r.Body,
payload: make([]byte, 0),
}
r.Body = rlogger
start := time.Now()
l := &responseLogger{
w: w,
output: make([]byte, 0),
}
next.ServeHTTP(l, r)
elapsed := time.Since(start)
ms := elapsed / time.Millisecond
nsec := elapsed % time.Millisecond
err := r.ParseForm()
if err != nil {
log.Println(err)
}
fmt.Printf("[%v] \"%v %v %v %v\" %v %v \"%v\" \"%v\" **%v/%v\n==> Payload: %v\n==> Output: %v\n",
time.Now().Format("02/Jan/2006:15:04:05 -0700"),
r.Method,
r.URL,
r.Form,
r.Proto,
l.status,
l.size,
r.Referer(),
r.UserAgent(),
float64(ms)+float64(nsec)/1e9,
elapsed,
string(rlogger.payload),
string(l.output),
)
})
}
type requestLogger struct {
r io.ReadCloser
payload []byte
}
func (r *requestLogger) Read(p []byte) (int, error) {
n, err := r.r.Read(p)
r.payload = append(r.payload, p[:n]...)
return n, err
}
func (r *requestLogger) Close() error {
return r.r.Close()
}
type responseLogger struct {
w http.ResponseWriter
output []byte
status int
size int
}
func (l *responseLogger) Write(b []byte) (int, error) {
l.output = append(l.output, b...)
size, err := l.w.Write(b)
l.size += size
return size, err
}
func (l *responseLogger) Header() http.Header {
return l.w.Header()
}
func (l *responseLogger) WriteHeader(s int) {
l.w.WriteHeader(s)
l.status = s
}
func main() {
var ep endpoint
usage := flag.Bool("h", false, "display help and exit")
flag.Parse()
if *usage == true {
envconfig.Usage("ENDPOINT", &ep)
os.Exit(0)
}
err := envconfig.Process("ENDPOINT", &ep)
if err != nil {
log.Fatal(err)
}
// pull the root url we're proxying to from an environment variable.
serverURL, err := url.Parse(ep.Host)
if err != nil {
log.Fatal("URL failed to parse", err)
}
// initialize our reverse proxy
reverseProxy := httputil.NewSingleHostReverseProxy(serverURL)
// wrap that proxy with our sameHost function
singleHosted := sameHost(reverseProxy)
proxy := logMdw(singleHosted)
log.Println("Listening on ", ep.Bind)
log.Fatal(http.ListenAndServe(ep.Bind, proxy))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment