diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..3018bff
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 5c0f819..dda6ba1 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,8 @@
This allows for the required meta headers to be outputted in order for the GO package system to find the source files of the package.
-The middleware can be configured in runtime, the server has a YAML configuration.
+The outputter can be configured in runtime, the server has a YAML configuration.
+The outputter can be used to add the extra meta tags to the head of the HTML document.
Maintainer:
[Captain ALM](https://code.mrmelon54.xyz/alfred)
diff --git a/cmd/gopkghsrv/main.go b/cmd/gopkghsrv/main.go
index ad4a711..4e11be1 100644
--- a/cmd/gopkghsrv/main.go
+++ b/cmd/gopkghsrv/main.go
@@ -1,6 +1,19 @@
package main
-import "log"
+import (
+ "fmt"
+ "github.com/joho/godotenv"
+ "golang.captainalm.com/GOPackageHeaderServer/conf"
+ "golang.captainalm.com/GOPackageHeaderServer/web"
+ "gopkg.in/yaml.v3"
+ "log"
+ "os"
+ "os/signal"
+ "path"
+ "sync"
+ "syscall"
+ "time"
+)
var (
buildVersion = "develop"
@@ -9,4 +22,84 @@ var (
func main() {
log.Printf("[Main] Starting up GO Package Header Server #%s (%s)\n", buildVersion, buildDate)
+ y := time.Now()
+
+ //Hold main thread till safe shutdown exit:
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+
+ //Get working directory:
+ cwdDir, err := os.Getwd()
+ if err != nil {
+ log.Println(err)
+ }
+
+ //Load environment file:
+ err = godotenv.Load()
+ if err != nil {
+ log.Fatalln("Error loading .env file")
+ }
+
+ //Data directory processing:
+ dataDir := os.Getenv("DIR_DATA")
+ if dataDir == "" {
+ dataDir = path.Join(cwdDir, ".data")
+ }
+
+ check(os.MkdirAll(dataDir, 0777))
+
+ //Config loading:
+ configFile, err := os.Open(path.Join(dataDir, "config.yml"))
+ if err != nil {
+ log.Fatalln("Failed to open config.yml")
+ }
+
+ var configYml conf.ConfigYaml
+ groupsDecoder := yaml.NewDecoder(configFile)
+ err = groupsDecoder.Decode(&configYml)
+ if err != nil {
+ log.Fatalln("Failed to parse config.yml:", err)
+ }
+
+ //Server definitions:
+ log.Printf("[Main] Starting up HTTP server on %s...\n", configYml.Listen.Web)
+ webServer, _ := web.New(configYml)
+
+ //=====================
+ // Safe shutdown
+ sigs := make(chan os.Signal, 1)
+ signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
+
+ //Startup complete:
+ z := time.Now().Sub(y)
+ log.Printf("[Main] Took '%s' to fully initialize modules\n", z.String())
+
+ go func() {
+ <-sigs
+ fmt.Printf("\n")
+
+ log.Printf("[Main] Attempting safe shutdown\n")
+ a := time.Now()
+
+ log.Printf("[Main] Shutting down HTTP server...\n")
+ err := webServer.Close()
+ if err != nil {
+ log.Println(err)
+ }
+
+ log.Printf("[Main] Signalling program exit...\n")
+ b := time.Now().Sub(a)
+ log.Printf("[Main] Took '%s' to fully shutdown modules\n", b.String())
+ wg.Done()
+ }()
+ //
+ //=====================
+ wg.Wait()
+ log.Println("[Main] Goodbye")
+}
+
+func check(err error) {
+ if err != nil {
+ panic(err)
+ }
}
diff --git a/conf/config.go b/conf/config.go
new file mode 100644
index 0000000..c9f133b
--- /dev/null
+++ b/conf/config.go
@@ -0,0 +1,6 @@
+package conf
+
+type ConfigYaml struct {
+ Listen ListenYaml `yaml:"listen"`
+ Zones []ZoneYaml `yaml:"zones"`
+}
diff --git a/conf/listen.go b/conf/listen.go
new file mode 100644
index 0000000..07eb258
--- /dev/null
+++ b/conf/listen.go
@@ -0,0 +1,26 @@
+package conf
+
+import "time"
+
+type ListenYaml struct {
+ Web string `yaml:"web"`
+ ReadTimeout time.Duration `yaml:"readTimeout"`
+ WriteTimeout time.Duration `yaml:"writeTimeout"`
+ Identify bool `yaml:"identify"`
+}
+
+func (ly ListenYaml) GetReadTimeout() time.Duration {
+ if ly.ReadTimeout.Seconds() < 1 {
+ return 1 * time.Second
+ } else {
+ return ly.ReadTimeout
+ }
+}
+
+func (ly ListenYaml) GetWriteTimeout() time.Duration {
+ if ly.WriteTimeout.Seconds() < 1 {
+ return 1 * time.Second
+ } else {
+ return ly.WriteTimeout
+ }
+}
diff --git a/conf/zone.go b/conf/zone.go
new file mode 100644
index 0000000..381d759
--- /dev/null
+++ b/conf/zone.go
@@ -0,0 +1,29 @@
+package conf
+
+import "golang.captainalm.com/GOPackageHeaderServer/outputMeta"
+
+type ZoneYaml struct {
+ Name string `yaml:"name"`
+ Domains []string `yaml:"domains"`
+ HavePageContents bool `yaml:"havePageContents"`
+ BasePath string `yaml:"basePath"`
+ UsernameProvided bool `yaml:"usernameProvided"`
+ Username string `yaml:"username"`
+ BasePrefixURL string `yaml:"basePrefixURL"`
+ SuffixDirectoryURL string `yaml:"suffixDirectoryURL"`
+ SuffixFileURL string `yaml:"suffixFileURL"`
+}
+
+func (zy ZoneYaml) GetPackageMetaTagOutputter() *outputMeta.PackageMetaTagOutputter {
+ var theUsername string
+ if !zy.UsernameProvided {
+ theUsername = zy.Username
+ }
+ return &outputMeta.PackageMetaTagOutputter{
+ BasePath: zy.BasePath,
+ Username: theUsername,
+ BasePrefixURL: zy.BasePrefixURL,
+ SuffixDirectoryURL: zy.SuffixDirectoryURL,
+ SuffixFileURL: zy.SuffixFileURL,
+ }
+}
diff --git a/go.mod b/go.mod
index dc1c1f0..629cb13 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,9 @@
-module GOPackageHeaderServer
+module golang.captainalm.com/GOPackageHeaderServer
go 1.18
+
+require (
+ github.com/gorilla/mux v1.8.0
+ github.com/joho/godotenv v1.4.0
+ gopkg.in/yaml.v3 v3.0.1
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..27d4d9a
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,8 @@
+github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
+github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/outputMeta/packagemetatagoutputter.go b/outputMeta/packagemetatagoutputter.go
new file mode 100644
index 0000000..a1f4c09
--- /dev/null
+++ b/outputMeta/packagemetatagoutputter.go
@@ -0,0 +1,77 @@
+package outputMeta
+
+import (
+ "path"
+ "strings"
+)
+
+type PackageMetaTagOutputter struct {
+ BasePath string
+ Username string //If set, the outputter will do /{repo}/ for repos rather than /{user}/{repo}/
+ BasePrefixURL string
+ SuffixDirectoryURL string
+ SuffixFileURL string
+}
+
+func (pkgMTO *PackageMetaTagOutputter) GetMetaTags(pathIn string) string {
+ return "\r\n" +
+ ""
+}
+
+func (pkgMTO *PackageMetaTagOutputter) assureBasePrefixURL() (failed bool) {
+ if pkgMTO.BasePrefixURL == "" {
+ if pkgMTO.BasePath == "" {
+ return true
+ }
+ pkgMTO.BasePrefixURL = "http://" + pkgMTO.BasePath
+ }
+ return false
+}
+
+func (pkgMTO *PackageMetaTagOutputter) getPrefix(pathIn string) string {
+ if pkgMTO.BasePath == "" {
+ return "_"
+ }
+ if pkgMTO.Username == "" {
+ return path.Join(pkgMTO.BasePath, pathIn)
+ } else {
+ return path.Join(pkgMTO.BasePath, pkgMTO.Username, pathIn)
+ }
+}
+
+func (pkgMTO *PackageMetaTagOutputter) getHomeURL(pathIn string) string {
+ if pkgMTO.assureBasePrefixURL() {
+ return "_"
+ }
+
+ if pkgMTO.Username == "" {
+ return pkgMTO.BasePrefixURL + "/" + strings.TrimLeft(path.Clean(pathIn), "/")
+ } else {
+ return pkgMTO.BasePrefixURL + "/" + strings.TrimLeft(path.Join(pkgMTO.Username, pathIn), "/")
+ }
+}
+
+func (pkgMTO *PackageMetaTagOutputter) getDirectoryURL(pathIn string) string {
+ if pkgMTO.assureBasePrefixURL() || pkgMTO.SuffixDirectoryURL == "" {
+ return "_"
+ }
+
+ if pkgMTO.Username == "" {
+ return pkgMTO.BasePrefixURL + "/" + strings.TrimLeft(path.Join(pathIn, pkgMTO.SuffixDirectoryURL), "/")
+ } else {
+ return pkgMTO.BasePrefixURL + "/" + strings.TrimLeft(path.Join(pkgMTO.Username, pathIn, pkgMTO.SuffixDirectoryURL), "/")
+ }
+}
+
+func (pkgMTO *PackageMetaTagOutputter) getFileURL(pathIn string) string {
+ if pkgMTO.assureBasePrefixURL() || pkgMTO.SuffixFileURL == "" {
+ return "_"
+ }
+
+ if pkgMTO.Username == "" {
+ return pkgMTO.BasePrefixURL + "/" + strings.TrimLeft(path.Join(pathIn, pkgMTO.SuffixFileURL), "/")
+ } else {
+ return pkgMTO.BasePrefixURL + "/" + strings.TrimLeft(path.Join(pkgMTO.Username, pathIn, pkgMTO.SuffixFileURL), "/")
+ }
+}
diff --git a/web/pagehandler.go b/web/pagehandler.go
new file mode 100644
index 0000000..a8dfb3b
--- /dev/null
+++ b/web/pagehandler.go
@@ -0,0 +1,50 @@
+package web
+
+import (
+ "golang.captainalm.com/GOPackageHeaderServer/outputMeta"
+ "net/http"
+ "path"
+ "strconv"
+ "strings"
+)
+
+type PageHandler struct {
+ Name string
+ OutputPage bool
+ MetaOutput *outputMeta.PackageMetaTagOutputter
+}
+
+func (pgh *PageHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
+ if request.Method == http.MethodGet || request.Method == http.MethodHead {
+ thePage := "\r\n\r\n
\r\n"
+ if pgh.OutputPage && pgh.Name != "" {
+ thePage += "Go Package: " + pgh.Name + "\r\n"
+ }
+ thePage += pgh.MetaOutput.GetMetaTags(request.URL.Path) + "\r\n\r\n\r\n"
+ if pgh.OutputPage {
+ if pgh.Name != "" {
+ thePage += "Go Package: " + pgh.Name + "
\r\n"
+ }
+ var theLink string
+ if pgh.MetaOutput.Username == "" {
+ theLink = pgh.MetaOutput.BasePrefixURL + "/" + strings.TrimLeft(path.Clean(request.URL.Path), "/")
+ } else {
+ theLink = pgh.MetaOutput.BasePrefixURL + "/" + strings.TrimLeft(path.Join(pgh.MetaOutput.Username, request.URL.Path), "/")
+ }
+ thePage += "" + theLink + "\r\n"
+ }
+ thePage += "\r\n\r\n"
+ writer.Header().Set("Content-Length", strconv.Itoa(len([]byte(thePage))))
+ writer.Header().Set("Content-Type", "text/html; charset=utf-8")
+ if writeResponseHeaderCanWriteBody(request.Method, writer, http.StatusOK, "") {
+ _, _ = writer.Write([]byte(thePage))
+ }
+ } else {
+ writer.Header().Set("Allow", http.MethodOptions+", "+http.MethodGet+", "+http.MethodHead)
+ if request.Method == http.MethodOptions {
+ writeResponseHeaderCanWriteBody(request.Method, writer, http.StatusOK, "")
+ } else {
+ writeResponseHeaderCanWriteBody(request.Method, writer, http.StatusMethodNotAllowed, "")
+ }
+ }
+}
diff --git a/web/utils.go b/web/utils.go
new file mode 100644
index 0000000..382bb95
--- /dev/null
+++ b/web/utils.go
@@ -0,0 +1,24 @@
+package web
+
+import (
+ "net/http"
+ "strconv"
+)
+
+func writeResponseHeaderCanWriteBody(method string, rw http.ResponseWriter, statusCode int, message string) bool {
+ hasBody := method != http.MethodHead && method != http.MethodOptions
+ if hasBody && message != "" {
+ rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
+ rw.Header().Set("X-Content-Type-Options", "nosniff")
+ rw.Header().Set("Content-Length", strconv.Itoa(len(message)+2))
+ }
+ rw.WriteHeader(statusCode)
+ if hasBody {
+ if message != "" {
+ _, _ = rw.Write([]byte(message + "\r\n"))
+ return false
+ }
+ return true
+ }
+ return false
+}
diff --git a/web/web.go b/web/web.go
new file mode 100644
index 0000000..26cecbd
--- /dev/null
+++ b/web/web.go
@@ -0,0 +1,62 @@
+package web
+
+import (
+ "github.com/gorilla/mux"
+ "golang.captainalm.com/GOPackageHeaderServer/conf"
+ "log"
+ "net/http"
+ "strings"
+)
+
+func New(yaml conf.ConfigYaml) (*http.Server, map[string]*PageHandler) {
+ router := mux.NewRouter()
+ var pages = make(map[string]*PageHandler)
+ for _, zc := range yaml.Zones {
+ currentPage := &PageHandler{
+ Name: zc.Name,
+ OutputPage: zc.HavePageContents,
+ MetaOutput: zc.GetPackageMetaTagOutputter(),
+ }
+ for _, d := range zc.Domains {
+ ld := strings.ToLower(d)
+ if _, exists := pages[ld]; !exists {
+ pages[ld] = currentPage
+ router.Host(ld).HandlerFunc(currentPage.ServeHTTP)
+ }
+ }
+ }
+ if yaml.Listen.Identify {
+ router.Use(headerMiddleware)
+ }
+ if yaml.Listen.Web == "" {
+ log.Fatalf("[Http] Invalid Listening Address")
+ }
+ s := &http.Server{
+ Addr: yaml.Listen.Web,
+ Handler: router,
+ ReadTimeout: yaml.Listen.GetReadTimeout(),
+ WriteTimeout: yaml.Listen.GetWriteTimeout(),
+ }
+ go runBackgroundHttp(s)
+ return s, pages
+}
+
+func runBackgroundHttp(s *http.Server) {
+ err := s.ListenAndServe()
+ if err != nil {
+ if err == http.ErrServerClosed {
+ log.Println("The http server shutdown successfully")
+ } else {
+ log.Fatalf("[Http] Error trying to host the http server: %s\n", err.Error())
+ }
+ }
+}
+
+func headerMiddleware(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Server", "Clerie Gilbert")
+ w.Header().Set("X-Powered-By", "Love")
+ w.Header().Set("X-Friendly", "True")
+ next.ServeHTTP(w, r)
+ })
+}