« Back to Index

[go struct with mutex to encapsulate data from users and help hide required lock implementation]

View original Gist on GitHub

Tags: #go #golang #struct #mutex #concurrency #encapsulation

basic example of go struct’s using a mutex.go

type A struct {
    mu sync.Mutex
}

a := &A{}

a.mu.Lock()
defer a.mu.Unlock()

a.Something()

// VS

var hits struct {
    sync.Mutex
    n int
}

hits.Lock()
hits.n++
hits.Unlock()

encapsulation example.go

package proxy

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
)

// ErrorCodes defines status codes as keys for custom error pages.
type ErrorCodes map[int][]byte

// ErrorHosts defines hosts as keys for supported error codes.
type ErrorHosts map[string]ErrorCodes

// ErrorPages contains custom error pages for host domains by status code.
type ErrorPages struct {
	mu    sync.RWMutex
	pages ErrorHosts
}

// Store sets a given HTML string within the pages map by host/status code.
func (ep *ErrorPages) Store(host string, status int, buf []byte) {
	ep.mu.Lock()
	defer ep.mu.Unlock()

	if !ep.HasHost(host) {
		ep.pages[host] = make(ErrorCodes)
	}

	ep.pages[host][status] = buf
}

// Get retrieves HTML string associated with the given host/status code.
func (ep *ErrorPages) Get(host string, status int) ([]byte, bool) {
	ep.mu.RLock()
	defer ep.mu.RUnlock()

	if !ep.HasHost(host) {
		return nil, false
	}

	if _, ok := ep.pages[host][status]; !ok {
		return nil, false
	}

	return ep.pages[host][status], true
}

// HasHost returns true/false to indicate if pages map contains the given host.
func (ep *ErrorPages) HasHost(host string) bool {
	_, ok := ep.pages[host]
	return ok
}

// newErrorPages reads the file system and stores the contents of each custom
// error page into an in-memory cache.
//
// example directory structure:
//
// static/
//   foo.com/
//     400.html
//     404.html
//     410.html
//     500.html
//   bar.com/
//     404.html
//     500.html
//   baz.com/
//     404.html
//     500.html
func newErrorPages(parentDir string) *ErrorPages {
	var host string

	ep := new(ErrorPages)
	ep.pages = make(ErrorHosts)

	root := "/app/static/"
	skip := "static"

	filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		if info.IsDir() && info.Name() == skip {
			return nil
		}

		// track the host so we can ensure the following files are added under it.
		if info.IsDir() {
			host = info.Name()
			return nil
		}

		// at this point we know we're dealing with a file and not a directory
		content, err := ioutil.ReadFile(path)
		if err != nil {
			log.Fatal(err)
		}

		// extract status code from filename and populate pages map with file contents.
		statusKey, _ := strconv.Atoi(strings.Split(info.Name(), ".html")[0])

		fmt.Println(host, path, statusKey)
		ep.Store(host, statusKey, content)

		return nil
	})

	return ep
}