github.com/gocaveman/caveman@v0.0.0-20191211162744-0ddf99dbdf6e/webutil/webutil.go (about)

     1  // General utilities for web serving.
     2  //
     3  // To qualify for inclusion in this package, the thing in question must be:
     4  //
     5  // Not already solved by or included in the Go stdlib.
     6  //
     7  // Not already solved by or included in a third party package or cost of including that package outweighs the cost of including it here.
     8  //
     9  // Generally useful for web serving.  Features which are intensely Caveman-specific belong elsewhere.  Features which follow a pattern used or introduced in Caveman but don't directly depend on it may be okay.
    10  //
    11  // Don't fit cleanly into one of the other Caveman packages.
    12  //
    13  // Small enough that they don't deserve their own package.
    14  //
    15  // Simple enough to not bloat this package.
    16  //
    17  // Complex enough to bother putting in a separate package at all (avoid one-line functions).
    18  //
    19  // Interfaces which connect disparate packages without introducing dependencies may be okay. (e.g. DataSource, ReadSeekCloser)
    20  //
    21  // Avoid introducing non-stdlib dependencies, pretty much at all costs.
    22  //
    23  // Must be worth maintaining as part of this package (as opposed to e.g. just copying and pasting it around).
    24  //
    25  package webutil
    26  
    27  import (
    28  	"bufio"
    29  	"bytes"
    30  	"fmt"
    31  	"io"
    32  	"os"
    33  	"runtime"
    34  	"strings"
    35  )
    36  
    37  // ErrNotFound is a generic "not found" error.  Useful to communicate that generic concept
    38  // across packages without introducing dependencies.  For convenience it is an alias
    39  // of os.ErrNotExist, which means os.IsNotExist() will return true for it.
    40  var ErrNotFound = os.ErrNotExist
    41  
    42  var ErrAlreadyExists = os.ErrExist
    43  
    44  // MainOnly checks the call stack to ensure that the caller is in the main package.
    45  // Used to defend against inexperienced developers trying to read from a registry anywhere
    46  // but in the main package.  The argument says how many levels of the stack to remove.
    47  // Use 0 to indicate the function that called this one, 1 to indicate it's caller, etc.
    48  func MainOnly(n int) {
    49  
    50  	stackbuf := make([]byte, 4096)
    51  	stackbuf = stackbuf[:runtime.Stack(stackbuf, false)]
    52  
    53  	// log.Printf("STACK\n%s", stackbuf)
    54  
    55  	// sample stack:
    56  
    57  	// goroutine 1 [running]:
    58  	// main.main.func1()
    59  	// 	/Volumes/Files/git/caveman/src/github.com/gocaveman/quickstart-full/main.go:60 +0x77
    60  	// main.main()
    61  	// 	/Volumes/Files/git/caveman/src/github.com/gocaveman/quickstart-full/main.go:62 +0x61d
    62  
    63  	r := bufio.NewReader(bytes.NewReader(stackbuf))
    64  
    65  	r.ReadString('\n') // "goroutine N ..."
    66  
    67  	c := -2
    68  	for {
    69  
    70  		line, err := r.ReadString('\n')
    71  		if err == io.EOF {
    72  			break
    73  		}
    74  
    75  		if c < n {
    76  			c++
    77  			continue
    78  		}
    79  
    80  		// lines starting with tabs are the filename:lineno ones, skip those
    81  		if strings.HasPrefix(line, "\t") {
    82  			continue
    83  		}
    84  
    85  		// first thing before the period should be the package name
    86  		lineParts := strings.SplitN(line, ".", 2)
    87  
    88  		if lineParts[0] != "main" {
    89  			panic(fmt.Errorf("call must be made from main package, found %q instead", line))
    90  		}
    91  
    92  		break
    93  
    94  	}
    95  
    96  }