การใช้ Session และ Routing , Middleware
แพ็กเกจ net/http ในภาษา Go มีฟังก์ชันเพื่ออำนวยความสะดวกสำหรับการสร้างและใช้งาน HTTP โพรโทคอล แต่มีเรื่องหนึ่งที่ทำได้ไม่ดีนัก คือถ้าหากเราต้องเจอกับรีเควสต์ที่ซับซ้อน ในบทความนี้เราจะมาเรียนรู้การใช้แพ็กเกจ bmizerany/pat เพื่อสร้างเร้า ตั้งชื่อตัวแปร จัดการกับ GET/POST เมธอด และการจัดการโดเมน การตั้งค่ากำหนด Routing ประกอบด้วยส่วน logical ที่เรียกว่า router หรือ เส้นทาง เป็นที่อยู่ของ respond ในแอป ในการตั้งค่า router ให้เรียกใช้เมธอด HandleFunc() บนอินสแตนซ์ http และกำหนด router เพื่อตอบสนองต่อคำขอ
ข้อกำหนดเบื้องต้น
ข้อกำหนดสำหรับบทความนี้คือ คุณต้องติดตั้ง Go และ ปฏิบัติตามบทความ เว็บแอปพลิเคชันพื้นฐาน มาก่อน
การใช้ Routing pat
ภายในโฟลเดอร์ cmd/web/ สร้างไฟล์ routes.go เขียนโค้ดดังนี้
package main
import (
"modern-web/pkg/config"
"modern-web/pkg/handlers"
"github.com/bmizerany/pat"
"net/http"
)
func routes(app *config.AppConfig) http.Handler {
mux := pat.New()
mux.Get("/", http.HandlerFunc(handlers.Repo.Home))
mux.Get("/about", http.HandlerFunc(handlers.Repo.About))
return mux
}
นำเข้า bmizerany/pat ด้วยคำสั่ง
go get github.com/bmizerany/pat
ไฟล์ main.go แก้ไขโค้ดดังนี้
package main
import (
"modern-web/pkg/config"
"modern-web/pkg/handlers"
"modern-web/pkg/render"
"fmt"
"log"
"net/http"
)
const portNumber = ":8080"
// main is the main function
func main() {
var app config.AppConfig
tc, err := render.CreateTemplateCache()
if err != nil {
log.Fatal("cannot create template cache")
}
app.TemplateCache = tc
app.UseCache = false
repo := handlers.NewRepo(&app)
handlers.NewHandlers(repo)
render.NewTemplates(&app)
fmt.Println(fmt.Sprintf("Staring application on port %s", portNumber))
srv := &http.Server{
Addr: portNumber,
Handler: routes(&app),
}
err = srv.ListenAndServe()
if err != nil {
log.Fatal(err)
}
}
ทดสอบการทำงาน
ทดสอบการทำงาน ด้วยคำสั่ง
go run ./cmd/web/ .
ทดสอบการทำงานโดยไปที่ หน้า Home http://localhost:8080/
และเมื่อเปลี่ยนไปที่ หน้า About http://localhost:8080/about
การใช้ Routing Chi
Chi ทำงานในส่วนของ router สำหรับ REST API โดยที่สนับสนุน context package ของ Go ด้วย ทำการสร้างเอกสารจาก code ในรูปแบบ JSON โดยมีขนาดเล็ก มี code ประมาณ 1,000 บรรทัด และไม่มี depenedncy อื่น ๆ นอกจาก G standard และ net/http package เท่านั้น
ติดตั้ง Chi ด้วยคำสั่ง
go get -u github.com/go-chi/chi
ไฟล์ routes.go เขียนโค้ดดังนี้
package main
import (
"modern-web/pkg/config"
"modern-web/pkg/handlers"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"net/http"
)
func routes(app *config.AppConfig) http.Handler {
mux := chi.NewRouter()
mux.Use(middleware.Recoverer)
mux.Get("/", handlers.Repo.Home)
mux.Get("/about", handlers.Repo.About)
return mux
}
เปิดไฟล์ go.mod แล้วใช้คำสั่ง
go mod tidy
การใช้ Middleware
Middleware (มิดเดิลแวร์) จะทำการรับค่า http.HandlerFunc เป็นตัวแปรตัวหนึ่ง ซึ่งจะทำการครอบตัวฟังก์ชันนั้นเอาไว้และจะส่งค่า http.HandlerFunc ตัวใหม่กลับไปจากเซิร์ฟเวอร์
ภายในโฟลเดอร์ cmd/web/ สร้างไฟล์ middleware.go เขียนโค้ดดังนี้
package main
import (
"fmt"
"github.com/justinas/nosurf"
"net/http"
)
func WriteToConsole(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("Hit the page")
next.ServeHTTP(w, r)
})
}
// NoSurf is the csrf protection middleware
func NoSurf(next http.Handler) http.Handler {
csrfHandler := nosurf.New(next)
csrfHandler.SetBaseCookie(http.Cookie{
HttpOnly: true,
Path: "/",
Secure: false,
SameSite: http.SameSiteLaxMode,
})
return csrfHandler
}
นำเข้า github.com/justinas/nosurf ด้วยคำสั่ง
go get github.com/justinas/nosurf
ไฟล์ routes.go เขียนโค้ดดังนี้
package main
import (
"modern-web/pkg/config"
"modern-web/pkg/handlers"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"net/http"
)
func routes(app *config.AppConfig) http.Handler {
mux := chi.NewRouter()
mux.Use(middleware.Recoverer)
mux.Use(NoSurf)
mux.Get("/", handlers.Repo.Home)
mux.Get("/about", handlers.Repo.About)
return mux
}
Session
Session หรือ เว็บเซสชั่น (Web Session) คือตัวแปรคล้าย คุกกี้ เป็นสิ่งที่ไคลเอนต์ (Client) สร้างขึ้นมาเมื่อเปิดเว็บบราวเซอร์และติดต่อมายังเว็บเซิฟเวอร์ผ่านทางยูอาร์แอล (URL ) ของเว็บไซต์ เมื่อไคลเอนต์ทำการปิดโปรแกรมเว็บบราวเซอร์ เซสชั่นก็จะถูกทำลายหรือปิดลง ข้อมูลที่ถูกเก็บในตัวแปร session จะถูกบันทึกเป็นไฟล์ session
นำเข้า alexedwards/scs ด้วยคำสั่ง
go get github.com/alexedwards/scs/v2
SCS: HTTP Session Management for Go
คุณสมบัติ
- โหลดและบันทึกข้อมูลเซสชันโดยอัตโนมัติผ่านมิดเดิลแวร์
- ตัวเลือกที่เก็บเซสชันฝั่งเซิร์ฟเวอร์ ได้แก่ PostgreSQL, MySQL, MSSQL, SQLite, Redis และอื่นๆ อีกมากมาย รองรับการจัดเก็บเซสชันที่กำหนดเองด้วย
- รองรับหลายเซสชันต่อคำขอ ข้อความ ‘แฟลช’ การสร้างโทเค็นเซสชันใหม่ การหมดเวลาของเซสชันที่ไม่ได้ใช้งานและแน่นอน และฟังก์ชัน ‘จดจำฉัน’
- ง่ายต่อการขยายและปรับแต่ง สื่อสารโทเค็นเซสชันไปยัง/จากไคลเอนต์ในส่วนหัว HTTP หรือเนื้อหาคำขอ/การตอบสนอง
- การออกแบบที่มีประสิทธิภาพ เล็กกว่า เร็วกว่า และใช้หน่วยความจำน้อยกว่ากอริลลา/เซสชัน
การใช้ Session
ไฟล์ config.go
package config
import (
"github.com/alexedwards/scs/v2"
"html/template"
"log"
)
// AppConfig holds the application config
type AppConfig struct {
UseCache bool
TemplateCache map[string]*template.Template
InfoLog *log.Logger
InProduction bool
Session *scs.SessionManager
}
ไฟล์ main.go
package main
import (
"modern-web/pkg/config"
"modern-web/pkg/handlers"
"modern-web/pkg/render"
"fmt"
"github.com/alexedwards/scs/v2"
"log"
"net/http"
"time"
)
const portNumber = ":8080"
var app config.AppConfig
var session *scs.SessionManager
// main is the main function
func main() {
// change this to true when in production
app.InProduction = false
// set up the session
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.TemplateCache = tc
app.UseCache = false
repo := handlers.NewRepo(&app)
handlers.NewHandlers(repo)
render.NewTemplates(&app)
fmt.Println(fmt.Sprintf("Staring application on port %s", portNumber))
srv := &http.Server{
Addr: portNumber,
Handler: routes(&app),
}
err = srv.ListenAndServe()
if err != nil {
log.Fatal(err)
}
}
ไฟล์ middleware.go
package main
import (
"github.com/justinas/nosurf"
"net/http"
)
// NoSurf is the csrf protection middleware
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 session data for current request
func SessionLoad(next http.Handler) http.Handler {
return session.LoadAndSave(next)
}
ไฟล์ handlers.go
package handlers
import (
"modern-web/pkg/config"
"modern-web/pkg/models"
"modern-web/pkg/render"
"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 handler for the home page
func (m *Repository) Home(w http.ResponseWriter, r *http.Request) {
remoteIP := r.RemoteAddr
m.App.Session.Put(r.Context(), "remote_ip", remoteIP)
render.RenderTemplate(w, "home.page.tmpl", &models.TemplateData{})
}
// About is the handler for the about page
func (m *Repository) About(w http.ResponseWriter, r *http.Request) {
// perform some logic
stringMap := make(map[string]string)
stringMap["test"] = "Hello, again"
remoteIP := m.App.Session.GetString(r.Context(), "remote_ip")
stringMap["remote_ip"] = remoteIP
// send data to the template
render.RenderTemplate(w, "about.page.tmpl", &models.TemplateData{
StringMap: stringMap,
})
}
ไฟล์ about.page.tmpl
{{template "base" .}}
{{define "content"}}
<div class="container">
<div class="row">
<div class="col">
<h1>This is the about page</h1>
<p>This is a paragraph of text</p>
<p>This is a paragraph of text</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="/">home page</a> so I can set it.
{{end}}
</p>
</div>
</div>
</div>
{{end}}
ไฟล์ routes.go
package main
import (
"modern-web/pkg/config"
"modern-web/pkg/handlers"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"net/http"
)
func routes(app *config.AppConfig) http.Handler {
mux := chi.NewRouter()
mux.Use(middleware.Recoverer)
mux.Use(NoSurf)
mux.Use(SessionLoad)
mux.Get("/", handlers.Repo.Home)
mux.Get("/about", handlers.Repo.About)
return mux
}
ทดสอบการทำงาน
ทดสอบการทำงาน ด้วยคำสั่ง
go run ./cmd/web/ .
ไปที่ หน้า About http://localhost:8080/about คลิกที่ลิงค์ home page
เมื่อกลับไปที่ หน้า About http://localhost:8080/about อีกครั้ง จะแสดง Session
credit : https://www.udemy.com/course/building-modern-web-applications-with-go/