Merge pull request 'Adding .gitignore and templates folder' (#3) from fix/params-failing into main

Reviewed-on: #3
This commit is contained in:
iratusmachina 2024-05-28 23:47:04 +00:00
commit add7cb1005
7 changed files with 172 additions and 40 deletions

20
.gitignore vendored Normal file
View File

@ -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

View File

@ -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)
```

View File

@ -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 {

View File

@ -5,15 +5,26 @@ import (
"html/template" "html/template"
) )
// https://stackoverflow.com/questions/70193820/why-isnt-go-embedding-files
//
//go:embed templates/* //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
} }

View File

@ -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

View File

@ -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
View File

@ -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())
} }