Kaynağa Gözat

WIP generation URLs

Frederic G. MARAND 5 yıl önce
ebeveyn
işleme
7e62df9e50

+ 4 - 2
.gitignore

@@ -5,8 +5,10 @@
 *.o
 *.a
 *.so
-cmd/kurz/kurz
-cmd/kurzd/kurzd
+/cmd/kurz/kurz
+/cmd/kurzd/kurzd
+/kurz
+/kurzd
 doc/*/*.svg
 /cover*
 

+ 1 - 1
.idea/runConfigurations/Kurzd_serve.xml

@@ -1,7 +1,7 @@
 <component name="ProjectRunConfigurationManager">
   <configuration default="false" name="Kurzd serve" type="GoApplicationRunConfiguration" factoryName="Go Application" singleton="true" show_console_on_std_err="true" show_console_on_std_out="true">
     <module name="kurz" />
-    <working_directory value="$PROJECT_DIR$/cmd/kurzd" />
+    <working_directory value="$PROJECT_DIR$" />
     <go_parameters value="-o kurzd" />
     <parameters value="serve" />
     <kind value="PACKAGE" />

+ 12 - 0
.idea/runConfigurations/Test_Web.xml

@@ -0,0 +1,12 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Test Web" type="GoTestRunConfiguration" factoryName="Go Test">
+    <module name="kurz" />
+    <working_directory value="$PROJECT_DIR$/" />
+    <framework value="gotest" />
+    <kind value="PACKAGE" />
+    <package value="code.osinet.fr/fgm/kurz/web" />
+    <directory value="$PROJECT_DIR$" />
+    <pattern value="./..." />
+    <method v="2" />
+  </configuration>
+</component>

+ 6 - 1
api/api.go

@@ -21,7 +21,7 @@ Code 451 MAY be replaced by 403, for example when legal censorship includes a
 gag order, super-injunction (UK), National security letter (US) or similar
 mechanisms.
 
-These routes are exposed by running SetupRoutes(address), which is enough to
+These routes are exposed by running SetupRoutes(listenAddress), which is enough to
 configure the Kurz domain API. Be sure to also configure the domain SPI to have
 a complete application.
 */
@@ -29,6 +29,7 @@ package api
 
 import (
 	"github.com/gorilla/mux"
+	"net/url"
 )
 
 // Short is the type of responses provided by the Web API from a target.
@@ -71,3 +72,7 @@ func SetupRoutes(router *mux.Router) {
 		Methods("POST").
 		Name(RoutePostTarget)
 }
+
+func URLFromRoute(name string, args map[string]string) url.URL {
+	return url.URL{}
+}

+ 8 - 6
cmd/kurzd/dist.config.yml

@@ -1,18 +1,20 @@
 api:
-  address: ":8000"
+  listenAddress: ":8000"
 database:
   # Code likely not to work on another engine because it adds ?parseTime=true
   driver: mysql
   dsn: <user>:<pass>@[<server>]/<database>
   test_dsn: <user>:<pass>@[<server>]/<database>
 web:
-  address: ":3000"
-  # May be absolute or relative to the "kurzd" binary.
-  assetsPath: ../../web/public
+  # May be absolute or relative to the current directory.
+  assetsPath: web/public
   # The initial version cache buster on Kurz start. Will probably not remain in config.
   assetsVersion: 1
-  fullyQualifiedSiteBaseURL: &fqsu http://localhost:3000
-  fullyQualifiedAssetsBaseURL: *fqsu
+  listenAddress: ":3000"
   # Delay is in seconds
   refreshDelay: 5
+  # Used to build absolute URLs.
+  siteBaseURL: &sbu http://localhost:3000
   siteName: "Kurz"
+  # Lines needs to be below siteBaseURL for the YAML reference to work.
+  assetsBaseURL: *sbu

+ 1 - 0
cmd/kurzd/rootCmd.go

@@ -14,6 +14,7 @@ var cmd = &cobra.Command{
 	Long: `kurzd is the actual engine for Kurz. Use it as the server for your Kurz clients.
 Configure it by copying dist.config.yml to ~/.kurz/config.yml and editing the copy.`,
 }
+
 var verbose bool
 
 func initConfig() {

+ 20 - 8
cmd/kurzd/serve.go

@@ -1,16 +1,18 @@
 package main
 
 import (
-	"code.osinet.fr/fgm/kurz/web"
 	"context"
 	"database/sql"
-	"github.com/spf13/viper"
+	"fmt"
 	"log"
 	"net/http"
 	"os"
 	"os/signal"
 	"time"
 
+	"code.osinet.fr/fgm/kurz/web"
+	"github.com/spf13/viper"
+
 	"code.osinet.fr/fgm/kurz/api"
 	"code.osinet.fr/fgm/kurz/domain"
 	"code.osinet.fr/fgm/kurz/infrastructure"
@@ -63,27 +65,37 @@ func ensureInfrastructure(db *sql.DB) *sql.DB {
 func serveHandler(_ *cobra.Command, args []string) {
 	db = ensureInfrastructure(db)
 	defer db.Close()
+	cwd, err := os.Getwd()
+	if err != nil {
+		panic(err)
+	}
+	fmt.Printf("Server initializing in %s\n", cwd)
 
-	// This default is the relative position of the assets from the kurzd binary during development.
-	viper.SetDefault("web.assetsPath", "../../web/public")
+	// This default is the relative position of the assets from the project root during development.
+	viper.SetDefault("web.assetsPath", "web/public")
 	assetsPath := viper.Get("web.assetsPath").(string)
 
+	siteBaseURL := viper.Get("web.siteBaseUrl").(string)
+
+	viper.SetDefault("web.assetsBaseURL", siteBaseURL)
+	assetsBaseURL := viper.Get("web.assetsBaseUrl").(string)
+
 	webConfig := viper.Get("web").(map[string]interface{})
 	router := mux.NewRouter()
 	api.SetupRoutes(router)
-	web.SetupRoutes(router, assetsPath)
+	web.SetupRoutes(router, siteBaseURL, assetsBaseURL, assetsPath)
 	web.BuildGlobals(webConfig)
 	http.Handle("/", router)
 
-	address := viper.Get("web.address").(string)
+	listenAddress := viper.Get("web.listenAddress").(string)
 
 	// Start a server that can handle a SIGINT to shutdown.
 	stop := make(chan os.Signal, 1)
 	signal.Notify(stop, os.Interrupt)
 
-	server := &http.Server{Addr: address, Handler: router}
+	server := &http.Server{Addr: listenAddress, Handler: router}
 	go func() {
-		log.Printf("Listening on %s", address)
+		log.Printf("Listening on %s, exposed on %s", listenAddress, siteBaseURL)
 		err := server.ListenAndServe()
 		log.Fatal(err)
 	}()

+ 1 - 0
domain/domain_api.go

@@ -1,6 +1,7 @@
 package domain
 
 func GetTargetURL(shortURL string) (target string, err error) {
+	return "", MakeError(TargetCensoredError, "censored")
 	su := ShortURL{URL: URL(shortURL)}
 	tu, err := shortURLRepository.GetTarget(su)
 	if err != nil {

+ 32 - 11
web/get_short.go

@@ -1,6 +1,7 @@
 package web
 
 import (
+	"fmt"
 	"io"
 	"net/http"
 	"strings"
@@ -12,9 +13,9 @@ import (
 /*
 Template variables:
 
-  - FullyQualifiedAssetsBaseURL http://plusvite-cdn.osinet.eu
+  - assetsBaseURL http://plusvite-cdn.osinet.eu
   - FullyQualifiedShortURL      (short string)
-  - FullyQualifiedSiteURL       http://plusvite.net/
+  - SiteBaseURL       http://plusvite.net/
   - FullyQualifiedTargetURL     (target string)
   - RefreshDelay                (seconds int)
   - SiteName                    PlusVite
@@ -22,7 +23,7 @@ Template variables:
 
 type bytes = []byte
 
-func build403(w http.ResponseWriter, short string) error {
+func build403(w http.ResponseWriter, short string) {
 	data := struct {
 		FullyQualifiedShortURL string
 		Globals
@@ -39,11 +40,9 @@ func build403(w http.ResponseWriter, short string) error {
 		w.WriteHeader(http.StatusForbidden)
 		io.Copy(w, strings.NewReader(sw.String()))
 	}
-
-	return err
 }
 
-func build404(w http.ResponseWriter, short string) error {
+func build404(w http.ResponseWriter, short string) {
 	data := struct {
 		FullyQualifiedShortURL string
 		Globals
@@ -60,15 +59,37 @@ func build404(w http.ResponseWriter, short string) error {
 		w.WriteHeader(http.StatusNotFound)
 		io.Copy(w, strings.NewReader(sw.String()))
 	}
-	return err
 }
 
-func build451(w http.ResponseWriter, short string) error {
-	return nil
+func build451(w http.ResponseWriter, router *mux.Router, short string) {
+	hr := router.Get(RouteGetRoot)
+	_, err := hr.URL()
+	if err != nil {
+		fmt.Println(err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	data := struct {
+		FullyQualifiedShortURL string
+		Globals
+	}{
+		short,
+		globals,
+	}
+
+	sw := &strings.Builder{}
+	err = tmpl.ExecuteTemplate(sw, "404", data)
+	if err != nil {
+		w.WriteHeader(http.StatusInternalServerError)
+	} else {
+		w.WriteHeader(http.StatusNotFound)
+		io.Copy(w, strings.NewReader(sw.String()))
+	}
 }
 
 // handleGetShort handles path /<short>
-func handleGetShort(w http.ResponseWriter, r *http.Request) {
+func handleGetShort(w http.ResponseWriter, r *http.Request, router *mux.Router) {
 	short, ok := mux.Vars(r)["short"]
 	if !ok {
 		w.WriteHeader(http.StatusBadRequest)
@@ -100,7 +121,7 @@ func handleGetShort(w http.ResponseWriter, r *http.Request) {
 		build403(w, short)
 
 	case domain.TargetCensoredError:
-		build451(w, short)
+		build451(w, router, short)
 
 	default:
 		// TargetInvalid is not supposed to happen in this case, so it is an internal error too.

+ 5 - 5
web/templates/201.gohtml

@@ -3,9 +3,9 @@
 <html lang="fr" dir="ltr">
 <head>
     <title>{{.SiteName}}: vers {{.FullyQualifiedTargetURL}}</title>
-    <link rel="shortcut icon" href="{{.FullyQualifiedAssetsBaseURL}}/favicon.ico?v=1">
-    <link rel="stylesheet" type="text/css" href="{{.FullyQualifiedAssetsBaseURL}}/public/css/plusvite.css?v={{.AssetsVersion}}" />
-    <link rel="stylesheet" type="text/css" href="{{.FullyQualifiedAssetsBaseURL}}/public/css/riff.css?v={{.AssetsVersion}}" />
+    <link rel="shortcut icon" href="{{.AssetsBaseURL}}/favicon.ico?v=1">
+    <link rel="stylesheet" type="text/css" href="{{.AssetsBaseURL}}/public/css/plusvite.css?v={{.AssetsVersion}}" />
+    <link rel="stylesheet" type="text/css" href="{{.AssetsBaseURL}}/public/css/riff.css?v={{.AssetsVersion}}" />
     {{template "analytics" .}}
     {{template "inlinecss"}}
 </head>
@@ -18,10 +18,10 @@
         <p>en utilisant le nouvel URL</p>
         <pre><a href="{{.FullyQualifiedShortURL}}">{{.FullyQualifiedShortURL}}</a></pre>
         <p>pour y faire référence.</p>
-        <p><a href="{{.FullyQualifiedSiteURL}}">Retour</a></p>
+        <p><a href="{{.SiteBaseURL}}">Retour</a></p>
     </div>
 
     {{template "footer"}}
 </body>
 </html>
-{{end}}
+{{end}}

+ 6 - 6
web/templates/403.gohtml

@@ -3,12 +3,12 @@
 <html lang="fr" dir="ltr">
 <head>
     <title>{{.SiteName}}: {{.FullyQualifiedShortURL}}</title>
-    <link rel="shortcut icon" href="{{.FullyQualifiedAssetsBaseURL}}/favicon.ico?v=1">
-    <link rel="stylesheet" type="text/css" href="{{.FullyQualifiedAssetsBaseURL}}/public/css/plusvite.css?v={{.AssetsVersion}}" />
-    <link rel="stylesheet" type="text/css" href="{{.FullyQualifiedAssetsBaseURL}}/public/css/riff.css?v={{.AssetsVersion}}" />
+    <link rel="shortcut icon" href="{{.AssetsBaseURL}}/favicon.ico?v=1">
+    <link rel="stylesheet" type="text/css" href="{{.AssetsBaseURL}}/public/css/plusvite.css?v={{.AssetsVersion}}" />
+    <link rel="stylesheet" type="text/css" href="{{.AssetsBaseURL}}/public/css/riff.css?v={{.AssetsVersion}}" />
     {{template "analytics" .}}
     {{template "inlinecss"}}
-    <meta http-equiv="Refresh" content="{{.RefreshDelay}};{{.FullyQualifiedSiteURL}}" />
+    <meta http-equiv="Refresh" content="{{.RefreshDelay}};{{.SiteBaseURL}}" />
 </head>
 
 <body>
@@ -16,10 +16,10 @@
         <h1>{{.FullyQualifiedShortURL}}</h1>
         <p>L'URL de destination est valide mais a été bloqué par son titulaire.</p>
         <p>Dans quelques secondes, vous serez redirigé vers la page d'accueil du site.</p>
-        <p><a href="{{.FullyQualifiedSiteURL}}">Retour</a></p>
+        <p><a href="{{.SiteBaseURL}}">Retour</a></p>
     </div>
 
     {{template "footer"}}
 </body>
 </html>
-{{end}}
+{{end}}

+ 7 - 7
web/templates/404.gohtml

@@ -3,23 +3,23 @@
 <html lang="fr" dir="ltr">
 <head>
     <title>{{.SiteName}}: {{.FullyQualifiedShortURL}}</title>
-    <link rel="shortcut icon" href="{{.FullyQualifiedAssetsBaseURL}}/favicon.ico?v=1">
-    <link rel="stylesheet" type="text/css" href="{{.FullyQualifiedAssetsBaseURL}}/public/css/plusvite.css?v={{.Globals.AssetsVersion}}" />
-    <link rel="stylesheet" type="text/css" href="{{.FullyQualifiedAssetsBaseURL}}/public/css/riff.css?v={{.Globals.AssetsVersion}}" />
+    <link rel="shortcut icon" href="{{.AssetsBaseURL}}/favicon.ico?v=1">
+    <link rel="stylesheet" type="text/css" href="{{.AssetsBaseURL}}/public/css/plusvite.css?v={{.Globals.AssetsVersion}}" />
+    <link rel="stylesheet" type="text/css" href="{{.AssetsBaseURL}}/public/css/riff.css?v={{.Globals.AssetsVersion}}" />
     {{template "analytics" .}}
     {{template "inlinecss"}}
-    <meta http-equiv="Refresh" content="{{.RefreshDelay}};{{.FullyQualifiedSiteURL}}" />
+    <meta http-equiv="Refresh" content="{{.RefreshDelay}};{{.SiteBaseURL}}" />
 </head>
 
 <body>
     <div id="box">
         <h1>{{.FullyQualifiedShortURL}}</h1>
-        <p>Ceci n'est pas un URL <a href="{{.FullyQualifiedSiteURL}}">{{.SiteName}}</a> valide.</p>
+        <p>Ceci n'est pas un URL <a href="{{.SiteBaseURL}}">{{.SiteName}}</a> valide.</p>
         <p>Dans quelques secondes, vous serez redirigé vers la page d'accueil du site.</p>
-        <p><a href="{{.FullyQualifiedSiteURL}}">Retour</a></p>
+        <p><a href="{{.SiteBaseURL}}">Retour</a></p>
     </div>
 
     {{template "footer"}}
 </body>
 </html>
-{{end}}
+{{end}}

+ 6 - 6
web/templates/451.gohtml

@@ -3,12 +3,12 @@
 <html lang="fr" dir="ltr">
 <head>
     <title>{{.SiteName}}: {{.FullyQualifiedShortURL}}</title>
-    <link rel="shortcut icon" href="{{.FullyQualifiedAssetsBaseURL}}/favicon.ico?v=1">
-    <link rel="stylesheet" type="text/css" href="{{.FullyQualifiedAssetsBaseURL}}/public/css/plusvite.css?v={{.AssetsVersion}}" />
-    <link rel="stylesheet" type="text/css" href="{{.FullyQualifiedAssetsBaseURL}}/public/css/riff.css?v={{.AssetsVersion}}" />
+    <link rel="shortcut icon" href="{{.AssetsBaseURL}}/favicon.ico?v=1">
+    <link rel="stylesheet" type="text/css" href="{{.AssetsBaseURL}}/public/css/plusvite.css?v={{.AssetsVersion}}" />
+    <link rel="stylesheet" type="text/css" href="{{.AssetsBaseURL}}/public/css/riff.css?v={{.AssetsVersion}}" />
     {{template "analytics" .}}
     {{template "inlinecss"}}
-    <meta http-equiv="Refresh" content="{{.RefreshDelay}};{{.FullyQualifiedSiteURL}}" />
+    <meta http-equiv="Refresh" content="{{.RefreshDelay}};{{.SiteBaseURL}}" />
 </head>
 
 <body>
@@ -16,10 +16,10 @@
         <h1>{{.FullyQualifiedShortURL}}</h1>
         <p>L'URL de destination est valide mais a été bloqué par décision légale.</p>
         <p>Dans quelques secondes, vous serez redirigé vers la page d'accueil du site.</p>
-        <p><a href="{{.FullyQualifiedSiteURL}}">Retour</a></p>
+        <p><a href="{{.SiteBaseURL}}">Retour</a></p>
     </div>
 
     {{template "footer"}}
 </body>
 </html>
-{{end}}
+{{end}}

+ 3 - 3
web/templates/home.gohtml

@@ -3,9 +3,9 @@
 <html lang="fr" dir="ltr">
 <head>
     <title>Plus Vite sur le Net: service gratuit d'URLs raccourcis</title>
-    <link rel="shortcut icon" href="{{.FullyQualifiedAssetsBaseURL}}/favicon.ico?v=1">
-    <link rel="stylesheet" type="text/css" href="{{.FullyQualifiedAssetsBaseURL}}/public/css/plusvite.css?v={{.AssetsVersion}}" />
-    <link rel="stylesheet" type="text/css" href="{{.FullyQualifiedAssetsBaseURL}}/public/css/riff.css?v={{.AssetsVersion}}" />
+    <link rel="shortcut icon" href="{{.AssetsBaseURL}}/favicon.ico?v=1">
+    <link rel="stylesheet" type="text/css" href="{{.AssetsBaseURL}}/public/css/plusvite.css?v={{.AssetsVersion}}" />
+    <link rel="stylesheet" type="text/css" href="{{.AssetsBaseURL}}/public/css/riff.css?v={{.AssetsVersion}}" />
     {{template "analytics" .}}
     {{template "inlinecss"}}
 </head>

+ 49 - 15
web/web.go

@@ -1,7 +1,7 @@
 /*
 The Kurz Web UI exposes HTTP routes for browsers, route names to access them, and types for the requests.
 
-These routes are exposed by running SetupRoutes(address), which is enough to
+These routes are exposed by running SetupRoutes(listenAddress), which is enough to
 configure the Kurz domain API. Be sure to also configure the domain SPI to have
 a complete application.
 */
@@ -10,6 +10,7 @@ package web
 import (
 	"html/template"
 	"net/http"
+	"net/url"
 	"path/filepath"
 
 	"github.com/gorilla/mux"
@@ -31,20 +32,20 @@ const (
 	HtmlTypeRegex = HtmlType
 )
 
-var tmpl *template.Template
-
 type Globals struct {
-	AssetsVersion               int
-	FullyQualifiedAssetsBaseURL string
-	FullyQualifiedSiteBaseURL   string
-	RefreshDelay                int
-	SiteName                    string
+	AssetsBaseURL string
+	AssetsVersion int
+	AssetsPath    string
+	SiteBaseURL   string
+	RefreshDelay  int
+	SiteName      string
 }
 
 var globals Globals
+var tmpl *template.Template
 
 // SetupRoutes() configures Web UI routes on the passed mux.Router.
-func SetupRoutes(router *mux.Router, configAssetsPath string) {
+func SetupRoutes(router *mux.Router, configSiteBaseURL, configAssetsBaseURL, configAssetsPath string) {
 	const assetsPrefix = "/public"
 	absAssetsDir, err := filepath.Abs(configAssetsPath)
 	if err != nil {
@@ -55,7 +56,9 @@ func SetupRoutes(router *mux.Router, configAssetsPath string) {
 	router.Handle("/favicon.ico", fs)
 
 	// BUG(fgm): improve Accept header matchers once https://github.com/golang/go/issues/19307 is completed.
-	router.HandleFunc("/{short}", handleGetShort).
+	router.HandleFunc("/{short}", func(w http.ResponseWriter, r *http.Request) {
+		handleGetShort(w, r, router)
+	}).
 		Methods("GET", "HEAD").
 		Name(RouteGetShort)
 	router.HandleFunc("/", handlePostTarget).
@@ -82,10 +85,41 @@ func SetupRoutes(router *mux.Router, configAssetsPath string) {
 func BuildGlobals(c map[string]interface{}) {
 	// Note: keys in viper are lower-cased.
 	globals = Globals{
-		AssetsVersion:               c["assetsversion"].(int),
-		FullyQualifiedAssetsBaseURL: c["fullyqualifiedassetsbaseurl"].(string),
-		FullyQualifiedSiteBaseURL:   c["fullyqualifiedsitebaseurl"].(string),
-		RefreshDelay:                c["refreshdelay"].(int),
-		SiteName:                    c["sitename"].(string),
+		AssetsBaseURL: c["assetsbaseurl"].(string),
+		AssetsPath:    c["assetspath"].(string),
+		AssetsVersion: c["assetsversion"].(int),
+		RefreshDelay:  c["refreshdelay"].(int),
+		SiteBaseURL:   c["sitebaseurl"].(string),
+		SiteName:      c["sitename"].(string),
+	}
+}
+
+/*
+URLFromRoute generates absolute URLs for named routes.
+
+To build URLs for assets, use URLForAsset().
+
+  - ns: the assets namespace. One of "js", "css', "images".
+  - path: the asset path relative to the project root
+ */
+func URLFromRoute(router mux.Router, name string, params map[string]string) string {
+	return ""
+}
+
+/*
+URLFromRoute generates absolute URLs for assets.
+
+To build URLs for routes, use URLFromRoute().
+
+  - ns: the assets namespace. One of "js", "css', "images".
+  - path: the asset path relative to the project root
+ */
+func URLForAsset(ns string, path string) string {
+	base, err := url.Parse(globals.AssetsBaseURL)
+	if err != nil {
+		panic(err)
 	}
+	base.Path = path
+	res := base.String()
+	return res
 }

+ 38 - 0
web/web_test.go

@@ -0,0 +1,38 @@
+package web
+
+import (
+	"testing"
+)
+
+// Not a Test function, a test helper.
+func testSetupGlobals() {
+	globals = Globals{
+		AssetsBaseURL: "https://cdn.osinet.fr",
+		AssetsVersion: 1,
+		AssetsPath:    "web/public",
+		SiteBaseURL:   "http://sut.kurz.osinet.fr",
+		RefreshDelay:  1,
+		SiteName:      "Kurz test site",
+	}
+}
+
+func TestURLForAsset(t *testing.T) {
+	testSetupGlobals()
+	checks := [][4]string{
+		// Name, NS, Path, Expected.
+		{"empty css path", "css", "", "https://cdn.osinet.fr/"},
+		{"root css path", "css", "/", "https://cdn.osinet.fr/"},
+	}
+	for _, check := range checks {
+		name := check[0]
+		ns := check[1]
+		path := check[2]
+		expected := check[3]
+		t.Run(name, func (t *testing.T) {
+			actual := URLForAsset(ns, path)
+			if actual != expected {
+				t.Fail()
+			}
+		})
+	}
+}