Implement program.
All checks were successful
continuous-integration/drone Build is passing

This commit is contained in:
Captain ALM 2022-07-11 21:47:22 +01:00
parent 2aefb9639b
commit b56c440ab7
Signed by: alfred
GPG Key ID: 4E4ADD02609997B1
12 changed files with 421 additions and 3 deletions

View File

@ -0,0 +1,36 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="HttpUrlsUsage" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredUrls">
<list>
<option value="http://localhost" />
<option value="http://127.0.0.1" />
<option value="http://0.0.0.0" />
<option value="http://www.w3.org/" />
<option value="http://json-schema.org/draft" />
<option value="http://java.sun.com/" />
<option value="http://xmlns.jcp.org/" />
<option value="http://javafx.com/javafx/" />
<option value="http://javafx.com/fxml" />
<option value="http://maven.apache.org/xsd/" />
<option value="http://maven.apache.org/POM/" />
<option value="http://www.springframework.org/schema/" />
<option value="http://www.springframework.org/tags" />
<option value="http://www.springframework.org/security/tags" />
<option value="http://www.thymeleaf.org" />
<option value="http://www.jboss.org/j2ee/schema/" />
<option value="http://www.jboss.com/xml/ns/" />
<option value="http://www.ibm.com/webservices/xsd" />
<option value="http://activemq.apache.org/schema/" />
<option value="http://schema.cloudfoundry.org/spring/" />
<option value="http://schemas.xmlsoap.org/" />
<option value="http://cxf.apache.org/schemas/" />
<option value="http://primefaces.org/ui" />
<option value="http://tiles.apache.org/" />
<option value="http://" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

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

View File

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

6
conf/config.go Normal file
View File

@ -0,0 +1,6 @@
package conf
type ConfigYaml struct {
Listen ListenYaml `yaml:"listen"`
Zones []ZoneYaml `yaml:"zones"`
}

26
conf/listen.go Normal file
View File

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

29
conf/zone.go Normal file
View File

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

8
go.mod
View File

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

8
go.sum Normal file
View File

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

View File

@ -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 "<meta name=\"go-import\" content=\"" + pkgMTO.getPrefix(pathIn) + " git " + pkgMTO.getHomeURL(pathIn) + "\">\r\n" +
"<meta name=\"go-source\" content=\"" + pkgMTO.getPrefix(pathIn) + " " + pkgMTO.getHomeURL(pathIn) + " " +
pkgMTO.getDirectoryURL(pathIn) + " " + pkgMTO.getFileURL(pathIn) + "\">"
}
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), "/")
}
}

50
web/pagehandler.go Normal file
View File

@ -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 := "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n"
if pgh.OutputPage && pgh.Name != "" {
thePage += "<title>Go Package: " + pgh.Name + "</title>\r\n"
}
thePage += pgh.MetaOutput.GetMetaTags(request.URL.Path) + "\r\n</head>\r\n<body>\r\n"
if pgh.OutputPage {
if pgh.Name != "" {
thePage += "<h1>Go Package: " + pgh.Name + "</h1>\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 += "<a href=\"" + theLink + "\">" + theLink + "</a>\r\n"
}
thePage += "</body>\r\n</html>\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, "")
}
}
}

24
web/utils.go Normal file
View File

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

62
web/web.go Normal file
View File

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