การใช้ 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/

Leave a Reply

Your email address will not be published. Required fields are marked *