/** 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) } }