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