Skip to content

Go Cheatsheet

AJ ONeal edited this page Oct 12, 2025 · 9 revisions

Help & Version Flags

TL;DR - handle version and help out-of-band so that it works with POSIX, BSD, Go, and subcommand flag styles.

   if len(os.Args) > 1 {
      switch os.Args[1] {
      case "-V", "version", "-version", "--version":
         printVersion()
         return
      case "help", "-help", "--help":
         mainFlags.Usage()
         os.Exit(0)
         return
      }
   }

main.go:

package main

import (
   "flag"
   "fmt"
)

const (
   name         = "CHANGEME"
   licenseYear  = "20XX"
   licenseOwner = "CHANGE ME"
   licenseType  = "MPL-2.0"
)

// set by GoReleaser via ldflags
var (
   version = "0.0.0-dev"
   commit  = "0000000"
   date    = "0001-01-01T00:00:00Z"
)

// printVersion displays the version, commit, and build date.
func printVersion() {
   fmt.Fprintf(os.Stderr, "%s v%s %s (%s)\n", name, version, commit[:7], date)
   fmt.Fprintf(os.Stderr, "Copyright (C) %s %s\n", licenseYear, licenseOwner)
   fmt.Fprintf(os.Stderr, "Licensed under the %s license\n", licenseType)
}

func main() {
   mainFlags := flag.NewFlagSet("", flag.ContinueOnError)

   var showVersion bool
   mainFlags.BoolVar(&showVersion, "version", false, "Print version and exit")

   mainFlags.Usage = func() {
      printVersion()
      fmt.Fprintf(os.Stderr, "\n")
      fmt.Fprintf(os.Stderr, "USAGE\n")
      fmt.Fprintf(os.Stderr, "   CHANGEME [options] <url>\n")
      mainFlags.PrintDefaults()
   }

   if len(os.Args) > 1 {
      switch os.Args[1] {
      case "-V", "version", "-version", "--version":
         printVersion()
         return
      case "help", "-help", "--help":
         mainFlags.Usage()
         os.Exit(0)
         return
      }
   }

   if err := mainFlags.Parse(os.Args[1:]); err != nil {
      fmt.Println(err)

      mainFlags.Usage()
      os.Exit(1)
      return
   }

   // Handle --version flag after parsing
   if showVersion {
      printVersion()
      return
   }

   // ...
}

Note: TinyGo handles ldflags and compile-time variable replacement differently, so they must be set at runtime in init().

// set by GoReleaser via ldflags
var (
   version = ""
   commit  = ""
   date    = ""
)

// workaround for `tinygo` ldflag replacement handling not allowing default values
// See <https://github.com/tinygo-org/tinygo/issues/2976>
func init() {
   if len(version) == 0 {
      version = "0.0.0-dev"
   }
   if len(date) == 0 {
      date = "0001-01-01T00:00:00Z"
   }
   if len(commit) == 0 {
      commit = "0000000"
   }
}

http router

   port := 8080
   flag.IntVar(&port, "port", port, "Port to listen on")

   bind := "127.0.0.1"
   flag.StringVar(&bind, "bind", bind, "Address to bind to")

   // ...

   mux := http.NewServeMux()
   m := mw.New(jsonapi.LogPanics, routes.RequireCustomer)
   mux.HandleFunc("GET /api/greet/{greeting}", m.Handle(routes.Greet))

   addr := fmt.Sprintf("%s:%d", bind, port)
   log.Printf("Listening on %s ...\n", addr)
   log.Fatal(http.ListenAndServe(addr, mux))

GoDoc Tricks: https://pkg.go.dev/github.com/fluhus/godoc-tricks

Browser Extension

https://chrome.google.com/webstore/detail/go-search-extension/epanejkfcekejmmfbcpbcbigfpefbnlb

new project

go mod init github.com/coolaj86/projectname

tools package

mkdir -p tools

go get github.com/goware/modvendor
go get github.com/kyleconroy/sqlc/cmd/sqlc@latest

go mod vendor
go run -mod=vendor github.com/goware/modvendor -copy="**/*.c **/*.h **/*.proto" -v

build.go

//go:generate go run -mod=vendor github.com/kyleconroy/sqlc/cmd/sqlc generate

package main

tools/tools.go:

//go:build tools

package tools

import (
    // for the modvendor command
    _ "github.com/goware/modvendor"
    
    // for the sqlc command
    _ "github.com/kyleconroy/sqlc/cmd/sqlc"
)
go mod tidy

go generate -mod=vendor ./...

go.mod exclude and replace

module github.com/example/project

require (
    github.com/SermoDigital/jose v0.0.0-20180104203859-803625baeddc
    github.com/google/uuid v1.1.0
)

exclude github.com/SermoDigital/jose v0.9.1

replace github.com/google/uuid v1.1.0 => git.coolaj86.com/coolaj86/uuid.go v1.1.1

See https://stackoverflow.com/a/53824026/151312.

Array Sort, Contains, ...

sort.Strings(haystack)
func contains(haystack []string, needle string) bool {
    i := sort.SearchStrings(haystack, needle)
    return i < len(haystack) && needle == haystack[i]
}

Internationalization

https://phrase.com/blog/posts/internationalization-i18n-go/

HTTP Request

In part from https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/

c := &http.Client{
    Transport: &http.Transport{
        Dial: (&net.Dialer{
                Timeout:   30 * time.Second,
                KeepAlive: 30 * time.Second,
        }).Dial,
        TLSHandshakeTimeout:   10 * time.Second,
        ResponseHeaderTimeout: 10 * time.Second,
        ExpectContinueTimeout: 1 * time.Second,
    }
}

c := make(chan struct{})
timer := time.AfterFunc(5*time.Second, func() {
	close(c)
})

// Serve 256 bytes every second.
req, err := http.NewRequest("GET", "http://httpbin.org/range/2048?duration=8&chunk_size=256", nil)
if err != nil {
	log.Fatal(err)
}
req.Cancel = c

log.Println("Sending request...")
resp, err := c.Do(req)
if err != nil {
	log.Fatal(err)
}
defer resp.Body.Close()

Base64 (and Random IDs)

s := base64.RawURLEncoding.EncodeToString([]byte(data))
b, err := base64.RawURLEncoding.DecodeString(s)

Random IDs

var Rando = rand.Reader
b := make([]byte, 16)
n, err := Rando.Read(b)
id := base64.RawURLEncoding.EncodeToString(b)

Parse Fractional Seconds

func ParseSeconds(s string) (int64, int64, error) {
	seconds, err := strconv.ParseFloat(s, 64)
	if nil != err {
		return 0.0, 0.0, err
	}
	secs, nanos := SecondsToInts(seconds)
	return secs, nanos, nil
}

func SecondsToInts(seconds float64) (int64, int64) {
	secs := math.Floor(seconds)
	nanos := math.Round((seconds - secs) * 1_000_000_000)
	return int64(secs), int64(nanos)
}
func ParseSeconds(secs string) (int64, int64, error) {
	// "789.0123" => []string{"789", "0123"}
	parts := strings.Split(secs, ".")
	if len(parts) > 2 {
		return 0, 0, errors.New("could not parse as seconds")
	} else if len(parts) < 2 {
		// no nanoseconds, just seconds
		// "789" => []string{"789"}
		s, err := strconv.ParseInt(parts[0], 10, 64)
		return s, 0, err
	}

	// convert the second's part
	s, err := strconv.ParseInt(parts[0], 10, 64)
	if nil != err {
		return 0, 0, err
	}

	// get nanoseconds from fractional second
	d, err := time.ParseDuration("0." + parts[1] + "s")
	return s, d.Nanoseconds(), err
}

Parse Unix Time

func ParseUnixTime(seconds string) (time.Time, error) {
	secs, nano, err := ParseSeconds(seconds)
	if nil != err {
		return time.Time{}, err
	}

	return time.Unix(secs, nano), nil
}

Time to Unix Seconds

func ToUnixSeconds(t time.Time) float64 {
    // 1614236182.651912345
    secs := float64(t.Unix())                          // 1614236182
    nanos := float64(t.Nanosecond()) / 1_000_000_000.0 // 0.651912345

    // in my case I want to truncate the precision to milliseconds
    nanos = math.Round((10000 * nanos) / 10000) // 0.6519

    s := secs + nanos // 1614236182.651912345
    return s
}

Left Pad and Right Pad

// Left pad up to 10 spaces
fmt.Sprintf("'%*s'", 10, "Hello")
// '     Hello'

// Right pad up to 10 spaces
fmt.Sprintf("'Hello%-*s'", 10, "Hello")
// 'Hello     '

// Left pad up to 10 zeros
fmt.Sprintf("'%0*s'\n", 10, "Hello")
// '00000Hello'

// Right pad up to 10 zeros... psych!
fmt.Sprintf("'%-0*s'\n", 10, "Hello")
// 'Hello     '

HTTP Request

  func SafeRequest(req *http.Request) (*http.Response, error) {
    var netTransport = &http.Transport{
      Dial: (&net.Dialer{
        Timeout: 5 * time.Second,
      }).Dial,
      TLSHandshakeTimeout: 5 * time.Second,
    }
    var netClient = &http.Client{
      Timeout:   time.Second * 10,
      Transport: netTransport,
    }
    response, err := netClient.Do(req)
    if nil != err {
      return nil, err
    }
    body := response.Body
    response.Body = io.LimitReader(body, 1024 * 1024)
    //response.Body = MaxBytesReader(body, 1024*1024)
    return response, nil
  }

HTTP Server

    srv := &http.Server{
        Addr:              runOpts.Addr,
        Handler:           r,
        ReadHeaderTimeout: 2 * time.Second,
        ReadTimeout:       10 * time.Second,
        WriteTimeout:      20 * time.Second,
        MaxHeaderBytes:    1024 * 1024, // 1MiB
    }
    if err := srv.ListenAndServe(); nil != err {
        fmt.Fprintf(os.Stderr, "%s", err)
        os.Exit(1)
        return
    }

HTTP Test Server

var srv *httptest.Server

func TestMain(m *testing.M) {
    // math/rand.Seed(0)
    
    srv = httptest.NewServer(mux)
    os.Exit(m.Run())
}
  client := srv.Client()
  urlstr, _ := url.Parse(srv.URL + "/debug/verify?exp=false")

  req := &http.Request{
    Method: "POST",
    URL:    urlstr,
    //Body:   ioutil.NopCloser(bytes.NewReader(jws)),
    Header: http.Header{},
  }
  req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", jwt))
  res, err := client.Do(req)
  if nil != err {
    t.Error(err)
    return
  }
  data, err := ioutil.ReadAll(res.Body)
  if nil != err {
    t.Error(err)
    return
  }
  if 200 != res.StatusCode {
    log.Printf(string(data))
    t.Error(fmt.Errorf("bad status code: %d", res.StatusCode))
    return
  }

HTTP Handler

r.Body = http.MaxBytesReader(w, r.Body, 1 << 20)

Tee HTTP Body

TODO

Security

Compare

import "crypto/subtle"

func ConstantTimeCompare(x, y []byte) bool {
  return 1 == subtle.ConstantTimeCompare(x, y)
}

Error Strategies

JSON to Go

JSON to Postgres

https://numidian.io/convert/json/to/postgres

410 Gone

export GO111MODULE=on
export GOPROXY=direct
export GOSUMDB=off

X-Hub-Signature

const (
	// sha1Prefix is the prefix used by GitHub before the HMAC hexdigest.
	sha1Prefix = "sha1"
	// sha256Prefix and sha512Prefix are provided for future compatibility.
	sha256Prefix = "sha256"
	sha512Prefix = "sha512"
	// signatureHeader is the GitHub header key used to pass the HMAC hexdigest.
	signatureHeader = "X-Hub-Signature"
)

	if len(secretToken) > 0 {
		sig := r.Header.Get(signatureHeader)
		if err := ValidateSignature(sig, body, secretToken); err != nil {
			return nil, err
		}
	}
    
// ValidateSignature validates the signature for the given payload.
// signature is the GitHub hash signature delivered in the X-Hub-Signature header.
// payload is the JSON payload sent by GitHub Webhooks.
// secretToken is the GitHub Webhook secret token.
//
// GitHub API docs: https://developer.github.com/webhooks/securing/#validating-payloads-from-github
func ValidateSignature(signature string, payload, secretToken []byte) error {
	messageMAC, hashFunc, err := messageMAC(signature)
	if err != nil {
		return err
	}
	if !checkMAC(payload, messageMAC, secretToken, hashFunc) {
		return errors.New("payload signature check failed")
	}
	return nil
}

// checkMAC reports whether messageMAC is a valid HMAC tag for message.
func checkMAC(message, messageMAC, key []byte, hashFunc func() hash.Hash) bool {
	expectedMAC := genMAC(message, key, hashFunc)
	return hmac.Equal(messageMAC, expectedMAC)
}

// messageMAC returns the hex-decoded HMAC tag from the signature and its
// corresponding hash function.
func messageMAC(signature string) ([]byte, func() hash.Hash, error) {
	if signature == "" {
		return nil, nil, errors.New("missing signature")
	}
	sigParts := strings.SplitN(signature, "=", 2)
	if len(sigParts) != 2 {
		return nil, nil, fmt.Errorf("error parsing signature %q", signature)
	}

	var hashFunc func() hash.Hash
	switch sigParts[0] {
	case sha1Prefix:
		hashFunc = sha1.New
	case sha256Prefix:
		hashFunc = sha256.New
	case sha512Prefix:
		hashFunc = sha512.New
	default:
		return nil, nil, fmt.Errorf("unknown hash type prefix: %q", sigParts[0])
	}

	buf, err := hex.DecodeString(sigParts[1])
	if err != nil {
		return nil, nil, fmt.Errorf("error decoding signature %q: %v", signature, err)
	}
	return buf, hashFunc, nil
}

Build Tags

Build Tag Syntax Build Tag Sample Boolean Statement
Space-separated elements // +build pro enterprise pro OR enterprise
Comma-separated elements // +build pro,enterprise pro AND enterprise
Exclamation point elements // +build !pro NOT pro