Adding .gitignore and templates folder #3
			
				
			
		
		
		
	| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
# Binaries for programs and plugins
 | 
			
		||||
artifacts
 | 
			
		||||
*.exe
 | 
			
		||||
*.exe~
 | 
			
		||||
*.dll
 | 
			
		||||
*.so
 | 
			
		||||
*.dylib
 | 
			
		||||
 | 
			
		||||
# Test binary, built with `go test -c`
 | 
			
		||||
*.test
 | 
			
		||||
 | 
			
		||||
# Output of the go coverage tool, specifically when used with LiteIDE
 | 
			
		||||
*.out
 | 
			
		||||
 | 
			
		||||
# Dependency directories (remove the comment below to include it)
 | 
			
		||||
# vendor/
 | 
			
		||||
 | 
			
		||||
# Go workspace file
 | 
			
		||||
go.work
 | 
			
		||||
go.work.sum
 | 
			
		||||
							
								
								
									
										39
									
								
								README.md
								
								
								
								
							
							
						
						
									
										39
									
								
								README.md
								
								
								
								
							| 
						 | 
				
			
			@ -1,6 +1,32 @@
 | 
			
		|||
# Testing
 | 
			
		||||
# GocustomUrls
 | 
			
		||||
 | 
			
		||||
With the go-get command
 | 
			
		||||
This is a homespun implementation of the practice of [using custom import paths for go modules](https://pkg.go.dev/cmd/go#hdr-Remote_import_paths).
 | 
			
		||||
 | 
			
		||||
This package basically returns a html document with a prefilled meta tag
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<meta name="go-import" content="{package} git {url location of the package}">
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
There are ways to do this with [nginx](https://www.nirenjan.com/2019/golang-vanity-urls-in-nginx/) or [hugo](https://blog.jbowen.dev/2020/07/using-go-vanity-urls-with-hugo/) but I wanted to:
 | 
			
		||||
 | 
			
		||||
* host on a subdomain
 | 
			
		||||
* not muck up my ngnix config
 | 
			
		||||
 | 
			
		||||
So I used golang for this project.
 | 
			
		||||
 | 
			
		||||
## Testing
 | 
			
		||||
 | 
			
		||||
You can test with 
 | 
			
		||||
 | 
			
		||||
(a) [httpie](https://httpie.io/)
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
$ http --body "https://{domain.name}/{package}?go-get=1"
 | 
			
		||||
...truncated output
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
(b) With the go-get command
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
$ go get -v -u jbowen.dev/cereal
 | 
			
		||||
| 
						 | 
				
			
			@ -8,9 +34,8 @@ get "jbowen.dev/cereal": found meta tag get.metaImport{Prefix:"jbowen.dev/cereal
 | 
			
		|||
jbowen.dev/cereal (download)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
With the httpie command
 | 
			
		||||
## TODOs
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
$ http --body "https://gopkg.in/yaml.v3?go-get=1"
 | 
			
		||||
...
 | 
			
		||||
```
 | 
			
		||||
* [x] Fix permission errors around opening the app.log and rules.json.
 | 
			
		||||
* [x] Make the flags (config, rules) required instead of optional.
 | 
			
		||||
* [ ] Figure how to use logrotate (a linux utility)
 | 
			
		||||
							
								
								
									
										4
									
								
								conf.go
								
								
								
								
							
							
						
						
									
										4
									
								
								conf.go
								
								
								
								
							| 
						 | 
				
			
			@ -41,8 +41,10 @@ func (c *Config) LoadMappingFile(fp string) error {
 | 
			
		|||
	if len(c.MappingFilePath) == 0 {
 | 
			
		||||
		ok := isFile(mappingFilePath)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return fmt.Errorf("%s is not found", mappingFilePath)
 | 
			
		||||
			return fmt.Errorf("mappingfile %s is not found", mappingFilePath)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		mappingFilePath = c.MappingFilePath
 | 
			
		||||
	}
 | 
			
		||||
	mappingFile, err := os.Open(mappingFilePath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										25
									
								
								embed.go
								
								
								
								
							
							
						
						
									
										25
									
								
								embed.go
								
								
								
								
							| 
						 | 
				
			
			@ -5,15 +5,26 @@ import (
 | 
			
		|||
	"html/template"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// go:embed templates/*
 | 
			
		||||
// https://stackoverflow.com/questions/70193820/why-isnt-go-embedding-files
 | 
			
		||||
//
 | 
			
		||||
//go:embed templates/*
 | 
			
		||||
var tmpls embed.FS
 | 
			
		||||
 | 
			
		||||
func GetServeHtml() *template.Template {
 | 
			
		||||
	data, _ := tmpls.ReadFile("success.html")
 | 
			
		||||
	return template.Must(template.New("main").Parse(string(data)))
 | 
			
		||||
func GetServeHtml() (*template.Template, error) {
 | 
			
		||||
	var t *template.Template
 | 
			
		||||
	data, err := tmpls.ReadFile("templates/success.html")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return t, err
 | 
			
		||||
	}
 | 
			
		||||
	return template.New("").Parse(string(data))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetDefaultHtml() []byte {
 | 
			
		||||
	data, _ := tmpls.ReadFile("default.html")
 | 
			
		||||
	return data
 | 
			
		||||
func GetDefaultHtml() ([]byte, error) {
 | 
			
		||||
	var data []byte
 | 
			
		||||
	var err error
 | 
			
		||||
	data, err = tmpls.ReadFile("templates/default.html")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return data, err
 | 
			
		||||
	}
 | 
			
		||||
	return data, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										30
									
								
								handlers.go
								
								
								
								
							
							
						
						
									
										30
									
								
								handlers.go
								
								
								
								
							| 
						 | 
				
			
			@ -2,6 +2,7 @@ package main
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -14,8 +15,9 @@ func reloadRules(c *Config) http.HandlerFunc {
 | 
			
		|||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		err := c.LoadMappingFile("")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorLog.Printf("Cannot reload rules: %+v", err)
 | 
			
		||||
			http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
			e := fmt.Errorf("annot reload rules: %+v", err)
 | 
			
		||||
			// errorLog.Printf("Cannot reload rules: %+v", err)
 | 
			
		||||
			http.Error(w, e.Error(), http.StatusInternalServerError)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		w.Write([]byte("ok"))
 | 
			
		||||
| 
						 | 
				
			
			@ -31,7 +33,12 @@ func serveRules(c *Config) http.HandlerFunc {
 | 
			
		|||
 | 
			
		||||
		// if go-get param is absent, return nothing
 | 
			
		||||
		if r.FormValue("go-get") != "1" {
 | 
			
		||||
			w.Write(GetDefaultHtml())
 | 
			
		||||
			data, err := GetDefaultHtml()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			w.Write(data)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -39,12 +46,9 @@ func serveRules(c *Config) http.HandlerFunc {
 | 
			
		|||
 | 
			
		||||
		var vanityUrl, proto, repoUrl string
 | 
			
		||||
		for _, rule := range c.MappingRules.Mappings {
 | 
			
		||||
			if strings.HasPrefix(strings.ToLower(nameOfPkg), strings.ToLower(rule.VanityUrl+"/")) {
 | 
			
		||||
				repo := strings.Replace(strings.ToLower(nameOfPkg), strings.ToLower(rule.VanityUrl), "", -1)
 | 
			
		||||
				repo = strings.Split(repo, "/")[1]
 | 
			
		||||
 | 
			
		||||
				vanityUrl = rule.VanityUrl + "/" + repo
 | 
			
		||||
				repoUrl = rule.RealUrl + "/" + repo
 | 
			
		||||
			if strings.HasPrefix(strings.ToLower(rule.VanityUrl+"/"), strings.Trim(strings.ToLower(nameOfPkg), " ")) {
 | 
			
		||||
				vanityUrl = rule.VanityUrl
 | 
			
		||||
				repoUrl = rule.RealUrl
 | 
			
		||||
				proto = rule.Protocol
 | 
			
		||||
 | 
			
		||||
				break
 | 
			
		||||
| 
						 | 
				
			
			@ -56,10 +60,14 @@ func serveRules(c *Config) http.HandlerFunc {
 | 
			
		|||
			Proto:     proto,
 | 
			
		||||
			RepoUrl:   repoUrl,
 | 
			
		||||
		}
 | 
			
		||||
		tmpl := GetServeHtml()
 | 
			
		||||
		tmpl, err := GetServeHtml()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var buf bytes.Buffer
 | 
			
		||||
		err := tmpl.Execute(&buf, &d)
 | 
			
		||||
		err = tmpl.Execute(&buf, &d)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
			return
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										16
									
								
								logger.go
								
								
								
								
							
							
						
						
									
										16
									
								
								logger.go
								
								
								
								
							| 
						 | 
				
			
			@ -5,7 +5,9 @@ import (
 | 
			
		|||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// some headers not worth logging
 | 
			
		||||
| 
						 | 
				
			
			@ -50,7 +52,13 @@ type LogFileRec struct {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func newFileLogger(path string) (*LogFile, error) {
 | 
			
		||||
	f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
 | 
			
		||||
	requestedFile := filepath.Clean(filepath.Join("/", path))
 | 
			
		||||
	parentDir := filepath.Dir(requestedFile)
 | 
			
		||||
	err := os.MkdirAll(parentDir, 0777)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	f, err := os.OpenFile(requestedFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -123,6 +131,11 @@ func shouldLogHeader(s string) bool {
 | 
			
		|||
	return !hdrsToNotLogMap[s]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getCurrentDate() string {
 | 
			
		||||
	dt := time.Now()
 | 
			
		||||
	return dt.Format(time.RFC3339Nano)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *LogFile) WriteLog(r *http.Request) error {
 | 
			
		||||
	if f == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
| 
						 | 
				
			
			@ -132,6 +145,7 @@ func (f *LogFile) WriteLog(r *http.Request) error {
 | 
			
		|||
	rec["requestUri"] = r.RequestURI
 | 
			
		||||
	rec["Host"] = r.Host
 | 
			
		||||
	rec["ipAddr"] = extractIpAddress(r)
 | 
			
		||||
	rec["requestDate"] = getCurrentDate()
 | 
			
		||||
	if !canSkipExtraHeaders(r) {
 | 
			
		||||
		for key, val := range r.Header {
 | 
			
		||||
			if shouldLogHeader(key) && len(val) > 0 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										78
									
								
								main.go
								
								
								
								
							
							
						
						
									
										78
									
								
								main.go
								
								
								
								
							| 
						 | 
				
			
			@ -9,16 +9,18 @@ import (
 | 
			
		|||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/signal"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var errorLog *log.Logger = log.New(os.Stderr, "", log.LstdFlags)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	DEFAULT_RULES_FILE string = "/var/lib/gocustomurls/rules.json"
 | 
			
		||||
	DEFAULT_LOG_FILE   string = "/var/log/gocustomurls/app.log"
 | 
			
		||||
)
 | 
			
		||||
// const (
 | 
			
		||||
// 	DEFAULT_RULES_FILE string = "/var/lib/gocustomurls/rules.json"
 | 
			
		||||
// 	DEFAULT_LOG_FILE   string = "/var/log/gocustomurls/app.log"
 | 
			
		||||
// )
 | 
			
		||||
 | 
			
		||||
// flagsSet returns a set of all the flags what were actually set on the
 | 
			
		||||
// command line.
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +32,35 @@ func flagsSet(flags *flag.FlagSet) map[string]bool {
 | 
			
		|||
	return s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// generateDefaults - generate the default values for the rules.json and log files
 | 
			
		||||
func generateDefaults(rulesfp string, logfp string) (string, string, error) {
 | 
			
		||||
	var newlogfp, newrulesfp string
 | 
			
		||||
	var err error
 | 
			
		||||
	newlogfp = logfp
 | 
			
		||||
	newrulesfp = rulesfp
 | 
			
		||||
	if len(newrulesfp) == 0 {
 | 
			
		||||
		dir, err := os.UserConfigDir()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return newrulesfp, newlogfp, err
 | 
			
		||||
		}
 | 
			
		||||
		newrulesfp = filepath.Join(dir, "gocustomcurls", "rules.json")
 | 
			
		||||
	}
 | 
			
		||||
	if len(newlogfp) == 0 {
 | 
			
		||||
		dir, err := os.UserHomeDir()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return newrulesfp, newlogfp, err
 | 
			
		||||
		}
 | 
			
		||||
		newlogfp = filepath.Join(dir, ".gocustomurls", "logs", "app.log")
 | 
			
		||||
	}
 | 
			
		||||
	return newrulesfp, newlogfp, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// isValidPort returns true if the port is valid
 | 
			
		||||
// following the RFC https://datatracker.ietf.org/doc/html/rfc6056#section-2.1
 | 
			
		||||
func isValidPort(port int) bool {
 | 
			
		||||
	return port > 0 && port < 65535
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	programName := os.Args[0]
 | 
			
		||||
	// errorLog = log.New(os.Stderr, "", log.LstdFlags)
 | 
			
		||||
| 
						 | 
				
			
			@ -43,9 +74,9 @@ func main() {
 | 
			
		|||
		flags.PrintDefaults()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	portFlag := flags.String("port", "7070", "port to listen to")
 | 
			
		||||
	rulesFileFlag := flags.String("config", DEFAULT_RULES_FILE, "contains go-import mapping")
 | 
			
		||||
	logFileFlag := flags.String("logfile", DEFAULT_LOG_FILE, "default log file")
 | 
			
		||||
	portFlag := flags.String("port", "7070", "Optional. Default port is 7070. Port to listen to")
 | 
			
		||||
	rulesFileFlag := flags.String("config", "", "Optional. Contains go-import mapping")
 | 
			
		||||
	logFileFlag := flags.String("logfile", "", "Optional. Default log file")
 | 
			
		||||
	flags.Parse(os.Args[1:])
 | 
			
		||||
 | 
			
		||||
	if len(flags.Args()) > 1 {
 | 
			
		||||
| 
						 | 
				
			
			@ -59,9 +90,23 @@ func main() {
 | 
			
		|||
	var port string
 | 
			
		||||
	if allSetFlags["port"] {
 | 
			
		||||
		port = *portFlag
 | 
			
		||||
	} else {
 | 
			
		||||
		port = "7070"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	p, err := strconv.Atoi(port)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errorLog.Println(err)
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !isValidPort(p) {
 | 
			
		||||
		errorLog.Println(fmt.Errorf("provided port (%d) is not valid", p))
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var rulesFile string
 | 
			
		||||
 | 
			
		||||
	if allSetFlags["config"] {
 | 
			
		||||
		rulesFile = *rulesFileFlag
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -71,14 +116,20 @@ func main() {
 | 
			
		|||
		logFile = *logFileFlag
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// load rules mapping
 | 
			
		||||
	c := &Config{}
 | 
			
		||||
	err := c.LoadMappingFile(rulesFile)
 | 
			
		||||
	rFile, lFile, err := generateDefaults(logFile, rulesFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errorLog.Println(err)
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
	l, err := newFileLogger(logFile)
 | 
			
		||||
 | 
			
		||||
	// load rules mapping
 | 
			
		||||
	c := &Config{}
 | 
			
		||||
	err = c.LoadMappingFile(rFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errorLog.Println(err)
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
	l, err := newFileLogger(lFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errorLog.Println(err)
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
| 
						 | 
				
			
			@ -92,12 +143,13 @@ func main() {
 | 
			
		|||
 | 
			
		||||
	// For graceful shutdowns
 | 
			
		||||
	go func() {
 | 
			
		||||
		app.Log.logger.Printf("%s Starting\n", getCurrentDate())
 | 
			
		||||
		err := srv.ListenAndServe()
 | 
			
		||||
		if !errors.Is(err, http.ErrServerClosed) {
 | 
			
		||||
			errorLog.Printf("HTTP Server error: %+v\n", err)
 | 
			
		||||
			os.Exit(1)
 | 
			
		||||
		}
 | 
			
		||||
		app.Log.logger.Println("Stopped serving new connections.")
 | 
			
		||||
		app.Log.logger.Printf("%s Stopped serving new connections.\n", getCurrentDate())
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	sigChan := make(chan os.Signal, 1)
 | 
			
		||||
| 
						 | 
				
			
			@ -111,5 +163,5 @@ func main() {
 | 
			
		|||
		errorLog.Printf("HTTP shutdown error: %+v\n", err)
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
	app.Log.logger.Println("Graceful shutdown complete.")
 | 
			
		||||
	app.Log.logger.Printf("%s Graceful shutdown complete.\n", getCurrentDate())
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue