راهاندازی مدیر نشست
در این فصل اصول راهاندازی و استفاده از بسته alexedwards/scs را مرور میکنم، اما اگر میخواهید از آن در یک برنامه تولیدی استفاده کنید، توصیه میکنم مستندات و مرجع API را بخوانید تا با طیف کامل ویژگیها آشنا شوید.
اولین کاری که باید انجام دهیم، ایجاد یک جدول sessions در پایگاه داده MySQL خود برای نگهداری دادههای نشست کاربران ما است. با اتصال به MySQL از پنجره ترمینال خود به عنوان کاربر root شروع کنید و دستور SQL زیر را برای راهاندازی جدول sessions اجرا کنید:
USE snippetbox; CREATE TABLE sessions ( token CHAR(43) PRIMARY KEY, data BLOB NOT NULL, expiry TIMESTAMP(6) NOT NULL ); CREATE INDEX sessions_expiry_idx ON sessions (expiry);
در این جدول:
فیلد
tokenشامل یک شناسه یکتا و تصادفی برای هر نشست خواهد بود.فیلد
dataشامل دادههای نشست واقعی است که میخواهید بین درخواستهای HTTP به اشتراک بگذارید. این به عنوان داده باینری در یک نوعBLOB(شیء بزرگ باینری) ذخیره میشود.فیلد
expiryشامل زمان انقضا برای نشست خواهد بود. بستهscsبه طور خودکار نشستهای منقضی شده را از جدولsessionsحذف میکند تا خیلی بزرگ نشود.
چیز بعدی که باید انجام دهیم، ایجاد یک مدیر نشست در فایل main.go خود و در دسترس قرار دادن آن برای handlerهای ما از طریق ساختار application است. مدیر نشست تنظیمات پیکربندی برای نشستهای ما را نگه میدارد و همچنین برخی میدلورها و متدهای کمککننده برای مدیریت بارگذاری و ذخیره دادههای نشست ارائه میدهد.
فایل main.go خود را باز کنید و آن را به این صورت بهروزرسانی کنید:
package main import ( "database/sql" "flag" "html/template" "log/slog" "net/http" "os" "time" // New import "snippetbox.alexedwards.net/internal/models" "github.com/alexedwards/scs/mysqlstore" // New import "github.com/alexedwards/scs/v2" // New import "github.com/go-playground/form/v4" _ "github.com/go-sql-driver/mysql" ) // Add a new sessionManager field to the application struct. type application struct { logger *slog.Logger snippets *models.SnippetModel templateCache map[string]*template.Template formDecoder *form.Decoder sessionManager *scs.SessionManager } func main() { addr := flag.String("addr", ":4000", "HTTP network address") dsn := flag.String("dsn", "web:pass@/snippetbox?parseTime=true", "MySQL data source name") flag.Parse() logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) db, err := openDB(*dsn) if err != nil { logger.Error(err.Error()) os.Exit(1) } defer db.Close() templateCache, err := newTemplateCache() if err != nil { logger.Error(err.Error()) os.Exit(1) } formDecoder := form.NewDecoder() // Use the scs.New() function to initialize a new session manager. Then we // configure it to use our MySQL database as the session store, and set a // lifetime of 12 hours (so that sessions automatically expire 12 hours // after first being created). sessionManager := scs.New() sessionManager.Store = mysqlstore.New(db) sessionManager.Lifetime = 12 * time.Hour // And add the session manager to our application dependencies. app := &application{ logger: logger, snippets: &models.SnippetModel{DB: db}, templateCache: templateCache, formDecoder: formDecoder, sessionManager: sessionManager, } logger.Info("starting server", "addr", *addr) err = http.ListenAndServe(*addr, app.routes()) logger.Error(err.Error()) os.Exit(1) } ...
برای کار کردن نشستها، همچنین باید مسیرهای برنامه خود را با میدلور ارائه شده توسط متد SessionManager.LoadAndSave() بپیچیم. این میدلور به طور خودکار دادههای نشست را با هر درخواست و پاسخ HTTP بارگذاری و ذخیره میکند.
مهم است که توجه داشته باشید که نیازی نیست این میدلور روی همه مسیرهای برنامه ما عمل کند. به طور خاص، نیازی به آن در مسیر GET /static/ نداریم، چون این فقط فایلهای استاتیک را سرو میکند و نیازی به هیچ رفتار با حالت نیست.
بنابراین، به همین دلیل، اضافه کردن میدلور نشست به زنجیره میدلور موجود standard ما منطقی نیست.
در عوض، بیایید یک زنجیره میدلور جدید dynamic ایجاد کنیم که شامل میدلور مناسب برای مسیرهای برنامه پویای ما فقط باشد.
فایل routes.go را باز کنید و آن را به این صورت بهروزرسانی کنید:
package main import ( "net/http" "github.com/justinas/alice" ) func (app *application) routes() http.Handler { mux := http.NewServeMux() // Leave the static files route unchanged. fileServer := http.FileServer(http.Dir("./ui/static/")) mux.Handle("GET /static/", http.StripPrefix("/static", fileServer)) // Create a new middleware chain containing the middleware specific to our // dynamic application routes. For now, this chain will only contain the // LoadAndSave session middleware but we'll add more to it later. dynamic := alice.New(app.sessionManager.LoadAndSave) // Update these routes to use the new dynamic middleware chain followed by // the appropriate handler function. Note that because the alice ThenFunc() // method returns a http.Handler (rather than a http.HandlerFunc) we also // need to switch to registering the route using the mux.Handle() method. mux.Handle("GET /{$}", dynamic.ThenFunc(app.home)) mux.Handle("GET /snippet/view/{id}", dynamic.ThenFunc(app.snippetView)) mux.Handle("GET /snippet/create", dynamic.ThenFunc(app.snippetCreate)) mux.Handle("POST /snippet/create", dynamic.ThenFunc(app.snippetCreatePost)) standard := alice.New(app.recoverPanic, app.logRequest, commonHeaders) return standard.Then(mux) }
اگر الان برنامه را اجرا کنید، باید ببینید که همه چیز به درستی کامپایل میشود و مسیرهای برنامه شما به طور عادی ادامه به کار میدهند.
اطلاعات اضافی
بدون استفاده از alice
اگر از بسته justinas/alice برای کمک به مدیریت زنجیرههای میدلور خود استفاده نمیکنید، آنگاه باید از adapter http.HandlerFunc() برای تبدیل توابع handler خود مانند app.home به یک http.Handler استفاده کنید، و سپس آن را با میدلور نشست بپیچید. به این صورت:
mux := http.NewServeMux() mux.Handle("GET /{$}", app.sessionManager.LoadAndSave(http.HandlerFunc(app.home))) mux.Handle("GET /snippet/view/:id", app.sessionManager.LoadAndSave(http.HandlerFunc(app.snippetView))) // ... etc