Let's Go حالت دار HTTP › راه‌اندازی مدیر جلسه
قبلی · فهرست · بعدی
فصل ۸.۲.

راه‌اندازی مدیر جلسه (Setting Up the Session Manager)

در این بخش، نحوه راه‌اندازی مدیر جلسه (Setting Up Session Manager) را بررسی می‌کنیم. این شامل پیکربندی (Configuration) و مقداردهی اولیه (Initialization) مدیر جلسه می‌شود.

برای شروع، بیایید تنظیمات جلسه (Session Settings) را در ساختار برنامه (Application Structure) خود اضافه کنیم:

در این فصل، به اصول راه‌اندازی و استفاده از بسته 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);

در این جدول:

کار بعدی که باید انجام دهیم، ایجاد یک مدیر نشست در فایل main.go و در دسترس قرار دادن آن برای هندلرها از طریق ساختار application است. مدیر نشست تنظیمات پیکربندی نشست‌های ما را نگه می‌دارد و همچنین برخی از میان‌افزارها و روش‌های کمکی را برای بارگذاری و ذخیره داده‌های نشست فراهم می‌کند.

فایل main.go خود را باز کنید و به صورت زیر به‌روزرسانی کنید:

File: cmd/web/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 را باز کنید و به صورت زیر به‌روزرسانی کنید:

File: cmd/web/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 برای مدیریت زنجیره‌های میان‌افزار خود استفاده نمی‌کنید، باید از آداپتور http.HandlerFunc() برای تبدیل توابع هندلر خود مانند 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

واژه‌نامه اصطلاحات فنی

اصطلاح فارسی معادل انگلیسی توضیح
راه‌اندازی مدیر جلسه Setting Up Session Manager آماده‌سازی و پیکربندی مدیر جلسه
پیکربندی Configuration تنظیمات و پارامترهای سیستم
مقداردهی اولیه Initialization راه‌اندازی اولیه سیستم
تنظیمات جلسه Session Settings پارامترهای پیکربندی جلسه
ساختار برنامه Application Structure ساختار و سازماندهی کد برنامه
میان‌افزار جلسه Session Middleware کد واسط برای مدیریت جلسه
کوکی‌های جلسه Session Cookies کوکی‌های مربوط به جلسه
زمان انقضا Expiry Time مدت زمان اعتبار جلسه
امنیت جلسه Session Security حفاظت از داده‌های جلسه
مسیریابی Routing هدایت درخواست‌ها به هندلرها