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
|
```sh
|
||||||
$ go get -v -u jbowen.dev/cereal
|
$ 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)
|
jbowen.dev/cereal (download)
|
||||||
```
|
```
|
||||||
|
|
||||||
With the httpie command
|
## TODOs
|
||||||
|
|
||||||
```sh
|
* [x] Fix permission errors around opening the app.log and rules.json.
|
||||||
$ http --body "https://gopkg.in/yaml.v3?go-get=1"
|
* [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 {
|
if len(c.MappingFilePath) == 0 {
|
||||||
ok := isFile(mappingFilePath)
|
ok := isFile(mappingFilePath)
|
||||||
if !ok {
|
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)
|
mappingFile, err := os.Open(mappingFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
25
embed.go
25
embed.go
|
@ -5,15 +5,26 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
// go:embed templates/*
|
// https://stackoverflow.com/questions/70193820/why-isnt-go-embedding-files
|
||||||
|
//
|
||||||
|
//go:embed templates/*
|
||||||
var tmpls embed.FS
|
var tmpls embed.FS
|
||||||
|
|
||||||
func GetServeHtml() *template.Template {
|
func GetServeHtml() (*template.Template, error) {
|
||||||
data, _ := tmpls.ReadFile("success.html")
|
var t *template.Template
|
||||||
return template.Must(template.New("main").Parse(string(data)))
|
data, err := tmpls.ReadFile("templates/success.html")
|
||||||
|
if err != nil {
|
||||||
|
return t, err
|
||||||
|
}
|
||||||
|
return template.New("").Parse(string(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultHtml() []byte {
|
func GetDefaultHtml() ([]byte, error) {
|
||||||
data, _ := tmpls.ReadFile("default.html")
|
var data []byte
|
||||||
return data
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -14,8 +15,9 @@ func reloadRules(c *Config) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
err := c.LoadMappingFile("")
|
err := c.LoadMappingFile("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorLog.Printf("Cannot reload rules: %+v", err)
|
e := fmt.Errorf("annot reload rules: %+v", err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// errorLog.Printf("Cannot reload rules: %+v", err)
|
||||||
|
http.Error(w, e.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Write([]byte("ok"))
|
w.Write([]byte("ok"))
|
||||||
|
@ -31,7 +33,12 @@ func serveRules(c *Config) http.HandlerFunc {
|
||||||
|
|
||||||
// if go-get param is absent, return nothing
|
// if go-get param is absent, return nothing
|
||||||
if r.FormValue("go-get") != "1" {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,12 +46,9 @@ func serveRules(c *Config) http.HandlerFunc {
|
||||||
|
|
||||||
var vanityUrl, proto, repoUrl string
|
var vanityUrl, proto, repoUrl string
|
||||||
for _, rule := range c.MappingRules.Mappings {
|
for _, rule := range c.MappingRules.Mappings {
|
||||||
if strings.HasPrefix(strings.ToLower(nameOfPkg), strings.ToLower(rule.VanityUrl+"/")) {
|
if strings.HasPrefix(strings.ToLower(rule.VanityUrl+"/"), strings.Trim(strings.ToLower(nameOfPkg), " ")) {
|
||||||
repo := strings.Replace(strings.ToLower(nameOfPkg), strings.ToLower(rule.VanityUrl), "", -1)
|
vanityUrl = rule.VanityUrl
|
||||||
repo = strings.Split(repo, "/")[1]
|
repoUrl = rule.RealUrl
|
||||||
|
|
||||||
vanityUrl = rule.VanityUrl + "/" + repo
|
|
||||||
repoUrl = rule.RealUrl + "/" + repo
|
|
||||||
proto = rule.Protocol
|
proto = rule.Protocol
|
||||||
|
|
||||||
break
|
break
|
||||||
|
@ -56,10 +60,14 @@ func serveRules(c *Config) http.HandlerFunc {
|
||||||
Proto: proto,
|
Proto: proto,
|
||||||
RepoUrl: repoUrl,
|
RepoUrl: repoUrl,
|
||||||
}
|
}
|
||||||
tmpl := GetServeHtml()
|
tmpl, err := GetServeHtml()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err := tmpl.Execute(&buf, &d)
|
err = tmpl.Execute(&buf, &d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|
16
logger.go
16
logger.go
|
@ -5,7 +5,9 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// some headers not worth logging
|
// some headers not worth logging
|
||||||
|
@ -50,7 +52,13 @@ type LogFileRec struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFileLogger(path string) (*LogFile, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -123,6 +131,11 @@ func shouldLogHeader(s string) bool {
|
||||||
return !hdrsToNotLogMap[s]
|
return !hdrsToNotLogMap[s]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCurrentDate() string {
|
||||||
|
dt := time.Now()
|
||||||
|
return dt.Format(time.RFC3339Nano)
|
||||||
|
}
|
||||||
|
|
||||||
func (f *LogFile) WriteLog(r *http.Request) error {
|
func (f *LogFile) WriteLog(r *http.Request) error {
|
||||||
if f == nil {
|
if f == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -132,6 +145,7 @@ func (f *LogFile) WriteLog(r *http.Request) error {
|
||||||
rec["requestUri"] = r.RequestURI
|
rec["requestUri"] = r.RequestURI
|
||||||
rec["Host"] = r.Host
|
rec["Host"] = r.Host
|
||||||
rec["ipAddr"] = extractIpAddress(r)
|
rec["ipAddr"] = extractIpAddress(r)
|
||||||
|
rec["requestDate"] = getCurrentDate()
|
||||||
if !canSkipExtraHeaders(r) {
|
if !canSkipExtraHeaders(r) {
|
||||||
for key, val := range r.Header {
|
for key, val := range r.Header {
|
||||||
if shouldLogHeader(key) && len(val) > 0 {
|
if shouldLogHeader(key) && len(val) > 0 {
|
||||||
|
|
78
main.go
78
main.go
|
@ -9,16 +9,18 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errorLog *log.Logger = log.New(os.Stderr, "", log.LstdFlags)
|
var errorLog *log.Logger = log.New(os.Stderr, "", log.LstdFlags)
|
||||||
|
|
||||||
const (
|
// const (
|
||||||
DEFAULT_RULES_FILE string = "/var/lib/gocustomurls/rules.json"
|
// DEFAULT_RULES_FILE string = "/var/lib/gocustomurls/rules.json"
|
||||||
DEFAULT_LOG_FILE string = "/var/log/gocustomurls/app.log"
|
// DEFAULT_LOG_FILE string = "/var/log/gocustomurls/app.log"
|
||||||
)
|
// )
|
||||||
|
|
||||||
// flagsSet returns a set of all the flags what were actually set on the
|
// flagsSet returns a set of all the flags what were actually set on the
|
||||||
// command line.
|
// command line.
|
||||||
|
@ -30,6 +32,35 @@ func flagsSet(flags *flag.FlagSet) map[string]bool {
|
||||||
return s
|
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() {
|
func main() {
|
||||||
programName := os.Args[0]
|
programName := os.Args[0]
|
||||||
// errorLog = log.New(os.Stderr, "", log.LstdFlags)
|
// errorLog = log.New(os.Stderr, "", log.LstdFlags)
|
||||||
|
@ -43,9 +74,9 @@ func main() {
|
||||||
flags.PrintDefaults()
|
flags.PrintDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
portFlag := flags.String("port", "7070", "port to listen to")
|
portFlag := flags.String("port", "7070", "Optional. Default port is 7070. Port to listen to")
|
||||||
rulesFileFlag := flags.String("config", DEFAULT_RULES_FILE, "contains go-import mapping")
|
rulesFileFlag := flags.String("config", "", "Optional. Contains go-import mapping")
|
||||||
logFileFlag := flags.String("logfile", DEFAULT_LOG_FILE, "default log file")
|
logFileFlag := flags.String("logfile", "", "Optional. Default log file")
|
||||||
flags.Parse(os.Args[1:])
|
flags.Parse(os.Args[1:])
|
||||||
|
|
||||||
if len(flags.Args()) > 1 {
|
if len(flags.Args()) > 1 {
|
||||||
|
@ -59,9 +90,23 @@ func main() {
|
||||||
var port string
|
var port string
|
||||||
if allSetFlags["port"] {
|
if allSetFlags["port"] {
|
||||||
port = *portFlag
|
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
|
var rulesFile string
|
||||||
|
|
||||||
if allSetFlags["config"] {
|
if allSetFlags["config"] {
|
||||||
rulesFile = *rulesFileFlag
|
rulesFile = *rulesFileFlag
|
||||||
}
|
}
|
||||||
|
@ -71,14 +116,20 @@ func main() {
|
||||||
logFile = *logFileFlag
|
logFile = *logFileFlag
|
||||||
}
|
}
|
||||||
|
|
||||||
// load rules mapping
|
rFile, lFile, err := generateDefaults(logFile, rulesFile)
|
||||||
c := &Config{}
|
|
||||||
err := c.LoadMappingFile(rulesFile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorLog.Println(err)
|
errorLog.Println(err)
|
||||||
os.Exit(1)
|
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 {
|
if err != nil {
|
||||||
errorLog.Println(err)
|
errorLog.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -92,12 +143,13 @@ func main() {
|
||||||
|
|
||||||
// For graceful shutdowns
|
// For graceful shutdowns
|
||||||
go func() {
|
go func() {
|
||||||
|
app.Log.logger.Printf("%s Starting\n", getCurrentDate())
|
||||||
err := srv.ListenAndServe()
|
err := srv.ListenAndServe()
|
||||||
if !errors.Is(err, http.ErrServerClosed) {
|
if !errors.Is(err, http.ErrServerClosed) {
|
||||||
errorLog.Printf("HTTP Server error: %+v\n", err)
|
errorLog.Printf("HTTP Server error: %+v\n", err)
|
||||||
os.Exit(1)
|
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)
|
sigChan := make(chan os.Signal, 1)
|
||||||
|
@ -111,5 +163,5 @@ func main() {
|
||||||
errorLog.Printf("HTTP shutdown error: %+v\n", err)
|
errorLog.Printf("HTTP shutdown error: %+v\n", err)
|
||||||
os.Exit(1)
|
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