Repository: davecheney/httpstat
Branch: master
Commit: c64e6dee9402
Files: 9
Total size: 20.5 KB
Directory structure:
gitextract__z7b9of5/
├── .github/
│ └── workflows/
│ └── push.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── go.mod
├── go.sum
├── main.go
└── main_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/push.yml
================================================
on:
push:
branches:
- master
pull_request:
branches:
- master
name: Push
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: Test
run: go test -race ./...
- name: Vet
run: go vet ./...
- name: Mod verify
run: go mod verify
================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
httpstat
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2016 Dave Cheney
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
TARGETS = linux-386 linux-amd64 linux-arm linux-arm64 darwin-amd64 windows-386 windows-amd64
COMMAND_NAME = httpstat
PACKAGE_NAME = github.com/davecheney/$(COMMAND_NAME)
LDFLAGS = -ldflags=-X=main.version=$(VERSION)
OBJECTS = $(patsubst $(COMMAND_NAME)-windows-amd64%,$(COMMAND_NAME)-windows-amd64%.exe, $(patsubst $(COMMAND_NAME)-windows-386%,$(COMMAND_NAME)-windows-386%.exe, $(patsubst %,$(COMMAND_NAME)-%-v$(VERSION), $(TARGETS))))
release: check-env $(OBJECTS) ## Build release binaries (requires VERSION)
clean: check-env ## Remove release binaries
rm $(OBJECTS)
$(OBJECTS): $(wildcard *.go)
env GOOS=`echo $@ | cut -d'-' -f2` GOARCH=`echo $@ | cut -d'-' -f3 | cut -d'.' -f 1` go build -o $@ $(LDFLAGS) $(PACKAGE_NAME)
.PHONY: help check-env
check-env:
ifndef VERSION
$(error VERSION is undefined)
endif
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
.DEFAULT_GOAL := help
================================================
FILE: README.md
================================================
# httpstat [](https://github.com/davecheney/httpstat/actions/workflows/push.yml) [](https://goreportcard.com/report/github.com/davecheney/httpstat)

Imitation is the sincerest form of flattery.
But seriously, https://github.com/reorx/httpstat is the new hotness, and this is a shameless rip off.
## Installation
`httpstat` requires Go 1.20 or later.
```
go install github.com/davecheney/httpstat@latest
```
## Usage
```
httpstat https://example.com/
```
## Features
- Windows/BSD/Linux supported.
- HTTP and HTTPS are supported, for self signed certificates use `-k`.
- Skip timing the body of a response with `-I`.
- Follow 30x redirects with `-L`.
- Change HTTP method with `-X METHOD`.
- Provide a `PUT` or `POST` request body with `-d string`. To supply the `PUT` or `POST` body as a file, use `-d @filename`.
- Add extra request headers with `-H 'Name: value'`.
- The response body is usually discarded, you can use `-o filename` to save it to a file, or `-O` to save it to the file name suggested by the server.
- HTTP/HTTPS proxies supported via the usual `HTTP_PROXY`/`HTTPS_PROXY` env vars (as well as lower case variants).
- Supply your own client side certificate with `-E cert.pem`.
## Contributing
Bug reports are most welcome, but with the exception of #5, this project is closed.
Pull requests must include a `fixes #NNN` or `updates #NNN` comment.
Please discuss your design on the accompanying issue before submitting a pull request. If there is no suitable issue, please open one to discuss the feature before slinging code. Thank you.
================================================
FILE: go.mod
================================================
module github.com/davecheney/httpstat
go 1.23
require github.com/fatih/color v1.18.0
require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.25.0 // indirect
)
================================================
FILE: go.sum
================================================
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
================================================
FILE: main.go
================================================
package main
import (
"context"
"crypto/tls"
"encoding/pem"
"flag"
"fmt"
"io"
"log"
"mime"
"net"
"net/http"
"net/http/httptrace"
"net/url"
"os"
"path"
"runtime"
"sort"
"strconv"
"strings"
"time"
"github.com/fatih/color"
)
const (
httpsTemplate = `` +
` DNS Lookup TCP Connection TLS Handshake Server Processing Content Transfer` + "\n" +
`[%s | %s | %s | %s | %s ]` + "\n" +
` | | | | |` + "\n" +
` namelookup:%s | | | |` + "\n" +
` connect:%s | | |` + "\n" +
` pretransfer:%s | |` + "\n" +
` starttransfer:%s |` + "\n" +
` total:%s` + "\n"
httpTemplate = `` +
` DNS Lookup TCP Connection Server Processing Content Transfer` + "\n" +
`[ %s | %s | %s | %s ]` + "\n" +
` | | | |` + "\n" +
` namelookup:%s | | |` + "\n" +
` connect:%s | |` + "\n" +
` starttransfer:%s |` + "\n" +
` total:%s` + "\n"
)
var (
// Command line flags.
httpMethod string
postBody string
followRedirects bool
onlyHeader bool
insecure bool
httpHeaders headers
saveOutput bool
outputFile string
showVersion bool
clientCertFile string
fourOnly bool
sixOnly bool
// number of redirects followed
redirectsFollowed int
version = "devel" // for -v flag, updated during the release process with -ldflags=-X=main.version=...
)
const maxRedirects = 10
func init() {
flag.StringVar(&httpMethod, "X", "GET", "HTTP method to use")
flag.StringVar(&postBody, "d", "", "the body of a POST or PUT request; from file use @filename")
flag.BoolVar(&followRedirects, "L", false, "follow 30x redirects")
flag.BoolVar(&onlyHeader, "I", false, "don't read body of request")
flag.BoolVar(&insecure, "k", false, "allow insecure SSL connections")
flag.Var(&httpHeaders, "H", "set HTTP header; repeatable: -H 'Accept: ...' -H 'Range: ...'")
flag.BoolVar(&saveOutput, "O", false, "save body as remote filename")
flag.StringVar(&outputFile, "o", "", "output file for body")
flag.BoolVar(&showVersion, "v", false, "print version number")
flag.StringVar(&clientCertFile, "E", "", "client cert file for tls config")
flag.BoolVar(&fourOnly, "4", false, "resolve IPv4 addresses only")
flag.BoolVar(&sixOnly, "6", false, "resolve IPv6 addresses only")
flag.Usage = usage
}
func usage() {
fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] URL\n\n", os.Args[0])
fmt.Fprintln(os.Stderr, "OPTIONS:")
flag.PrintDefaults()
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "ENVIRONMENT:")
fmt.Fprintln(os.Stderr, " HTTP_PROXY proxy for HTTP requests; complete URL or HOST[:PORT]")
fmt.Fprintln(os.Stderr, " used for HTTPS requests if HTTPS_PROXY undefined")
fmt.Fprintln(os.Stderr, " HTTPS_PROXY proxy for HTTPS requests; complete URL or HOST[:PORT]")
fmt.Fprintln(os.Stderr, " NO_PROXY comma-separated list of hosts to exclude from proxy")
}
func printf(format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(color.Output, format, a...)
}
func grayscale(code color.Attribute) func(string, ...interface{}) string {
return color.New(code + 232).SprintfFunc()
}
func main() {
flag.Parse()
if showVersion {
fmt.Printf("%s %s (runtime: %s)\n", os.Args[0], version, runtime.Version())
os.Exit(0)
}
if fourOnly && sixOnly {
fmt.Fprintf(os.Stderr, "%s: Only one of -4 and -6 may be specified\n", os.Args[0])
os.Exit(-1)
}
args := flag.Args()
if len(args) != 1 {
flag.Usage()
os.Exit(2)
}
if (httpMethod == "POST" || httpMethod == "PUT") && postBody == "" {
log.Fatal("must supply post body using -d when POST or PUT is used")
}
if onlyHeader {
httpMethod = "HEAD"
}
url := parseURL(args[0])
visit(url)
}
// readClientCert - helper function to read client certificate
// from pem formatted file
func readClientCert(filename string) []tls.Certificate {
if filename == "" {
return nil
}
var (
pkeyPem []byte
certPem []byte
)
// read client certificate file (must include client private key and certificate)
certFileBytes, err := os.ReadFile(filename)
if err != nil {
log.Fatalf("failed to read client certificate file: %v", err)
}
for {
block, rest := pem.Decode(certFileBytes)
if block == nil {
break
}
certFileBytes = rest
if strings.HasSuffix(block.Type, "PRIVATE KEY") {
pkeyPem = pem.EncodeToMemory(block)
}
if strings.HasSuffix(block.Type, "CERTIFICATE") {
certPem = pem.EncodeToMemory(block)
}
}
cert, err := tls.X509KeyPair(certPem, pkeyPem)
if err != nil {
log.Fatalf("unable to load client cert and key pair: %v", err)
}
return []tls.Certificate{cert}
}
func parseURL(uri string) *url.URL {
if !strings.Contains(uri, "://") && !strings.HasPrefix(uri, "//") {
uri = "//" + uri
}
url, err := url.Parse(uri)
if err != nil {
log.Fatalf("could not parse url %q: %v", uri, err)
}
if url.Scheme == "" {
url.Scheme = "http"
if !strings.HasSuffix(url.Host, ":80") {
url.Scheme += "s"
}
}
return url
}
func headerKeyValue(h string) (string, string) {
i := strings.Index(h, ":")
if i == -1 {
log.Fatalf("Header '%s' has invalid format, missing ':'", h)
}
return strings.TrimRight(h[:i], " "), strings.TrimLeft(h[i:], " :")
}
func dialContext(network string) func(ctx context.Context, network, addr string) (net.Conn, error) {
return func(ctx context.Context, _, addr string) (net.Conn, error) {
return (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: false,
}).DialContext(ctx, network, addr)
}
}
// visit visits a url and times the interaction.
// If the response is a 30x, visit follows the redirect.
func visit(url *url.URL) {
req := newRequest(httpMethod, url, postBody)
var t0, t1, t2, t3, t4, t5, t6 time.Time
trace := &httptrace.ClientTrace{
DNSStart: func(_ httptrace.DNSStartInfo) { t0 = time.Now() },
DNSDone: func(_ httptrace.DNSDoneInfo) { t1 = time.Now() },
ConnectStart: func(_, _ string) {
if t1.IsZero() {
// connecting to IP
t1 = time.Now()
}
},
ConnectDone: func(net, addr string, err error) {
if err != nil {
log.Fatalf("unable to connect to host %v: %v", addr, err)
}
t2 = time.Now()
printf("\n%s%s\n", color.GreenString("Connected to "), color.CyanString(addr))
},
GotConn: func(_ httptrace.GotConnInfo) { t3 = time.Now() },
GotFirstResponseByte: func() { t4 = time.Now() },
TLSHandshakeStart: func() { t5 = time.Now() },
TLSHandshakeDone: func(_ tls.ConnectionState, _ error) { t6 = time.Now() },
}
req = req.WithContext(httptrace.WithClientTrace(context.Background(), trace))
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
ForceAttemptHTTP2: true,
}
switch {
case fourOnly:
tr.DialContext = dialContext("tcp4")
case sixOnly:
tr.DialContext = dialContext("tcp6")
}
switch url.Scheme {
case "https":
host, _, err := net.SplitHostPort(req.Host)
if err != nil {
host = req.Host
}
tr.TLSClientConfig = &tls.Config{
ServerName: host,
InsecureSkipVerify: insecure,
Certificates: readClientCert(clientCertFile),
MinVersion: tls.VersionTLS12,
}
}
client := &http.Client{
Transport: tr,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// always refuse to follow redirects, visit does that
// manually if required.
return http.ErrUseLastResponse
},
}
resp, err := client.Do(req)
if err != nil {
log.Fatalf("failed to read response: %v", err)
}
// Print SSL/TLS version which is used for connection
connectedVia := "plaintext"
if resp.TLS != nil {
switch resp.TLS.Version {
case tls.VersionTLS12:
connectedVia = "TLSv1.2"
case tls.VersionTLS13:
connectedVia = "TLSv1.3"
}
}
printf("\n%s %s\n", color.GreenString("Connected via"), color.CyanString("%s", connectedVia))
bodyMsg := readResponseBody(req, resp)
resp.Body.Close()
t7 := time.Now() // after read body
if t0.IsZero() {
// we skipped DNS
t0 = t1
}
// print status line and headers
printf("\n%s%s%s\n", color.GreenString("HTTP"), grayscale(14)("/"), color.CyanString("%d.%d %s", resp.ProtoMajor, resp.ProtoMinor, resp.Status))
names := make([]string, 0, len(resp.Header))
for k := range resp.Header {
names = append(names, k)
}
sort.Sort(headers(names))
for _, k := range names {
printf("%s %s\n", grayscale(14)(k+":"), color.CyanString(strings.Join(resp.Header[k], ",")))
}
if bodyMsg != "" {
printf("\n%s\n", bodyMsg)
}
fmta := func(d time.Duration) string {
return color.CyanString("%7dms", int(d/time.Millisecond))
}
fmtb := func(d time.Duration) string {
return color.CyanString("%-9s", strconv.Itoa(int(d/time.Millisecond))+"ms")
}
colorize := func(s string) string {
v := strings.Split(s, "\n")
v[0] = grayscale(16)(v[0])
return strings.Join(v, "\n")
}
fmt.Println()
switch url.Scheme {
case "https":
printf(colorize(httpsTemplate),
fmta(t1.Sub(t0)), // dns lookup
fmta(t2.Sub(t1)), // tcp connection
fmta(t6.Sub(t5)), // tls handshake
fmta(t4.Sub(t3)), // server processing
fmta(t7.Sub(t4)), // content transfer
fmtb(t1.Sub(t0)), // namelookup
fmtb(t2.Sub(t0)), // connect
fmtb(t3.Sub(t0)), // pretransfer
fmtb(t4.Sub(t0)), // starttransfer
fmtb(t7.Sub(t0)), // total
)
case "http":
printf(colorize(httpTemplate),
fmta(t1.Sub(t0)), // dns lookup
fmta(t3.Sub(t1)), // tcp connection
fmta(t4.Sub(t3)), // server processing
fmta(t7.Sub(t4)), // content transfer
fmtb(t1.Sub(t0)), // namelookup
fmtb(t3.Sub(t0)), // connect
fmtb(t4.Sub(t0)), // starttransfer
fmtb(t7.Sub(t0)), // total
)
}
if followRedirects && isRedirect(resp) {
loc, err := resp.Location()
if err != nil {
if err == http.ErrNoLocation {
// 30x but no Location to follow, give up.
return
}
log.Fatalf("unable to follow redirect: %v", err)
}
redirectsFollowed++
if redirectsFollowed > maxRedirects {
log.Fatalf("maximum number of redirects (%d) followed", maxRedirects)
}
visit(loc)
}
}
func isRedirect(resp *http.Response) bool {
return resp.StatusCode > 299 && resp.StatusCode < 400
}
func newRequest(method string, url *url.URL, body string) *http.Request {
req, err := http.NewRequest(method, url.String(), createBody(body))
if err != nil {
log.Fatalf("unable to create request: %v", err)
}
for _, h := range httpHeaders {
k, v := headerKeyValue(h)
if strings.EqualFold(k, "host") {
req.Host = v
continue
}
req.Header.Add(k, v)
}
return req
}
func createBody(body string) io.Reader {
if strings.HasPrefix(body, "@") {
filename := body[1:]
f, err := os.Open(filename)
if err != nil {
log.Fatalf("failed to open data file %s: %v", filename, err)
}
return f
}
return strings.NewReader(body)
}
// getFilenameFromHeaders tries to automatically determine the output filename,
// when saving to disk, based on the Content-Disposition header.
// If the header is not present, or it does not contain enough information to
// determine which filename to use, this function returns "".
func getFilenameFromHeaders(headers http.Header) string {
// if the Content-Disposition header is set parse it
if hdr := headers.Get("Content-Disposition"); hdr != "" {
// pull the media type, and subsequent params, from
// the body of the header field
mt, params, err := mime.ParseMediaType(hdr)
// if there was no error and the media type is attachment
if err == nil && mt == "attachment" {
if filename := params["filename"]; filename != "" {
return filename
}
}
}
// return an empty string if we were unable to determine the filename
return ""
}
// readResponseBody consumes the body of the response.
// readResponseBody returns an informational message about the
// disposition of the response body's contents.
func readResponseBody(req *http.Request, resp *http.Response) string {
if isRedirect(resp) || req.Method == http.MethodHead {
return ""
}
w := io.Discard
msg := color.CyanString("Body discarded")
if saveOutput || outputFile != "" {
filename := outputFile
if saveOutput {
// try to get the filename from the Content-Disposition header
// otherwise fall back to the RequestURI
if filename = getFilenameFromHeaders(resp.Header); filename == "" {
filename = path.Base(req.URL.RequestURI())
}
if filename == "/" {
log.Fatalf("No remote filename; specify output filename with -o to save response body")
}
}
f, err := os.Create(filename)
if err != nil {
log.Fatalf("unable to create file %s: %v", filename, err)
}
defer f.Close()
w = f
msg = color.CyanString("Body read")
}
if _, err := io.Copy(w, resp.Body); err != nil && w != io.Discard {
log.Fatalf("failed to read response body: %v", err)
}
return msg
}
type headers []string
func (h headers) String() string {
var o []string
for _, v := range h {
o = append(o, "-H "+v)
}
return strings.Join(o, " ")
}
func (h *headers) Set(v string) error {
*h = append(*h, v)
return nil
}
func (h headers) Len() int { return len(h) }
func (h headers) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h headers) Less(i, j int) bool {
a, b := h[i], h[j]
// server always sorts at the top
if a == "Server" {
return true
}
if b == "Server" {
return false
}
endtoend := func(n string) bool {
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
switch n {
case "Connection",
"Keep-Alive",
"Proxy-Authenticate",
"Proxy-Authorization",
"TE",
"Trailers",
"Transfer-Encoding",
"Upgrade":
return false
default:
return true
}
}
x, y := endtoend(a), endtoend(b)
if x == y {
// both are of the same class
return a < b
}
return x
}
================================================
FILE: main_test.go
================================================
package main
import "testing"
func TestParseURL(t *testing.T) {
tests := []struct {
in string
want string
}{
{"https://golang.org", "https://golang.org"},
{"https://golang.org:443/test", "https://golang.org:443/test"},
{"localhost:8080/test", "https://localhost:8080/test"},
{"localhost:80/test", "http://localhost:80/test"},
{"//localhost:8080/test", "https://localhost:8080/test"},
{"//localhost:80/test", "http://localhost:80/test"},
}
for _, test := range tests {
u := parseURL(test.in)
if u.String() != test.want {
t.Errorf("Given: %s\nwant: %s\ngot: %s", test.in, test.want, u.String())
}
}
}
gitextract__z7b9of5/ ├── .github/ │ └── workflows/ │ └── push.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── main.go └── main_test.go
SYMBOL INDEX (25 symbols across 2 files)
FILE: main.go
constant httpsTemplate (line 28) | httpsTemplate = `` +
constant httpTemplate (line 38) | httpTemplate = `` +
constant maxRedirects (line 69) | maxRedirects = 10
function init (line 71) | func init() {
function usage (line 88) | func usage() {
function printf (line 100) | func printf(format string, a ...interface{}) (n int, err error) {
function grayscale (line 104) | func grayscale(code color.Attribute) func(string, ...interface{}) string {
function main (line 108) | func main() {
function readClientCert (line 142) | func readClientCert(filename string) []tls.Certificate {
function parseURL (line 179) | func parseURL(uri string) *url.URL {
function headerKeyValue (line 198) | func headerKeyValue(h string) (string, string) {
function dialContext (line 206) | func dialContext(network string) func(ctx context.Context, network, addr...
function visit (line 218) | func visit(url *url.URL) {
function isRedirect (line 391) | func isRedirect(resp *http.Response) bool {
function newRequest (line 395) | func newRequest(method string, url *url.URL, body string) *http.Request {
function createBody (line 411) | func createBody(body string) io.Reader {
function getFilenameFromHeaders (line 427) | func getFilenameFromHeaders(headers http.Header) string {
function readResponseBody (line 449) | func readResponseBody(req *http.Request, resp *http.Response) string {
type headers (line 488) | type headers
method String (line 490) | func (h headers) String() string {
method Set (line 498) | func (h *headers) Set(v string) error {
method Len (line 503) | func (h headers) Len() int { return len(h) }
method Swap (line 504) | func (h headers) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
method Less (line 505) | func (h headers) Less(i, j int) bool {
FILE: main_test.go
function TestParseURL (line 5) | func TestParseURL(t *testing.T) {
Condensed preview — 9 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (23K chars).
[
{
"path": ".github/workflows/push.yml",
"chars": 444,
"preview": "on:\n push:\n branches:\n - master\n pull_request:\n branches:\n - master\nname: Push\njobs:\n test:\n runs-"
},
{
"path": ".gitignore",
"chars": 276,
"preview": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture spe"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2016 Dave Cheney\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "Makefile",
"chars": 988,
"preview": "TARGETS = linux-386 linux-amd64 linux-arm linux-arm64 darwin-amd64 windows-386 windows-amd64\nCOMMAND_NAME = httpstat\nPAC"
},
{
"path": "README.md",
"chars": 1767,
"preview": "# httpstat [](https://github"
},
{
"path": "go.mod",
"chars": 237,
"preview": "module github.com/davecheney/httpstat\n\ngo 1.23\n\nrequire github.com/fatih/color v1.18.0\n\nrequire (\n\tgithub.com/mattn/go-c"
},
{
"path": "go.sum",
"chars": 946,
"preview": "github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=\ngithub.com/fatih/color v1.18.0/go.mod h1:"
},
{
"path": "main.go",
"chars": 14586,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/pem\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"mime\"\n\t\"net\"\n\t\"net/http\"\n\t"
},
{
"path": "main_test.go",
"chars": 634,
"preview": "package main\n\nimport \"testing\"\n\nfunc TestParseURL(t *testing.T) {\n\ttests := []struct {\n\t\tin string\n\t\twant string\n\t}{\n\t"
}
]
About this extraction
This page contains the full source code of the davecheney/httpstat GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 9 files (20.5 KB), approximately 6.4k tokens, and a symbol index with 25 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.