Newer
Older
httpcat / src / httpcat.go
/**
Author:  Mark George <mark.george@otago.ac.nz>
Warranty:  None.  Works for me.  If it doesn't work for you then you already have the source code...
License: WTFPL v2 <http://www.wtfpl.net/txt/copying/>
*/

package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"os"
	"strconv"
)

var (
	port      int
	status    int
	body      string
	entire    bool
	verbose   bool
	server    bool
	uri       string
	accept    string
	separator string
	cors      bool
)

func requestHandler(resp http.ResponseWriter, req *http.Request) {

	if verbose {
		fmt.Printf("Received a %s request to %s\n", req.Method, req.RequestURI)
	}

	// print entire request
	if entire {
		dump, _ := httputil.DumpRequest(req, true)
		fmt.Println(string(dump[:]))

		// print only request body
	} else {
		body, _ := ioutil.ReadAll(req.Body)
		fmt.Println(string(body[:]))
	}

	// cors
	if cors && req.Method == http.MethodOptions {
		if verbose {
			fmt.Println("CORS preflight")
		}

		if origin := req.Header.Get("Origin"); origin != "" {

			resp.Header().Set("Access-Control-Allow-Origin", origin)

			if headers := req.Header.Get("Access-Control-Request-Headers"); headers != "" {
				resp.Header().Set("Access-Control-Allow-Headers", headers)
			}

			if method := req.Header.Get("Access-Control-Request-Method"); method != "" {
				resp.Header().Set("Access-Control-Allow-Method", method)
			}

			resp.WriteHeader(200)

			return

		} else {
			if verbose {
				fmt.Println("Preflight did not contain 'Origin' header!")
			}
		}
	}

	// send response
	if body != "" {
		if status == 204 {
			status = 200
		} // if we have a body then we don't want to return the default 204 status
		resp.WriteHeader(status)
		fmt.Fprintf(resp, body)
	} else {
		resp.WriteHeader(status)
	}

	if separator != "" {
		fmt.Println(separator)
	}
}

func startServer() {
	http.HandleFunc("/", requestHandler)

	if err := http.ListenAndServe(":"+strconv.Itoa(port), nil); err != nil {
		fmt.Fprintf(os.Stderr, "Could not start server.  Is port %d available?\n", port)
	}
}

func sendRequest(uri string) {

	client := http.Client{}

	request, _ := http.NewRequest("GET", uri, nil)

	// add Accept header if required by user
	if accept != "" {
		request.Header.Add("Accept", accept)
	}

	response, err := client.Do(request)

	if err != nil {
		fmt.Fprintln(os.Stderr, err)
	} else {

		// print entire response
		if entire {
			dump, _ := httputil.DumpResponse(response, true)
			fmt.Println(string(dump[:]))

			// print only the response body
		} else {
			body, _ := ioutil.ReadAll(response.Body)
			fmt.Println(string(body[:]))
		}
	}
}

func parseCommandLine() {
	flag.IntVar(&port, "port", 8080, "")
	flag.IntVar(&port, "p", 8080, "")

	flag.IntVar(&status, "response", 204, "")
	flag.IntVar(&status, "r", 204, "")

	flag.StringVar(&body, "body", "", "")
	flag.StringVar(&body, "b", "", "")

	flag.BoolVar(&entire, "entire", false, "")
	flag.BoolVar(&entire, "e", false, "")

	flag.StringVar(&separator, "separator", "", "")
	flag.StringVar(&separator, "s", "", "")

	flag.BoolVar(&verbose, "verbose", false, "")
	flag.BoolVar(&verbose, "v", false, "")

	flag.StringVar(&accept, "accept", "", "")
	flag.StringVar(&accept, "a", "", "")

	flag.BoolVar(&cors, "cors", false, "")
	flag.BoolVar(&cors, "c", false, "")

	serverMode := flag.Bool("server", false, "")
	clientMode := flag.Bool("client", false, "")

	flag.Usage = usage

	flag.Parse()

	if !(*serverMode || *clientMode) || (*serverMode && *clientMode) {
		usage()
		os.Exit(1)
	} else {
		server = *serverMode
	}

	if *clientMode {
		uri = flag.Arg(0)
		if uri == "" {
			usage()
			os.Exit(1)
		}
	}

}

var usage = func() {
	fmt.Fprintf(os.Stderr, "Usage:\n")
	fmt.Fprintf(os.Stderr, "\n")
	fmt.Fprintf(os.Stderr, "  Client mode\n")
	fmt.Fprintf(os.Stderr, "    httpcat -client [options] http://uri-to-send-request-to.com\n")
	fmt.Fprintf(os.Stderr, "\n")
	fmt.Fprintf(os.Stderr, "    Currently only supports GET requests.\n")
	fmt.Fprintf(os.Stderr, "\n")
	fmt.Fprintf(os.Stderr, "  Server mode\n")
	fmt.Fprintf(os.Stderr, "    httpcat -server [options]\n")
	fmt.Fprintf(os.Stderr, "\n")
	fmt.Fprintf(os.Stderr, "  Options (either mode)\n")
	fmt.Fprintf(os.Stderr, "    -entire or -e : Display entire request/response instead of just the body.\n")
	fmt.Fprintf(os.Stderr, "    -verbose or -v : Be verbose.\n")
	fmt.Fprintf(os.Stderr, "\n")
	fmt.Fprintf(os.Stderr, "  Options (client mode only)\n")
	fmt.Fprintf(os.Stderr, "    -accept or -a [accept string] : Adds 'Accept' header to request.\n")
	fmt.Fprintf(os.Stderr, "\n")
	fmt.Fprintf(os.Stderr, "  Options (server mode only)\n")
	fmt.Fprintf(os.Stderr, "    -body or -b [body message] : Body to respond with.  Response code will default to 200.\n")
	fmt.Fprintf(os.Stderr, "    -port or -p [port] : Port to listen on.\n")
	fmt.Fprintf(os.Stderr, "    -response or -r [response code] : Status code to respond with.  Defaults to 204.\n")
	fmt.Fprintf(os.Stderr, "    -cors or -c : Enable Cross Origin Resource Sharing support.\n")
	fmt.Fprintf(os.Stderr, "    -separator or -s [separator string] : Use the provided separator to separate messages.\n")
}

func xmain() {

	parseCommandLine()

	if server {

		// server mode

		if verbose {
			fmt.Printf("Listening on port %d.\n", port)
			if entire {
				fmt.Println("Displaying entire request details.\n")
			} else {
				fmt.Println("Displaying bodies only.\n")
			}
		}

		startServer()

	} else {

		// client mode

		if verbose {
			fmt.Println("Sending GET request to " + uri)
		}
		sendRequest(uri)
	}
}