Compare commits

...

10 Commits

Author SHA1 Message Date
Muyao CHEN
c15e4da93c Experimenting with sessions 2024-06-28 15:41:25 +02:00
Muyao CHEN
8ec31267c6 Installing and setting up a sessions package 2024-06-28 15:30:00 +02:00
Muyao CHEN
9911144aff Developping our own middleware 2024-06-28 13:33:43 +02:00
Muyao CHEN
e1b990dfd3 Using chi for routing 2024-06-28 13:14:05 +02:00
Muyao CHEN
daef31f070 Using pat for routing 2024-06-28 12:59:47 +02:00
Muyao CHEN
b9c8c2592d Sharing data with templates: correct import cycle 2024-06-28 10:48:52 +02:00
Muyao CHEN
668e88e578 Sharing data with templates: cycle import error 2024-06-28 10:40:47 +02:00
Muyao CHEN
1dd22ba8db Optimizing template cache by using an application config *** 2024-06-27 14:03:43 +02:00
Muyao CHEN
2391f5a160 Building a more complex template cache 2024-06-27 13:19:38 +02:00
Muyao CHEN
3eb7a210b2 Building a simple template cache 2024-06-26 22:50:04 +02:00
10 changed files with 269 additions and 13 deletions

View File

@ -2,18 +2,55 @@ package main
import ( import (
"fmt" "fmt"
"go-udemy-web-1/pkg/config"
"go-udemy-web-1/pkg/handlers" "go-udemy-web-1/pkg/handlers"
"go-udemy-web-1/pkg/render"
"log"
"net/http" "net/http"
"time"
"github.com/alexedwards/scs/v2"
) )
const portNumber = ":8080" const portNumber = ":8080"
var (
app config.AppConfig
session *scs.SessionManager
)
// main is the main application function // main is the main application function
func main() { func main() {
http.HandleFunc("/", handlers.Home) // change this to true when in production
http.HandleFunc("/about", handlers.About) app.InProduction = false
session = scs.New()
session.Lifetime = 24 * time.Hour
session.Cookie.Persist = true
session.Cookie.SameSite = http.SameSiteLaxMode
session.Cookie.Secure = app.InProduction
app.Session = session
tc, err := render.CreateTemplateCache()
if err != nil {
log.Fatal("cannot create template cache")
}
app.TemplateCahce = tc
app.UseCache = false
repo := handlers.NewRepo(&app)
handlers.NewHandlers(repo)
render.NewTemplates(&app)
fmt.Printf("Starting application on port %s\n", portNumber) fmt.Printf("Starting application on port %s\n", portNumber)
_ = http.ListenAndServe(portNumber, nil) srv := &http.Server{
Addr: portNumber,
Handler: routes(&app),
}
err = srv.ListenAndServe()
log.Fatal(err)
} }

36
cmd/web/middleware.go Normal file
View File

@ -0,0 +1,36 @@
package main
import (
"fmt"
"net/http"
"github.com/justinas/nosurf"
)
// WriteToConsole writes a log when user hits a page
func WriteToConsole(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("Hit the page %s\n", r.URL.String())
next.ServeHTTP(w, r)
})
}
// NoSurf adds CSRF protection to all POST requests
func NoSurf(next http.Handler) http.Handler {
csrfHandler := nosurf.New(next)
csrfHandler.SetBaseCookie(http.Cookie{
HttpOnly: true,
Path: "/",
Secure: app.InProduction,
SameSite: http.SameSiteLaxMode,
})
return csrfHandler
}
// SessionLoad loads and saves the session on every request
func SessionLoad(next http.Handler) http.Handler {
return session.LoadAndSave(next)
}

24
cmd/web/routes.go Normal file
View File

@ -0,0 +1,24 @@
package main
import (
"go-udemy-web-1/pkg/config"
"go-udemy-web-1/pkg/handlers"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func routes(app *config.AppConfig) http.Handler {
mux := chi.NewMux()
mux.Use(middleware.Recoverer)
mux.Use(WriteToConsole)
mux.Use(NoSurf)
mux.Use(SessionLoad)
mux.Get("/", handlers.Repo.Home)
mux.Get("/about", handlers.Repo.About)
return mux
}

6
go.mod
View File

@ -1,3 +1,9 @@
module go-udemy-web-1 module go-udemy-web-1
go 1.21.0 go 1.21.0
require github.com/go-chi/chi/v5 v5.0.14
require github.com/justinas/nosurf v1.1.1
require github.com/alexedwards/scs/v2 v2.8.0

6
go.sum Normal file
View File

@ -0,0 +1,6 @@
github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw=
github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
github.com/go-chi/chi/v5 v5.0.14 h1:PyEwo2Vudraa0x/Wl6eDRRW2NXBvekgfxyydcM0WGE0=
github.com/go-chi/chi/v5 v5.0.14/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/justinas/nosurf v1.1.1 h1:92Aw44hjSK4MxJeMSyDa7jwuI9GR2J/JCQiaKvXXSlk=
github.com/justinas/nosurf v1.1.1/go.mod h1:ALpWdSbuNGy2lZWtyXdjkYv4edL23oSEgfBT1gPJ5BQ=

15
pkg/config/config.go Normal file
View File

@ -0,0 +1,15 @@
package config
import (
"html/template"
"github.com/alexedwards/scs/v2"
)
// AppConfig holds the application config
type AppConfig struct {
TemplateCahce map[string]*template.Template
UseCache bool
InProduction bool
Session *scs.SessionManager
}

View File

@ -1,16 +1,49 @@
package handlers package handlers
import ( import (
"go-udemy-web-1/pkg/config"
"go-udemy-web-1/pkg/models"
"go-udemy-web-1/pkg/render" "go-udemy-web-1/pkg/render"
"net/http" "net/http"
) )
// Repo the repository used by the handlers
var Repo *Repository
// Repository is the repository type
type Repository struct {
App *config.AppConfig
}
// NewRepo creates a new repository
func NewRepo(a *config.AppConfig) *Repository {
return &Repository{
App: a,
}
}
// NewHandlers sets the repository for the handlers
func NewHandlers(r *Repository) {
Repo = r
}
// Home is the about page handler // Home is the about page handler
func Home(w http.ResponseWriter, r *http.Request) { func (m *Repository) Home(w http.ResponseWriter, r *http.Request) {
render.RenderTemplate(w, "home.page.tmpl") remoteIP := r.RemoteAddr
m.App.Session.Put(r.Context(), "remote_ip", remoteIP)
render.RenderTemplate(w, "home.page.tmpl", &models.TemplateData{})
} }
// About is the about page handler // About is the about page handler
func About(w http.ResponseWriter, r *http.Request) { func (m *Repository) About(w http.ResponseWriter, r *http.Request) {
render.RenderTemplate(w, "about.page.tmpl") // perform some logic
stringMap := make(map[string]string)
stringMap["test"] = "Hello world!"
remoteIP := m.App.Session.GetString(r.Context(), "remote_ip")
stringMap["remote_ip"] = remoteIP
// send the data to the template
render.RenderTemplate(w, "about.page.tmpl", &models.TemplateData{StringMap: stringMap})
} }

View File

@ -0,0 +1,13 @@
package models
// TemplateData holds data sent from handlers to templates
type TemplateData struct {
StringMap map[string]string
IntMap map[string]int
FloatMap map[string]float32
Data map[string]interface{}
CSRFToken string
Flash string
Warning string
Error string
}

View File

@ -1,16 +1,92 @@
package render package render
import ( import (
"fmt" "bytes"
"go-udemy-web-1/pkg/config"
"go-udemy-web-1/pkg/models"
"html/template" "html/template"
"log"
"net/http" "net/http"
"path/filepath"
) )
// renderTemplate renders a HTML template file var app *config.AppConfig
func RenderTemplate(w http.ResponseWriter, tmpl string) {
parsedTemplate, _ := template.ParseFiles("./templates/"+tmpl, "./templates/base.layout.tmpl") // NewTemplates sets the config for the template package
err := parsedTemplate.Execute(w, nil) func NewTemplates(a *config.AppConfig) {
app = a
}
// AddDefaultData adds default template data
func AddDefaultData(td *models.TemplateData) *models.TemplateData {
return td
}
// RenderTemplate renders a HTML template file
func RenderTemplate(w http.ResponseWriter, tmpl string, td *models.TemplateData) {
var tc map[string]*template.Template
if app.UseCache {
// get the template cache from the app config
tc = app.TemplateCahce
} else {
tc, _ = CreateTemplateCache()
}
// get requested template from cache
t, ok := tc[tmpl]
if !ok {
log.Fatal("Could not get template from template cache")
}
// Write to a buffer to make sure that the template can be read and
// written successfully
buf := new(bytes.Buffer)
td = AddDefaultData(td)
err := t.Execute(buf, td)
if err != nil { if err != nil {
fmt.Println("error parsing template:", err) log.Println(err)
}
// render the template
_, err = buf.WriteTo(w)
if err != nil {
log.Println(err)
} }
} }
func CreateTemplateCache() (map[string]*template.Template, error) {
myCache := map[string]*template.Template{}
// get all of the files named *.page.tmpl from ./templates
pages, err := filepath.Glob("./templates/*.page.tmpl")
if err != nil {
return myCache, err
}
// range through all files ending with *page.tmpl
for _, page := range pages {
name := filepath.Base(page)
ts, err := template.New(name).ParseFiles(page)
if err != nil {
return myCache, err
}
matches, err := filepath.Glob("./templates/*.layout.tmpl")
if err != nil {
return myCache, err
}
if len(matches) > 0 {
ts, err = ts.ParseGlob("./templates/*.layout.tmpl")
if err != nil {
return myCache, err
}
}
myCache[name] = ts
}
return myCache, nil
}

View File

@ -5,6 +5,16 @@
<div class="col"> <div class="col">
<h1>This is the about page</h1> <h1>This is the about page</h1>
<p>This is the content</p> <p>This is the content</p>
<p>This came from the template: {{index .StringMap "test"}}</p>
<p>
{{if ne (index .StringMap "remote_ip") ""}}
Your remote IP address is {{index .StringMap "remote_ip"}}
{{else}}
I don't know your ip address yet. Visit the <a href="/"></a> so I can set it.
{{end}}
</p>
</div> </div>
</div> </div>
</div> </div>