Newer
Older
httpcat / src / proxy.go
/**
  Author: Mark George <mark.george@otago.ac.nz>
  License: Zero-Clause BSD License
*/

package main

import (
	"fmt"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strconv"
)

type ProxyCommand struct {
	Port   int    `short:"p" long:"port" description:"The port that the proxy listens on." required:"true"`
	Target string `short:"t" long:"target" description:"The URL for the target web server that the proxy forwards requests to." required:"true"`
}

func (opts *ProxyCommand) Execute(args []string) error {
	fmt.Println("Proxying port " + strconv.Itoa(opts.Port) + " to " + opts.Target)

	proxy, err := CreateProxy(opts.Target)

	if err != nil {
		panic(err)
	}

	http.HandleFunc("/", RequestHandler(proxy))
	if err := http.ListenAndServe(":"+strconv.Itoa(opts.Port), nil); err != nil {
		panic(err)
	}

	return nil
}

func CreateProxy(target string) (*httputil.ReverseProxy, error) {

	url, err := url.Parse(target)

	// bad target URL
	if err != nil {
		return nil, err
	}

	proxy := httputil.NewSingleHostReverseProxy(url)

	OriginalDirector := proxy.Director

	proxy.Director = func(req *http.Request) {
		FixHeaders(url, req)

		showRequest(req)

		// send request to target
		OriginalDirector(req)
	}

	proxy.ModifyResponse = func(rsp *http.Response) error {
		showResponse(rsp)
		return nil
	}

	proxy.ErrorHandler = func(rsp http.ResponseWriter, req *http.Request, err error) {
		fmt.Println("Error sending request to target server:")
		fmt.Println("   " + err.Error())
	}

	return proxy, nil
}

func FixHeaders(url *url.URL, req *http.Request) {
	// TODO this may be necessary for proxying HTTPS (need to test)
	req.URL.Host = url.Host
	req.URL.Scheme = url.Scheme
	req.Host = url.Host
}

func RequestHandler(proxy *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		proxy.ServeHTTP(w, r)
	}
}