xun

package module
v1.1.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 31, 2026 License: Apache-2.0 Imports: 27 Imported by: 1

README

Xun AI Agent Specification

Status: AUTHORITATIVE

Before writing any Xun code, read this entire document. All guidance must be derived from this file — do not rely on prior knowledge of gin/echo/chi.


Section 0 — Critical Rules (Read Before Writing Any Code)

These rules are numbered. All other sections reference them by number.

Rule 0.1 — NEVER call WithHandlerViewers() with no arguments
// WRONG — compiles but sets app.handlerViewers = nil
xun.New(xun.WithHandlerViewers())

// CORRECT — pass at least one Viewer
xun.New(xun.WithHandlerViewers(&xun.JsonViewer{}))

Consequence: app.handlerViewers == nil → all handler routes get r.Viewers == nilc.View(data) returns ErrViewNotFound → HTTP 404.

Rule 0.2 — NEVER write response body directly
// WRONG — bypasses compression and BufPool
c.Response.Write([]byte("hello"))
return nil

// CORRECT — always use c.View()
return c.View("hello")  // with StringViewer registered
Rule 0.3 — NEVER return an error from middleware when refusing
// WRONG — returns 500 + X-Log-Id
func refuseMiddleware(next xun.HandleFunc) xun.HandleFunc {
    return func(c *xun.Context) error {
        if !allowed {
            return errors.New("forbidden")
        }
        return next(c)
    }
}

// CORRECT — set status and return ErrCancelled
func refuseMiddleware(next xun.HandleFunc) xun.HandleFunc {
    return func(c *xun.Context) error {
        if !allowed {
            c.WriteStatus(http.StatusForbidden)
            return xun.ErrCancelled
        }
        return next(c)
    }
}
Rule 0.4 — app.Start() does NOT start the server
// WRONG — gin habit
app.Run(":8080")

// CORRECT
app := xun.New(opts...)
app.Start()                        // only prints route logs
defer app.Close()
http.ListenAndServe(":80", mux)    // server startup is caller's responsibility
Rule 0.5 — Named viewer MUST match Accept header or silently falls back
// Route: r.Viewers = [JsonViewer]
// Request: Accept: application/json

return c.View(user, "views/user/profile")  // views/user/profile is HtmlViewer
// HtmlViewer (text/html) does NOT match Accept (application/json)
// → falls back to JsonViewer (r.Viewers[0]), NOT the named viewer
Rule 0.6 — pages/* auto-registers GET only
// File: pages/admin/dashboard.html
// Route: GET /admin/dashboard     ← GET only, no POST/PUT/DELETE auto-registered
// To handle POST, register explicitly:
app.Post("/admin/dashboard", handler)
Rule 0.7 — {$} means trailing slash required
app.Get("/posts/{$}")   // matches GET /posts/  ONLY
app.Get("/posts/")       // matches GET /posts/abc, GET /posts/123
app.Get("/posts")        // matches GET /posts  ONLY (no slash)

Section 1 — Types

HandleFunc       = func(c *Context) error
Middleware       = func(next HandleFunc) HandleFunc
Option           = func(*App)
RoutingOption    = func(*RoutingOptions)
chain            = interface{ Next(hf HandleFunc) HandleFunc }

HandleFunc returns error, not nil. See Section 10 for error meanings.


Section 2 — App

2.1 Creation
app := xun.New(opts ...Option) *App
2.2 Fields
Field Type Default Overridden-By Nil-Result
app.mux *http.ServeMux http.DefaultServeMux WithMux(mux)
app.handlerViewers []Viewer []Viewer{&JsonViewer{}} WithHandlerViewers(v...) All handler routes return 404 (Rule 0.1)
app.fsys fs.FS nil WithFsys(fsys) Page routing disabled
app.watch bool false WithWatch() Hot reload disabled
app.interceptor Interceptor nil WithInterceptor(i) Redirect/RequestReferer use defaults
app.compressors []Compressor nil WithCompressor(c...) No compression
app.viewers map[string]Viewer empty map HtmlViewEngine.Load() registers views/* Named viewers unavailable
app.funcMap template.FuncMap xun.builtins WithTemplateFunc, WithTemplateFuncMap Builtin asset func unavailable
app.routes map[string]*Routing empty map app.Get/Post/etc, app.HandlePage
2.3 App.Start()
app.Start()

Writes info-level logs for each registered route (pattern + viewer MIME types). Does NOT start the HTTP server. Server startup is the caller's responsibility.

2.4 App.Close()

Currently a no-op. Reserved for future use.

2.5 Option Functions
WithMux(mux *http.ServeMux) Option
WithFsys(fsys fs.FS) Option
WithWatch() Option                    // dev only — not thread-safe
WithHandlerViewers(v ...Viewer) Option
WithViewEngines(ve ...ViewEngine) Option
WithInterceptor(i Interceptor) Option
WithCompressor(c ...Compressor) Option
WithTemplateFunc(name string, fn any) Option
WithTemplateFuncMap(fm template.FuncMap) Option
WithBuildAssetURL(match func(string) bool) Option
WithLogger(logger *slog.Logger) Option
2.6 Route Registration
app.Get(pattern string, hf HandleFunc, opts ...RoutingOption)
app.Post(pattern string, hf HandleFunc, opts ...RoutingOption)
app.Put(pattern string, hf HandleFunc, opts ...RoutingOption)
app.Delete(pattern string, hf HandleFunc, opts ...RoutingOption)
app.Group(prefix string) *group

Pattern format: "METHOD pattern" (e.g., "GET /users/{id}"). Go 1.22 ServeMux syntax.


Section 3 — Group

group implements chain.

func (g *group) Use(middleware ...Middleware)
func (g *group) Get(pattern string, hf HandleFunc, opts ...RoutingOption)
func (g *group) HandleFunc(pattern string, hf HandleFunc, opts ...RoutingOption)
func (g *group) Next(hf HandleFunc) HandleFunc

Middleware chain construction (inside-out):

// given [A, B, C] and handler H:
// build: C(B(A(H)))
next := H
for i := len(g.middlewares); i > 0; i-- {
    next = g.middlewares[i-1](next)
}

Section 4 — Middleware

Middleware signature: func(next HandleFunc) HandleFunc

func AuthMiddleware(next xun.HandleFunc) xun.HandleFunc {
    return func(c *xun.Context) error {
        // pre logic
        token := c.Request.Header.Get("X-Token")
        if token == "" {
            c.WriteStatus(http.StatusUnauthorized)
            return xun.ErrCancelled
        }
        err := next(c)
        // post logic (runs after handler)
        return err
    }
}

Pre-logic: runs before next(c). Post-logic: runs after next(c) returns. On refusal: ALWAYS set status + return xun.ErrCancelled (Rule 0.3).


Section 5 — Context

Context wraps *http.Request, ResponseWriter, and application state.

5.1 Fields
c.Request  *http.Request    // standard library
c.Response ResponseWriter   // xun interface (extends http.ResponseWriter)
c.Routing  Routing          // route metadata
c.App      *App            // application instance
c.TempData TempData        // map[string]any, request-scoped storage
5.2 Standard Library Equivalents

Use standard library directly for these:

c.Request.PathValue("id")              // path parameter (Go 1.22+)
c.Request.URL.Query().Get("name")     // query string
c.Request.Header.Get("X-Token")       // headers
c.Request.Cookie("session_id")        // read cookie
c.Request.Body                         // request body
c.Request.ParseMultipartForm()         // multipart form
c.Request.Context()                    // context.Context
c.Response.Header().Set(k, v)         // response headers
http.SetCookie(c.Response, &cookie)   // write cookie
c.Request.RemoteAddr                   // client address (no proxy support; use ext/proxyproto)
5.3 xun-Specific Methods
c.View(data any, options ...string) error
c.Redirect(url string, statusCode ...int)
c.AcceptLanguage() []string
c.Accept() []MimeType
c.RequestReferer() string
c.WriteStatus(code int)
c.WriteHeader(key string, value string)
c.Get(key string) any
c.Set(key string, value any)
5.4 c.View(data any, options ...string) Behavior
IF options[0] is provided (named viewer name):
  → getViewer(name) checks: named viewer.MimeType() matches any Accept header
  → IF match: use named viewer
  → IF no match: proceed to step 2

ELSE skip to step 2.

STEP 2: Iterate Accept headers, match against r.Viewers:
  → First matching viewer is used

STEP 3: No match found:
  → Use r.Viewers[0] as fallback

STEP 4: r.Viewers is empty at this point:
  → Return ErrViewNotFound → HTTP 404

c.View() sets status 200 automatically. Call c.WriteStatus() before c.View() to override.

5.5 c.Redirect(url string, statusCode ...int)

Sets Location header. Default status: http.StatusFound (302). Interceptor can override if configured.


Section 6 — Routing

6.1 Routing Fields
type Routing struct {
    Pattern string
    Handle  HandleFunc
    chain   chain           // *App or *group
    Options *RoutingOptions
    Viewers []Viewer       // viewers for this route
}
6.2 Routing.Next(ctx)
func (r *Routing) Next(ctx *Context) error {
    return r.chain.Next(r.Handle)(ctx)
}
6.3 RoutingOptions Fields
type RoutingOptions struct {
    metadata map[string]any
    viewers  []Viewer
}
6.4 RoutingOption Functions
WithViewer(v ...Viewer) RoutingOption
WithMetadata(key string, value any) RoutingOption
WithNavigation(name, icon, access string) RoutingOption

Section 7 — Viewer

7.1 Interface
type Viewer interface {
    MimeType() *MimeType
    Render(ctx *Context, data any) error
}
7.2 Built-in Viewers
Viewer MimeType Default For
HtmlViewer text/html Page routes
JsonViewer application/json Handler routes (only if app.handlerViewers not overridden)
TextViewer text/* (from filename) Text templates
XmlViewer text/xml
StringViewer text/plain
FileViewer */* Static files
7.3 Implementing a Viewer
type MyViewer struct{}

func (*MyViewer) MimeType() *xun.MimeType {
    return &xun.MimeType{Type: "application", SubType: "json"}
}

func (*MyViewer) Render(c *xun.Context, data any) error {
    c.Response.Header().Set("Content-Type", "application/json")
    buf := xun.BufPool.Get()
    defer xun.BufPool.Put(buf)
    // render JSON to buf
    json.NewEncoder(buf).Encode(data)
    _, err := buf.WriteTo(c.Response)
    return err
}

BufPool = *xun.BufferPool (pool of *bytes.Buffer). Get() returns a buffer; Put(buf) returns it. Always defer Put(buf) immediately after Get().

7.4 Named Viewers

Named viewers are stored in app.viewers (map[string]Viewer). They are registered automatically by HtmlViewEngine.Load() for files under views/.

Registration: app.viewers["views/user/profile"] = &HtmlViewer{template: t}

Usage: return c.View(user, "views/user/profile") (Rule 0.5 applies).


Section 8 — ViewEngine

8.1 Interface
type ViewEngine interface {
    Load(fsys fs.FS, app *App)
    FileChanged(fsys fs.FS, app *App, event fsnotify.Event) error
}
8.2 Built-in Engines
Engine Loads Hot Reload Triggers
StaticViewEngine public/* as routes public/* Create/Write
HtmlViewEngine components/, layouts/, pages/, views/ *.html Create/Write in those dirs
TextViewEngine text/* text/* Create/Write

Default engines loaded when app.engines == nil (i.e., New() called without WithViewEngines).

8.3 HtmlViewEngine Dependency Graph

When a layout is reloaded, HtmlViewEngine tracks dependents and reloads all pages that {{ define }} blocks from that layout.


Section 9 — Project Structure

public/      → Static assets. public/index.html → GET /{$}
components/  → Reusable HTML fragments. Include via {{ block "components/name" . }}
layouts/     → Page layouts. Select with <!--layout:name--> at top of page file.
pages/       → Auto-routed pages. pages/foo/index.html → GET /foo/{$}
              pages only registers GET. Other methods require explicit handlers.
views/       → Named views (not auto-routed). Use via c.View(data, "views/name")
text/        → text/template files. c.View(data, "text/sitemap.xml")

Dynamic segments: {var} in filenames → e.g., pages/user/{id}.html → GET /user/{id}. Multiple hosts: @host.com/ prefix → e.g., pages/@abc.com/index.html → GET abc.com/{$}.

9.1 Layout Selection

File: pages/user/profile.html

<!--layout:admin-->
{{ define "content" }}
  <p>{{ .Data.Name }}</p>
{{ end }}

Layout file: layouts/admin.html

<!DOCTYPE html>
<html>
<body>
{{ block "content" . }}{{ end }}
</body>
</html>
9.2 Template Model

ViewModel{TempData, Data} is passed to templates. Access via .Data and .TempData.

<p>Name: {{ .Data.Name }}</p>
{{ if .TempData.Session }}
  <p>Session: {{ .TempData.Session }}</p>
{{ end }}

Section 10 — Error Handling

Return Value Framework Behavior
return nil Response complete
return xun.ErrCancelled Stop middleware chain; response already handled
return xun.ErrViewNotFound Emit 404
return other error Emit 500 + X-Log-Id header

ErrCancelled usage (Rule 0.3): after calling c.WriteStatus() to set the status.


Section 11 — Static Assets and Fingerprinting

11.1 Static Files

StaticViewEngine.Load() registers all non-dir files under public/ as routes.

11.2 Fingerprinting
app := xun.New(
    xun.WithFsys(fsys),
    xun.WithBuildAssetURL(func(path string) bool {
        return strings.HasSuffix(path, ".js") || strings.HasSuffix(path, ".css")
    }),
)

Flow:

  1. Matcher returns true for /assets/app.js
  2. StaticViewEngine computes content ETag
  3. Registers /assets/app-a1b2c3.js as separate route
  4. app.AssetURLs["/assets/app.js"] = "/assets/app-a1b2c3.js"
  5. {{ asset "/assets/app.js" }} in templates returns /assets/app-a1b2c3.js

Fingerprinted assets get Cache-Control: public, max-age=31536000, immutable.


Section 12 — Compression

12.1 Compressors
WithCompressor(&xun.GzipCompressor{})
WithCompressor(&xun.DeflateCompressor{})

Selected by Accept-Encoding header. * in Accept-Encoding matches all.

12.2 ResponseWriter Interface
type ResponseWriter interface {
    http.ResponseWriter
    BodyBytesSent() int
    StatusCode() int
    Close()
}

Close() is called automatically by framework via defer in handler wrapper. Do not call manually.


Section 13 — Redirects and Interceptor

13.1 Redirect
c.Redirect(url)              // 302 by default
c.Redirect(url, 301)         // custom status
13.2 Interceptor Interface
type Interceptor interface {
    RequestReferer(c *Context) string
    Redirect(c *Context, url string, statusCode ...int) bool
}

If Redirect returns true, the default redirect behavior is skipped. Use WithInterceptor(htmx.New()) for htmx support.


Section 14 — Form Binding and Validation (ext/form)

14.1 Binding Functions
form.BindQuery[T any](req *http.Request) (*TEntity[T], error)
form.BindForm[T any](req *http.Request) (*TEntity[T], error)
form.BindJson[T any](req *http.Request) (*TEntity[T], error)
14.2 TEntity
type TEntity[T any] struct {
    Data   T                 `json:"data"`
    Errors map[string]string `json:"errors"`
}

it.Validate(languages...) populates Errors and returns false if validation fails.

14.3 Validation Setup
import (
    "github.com/go-playground/locales/zh"
    ut "github.com/go-playground/universal-translator"
    trans "github.com/go-playground/validator/v10/translations/zh"
    "github.com/yaitoo/xun"
    "github.com/yaitoo/xun/ext/form"
)

xun.AddValidator(ut.New(zh.New()).GetFallback(), trans.RegisterDefaultTranslations)

Call before registering routes.

14.4 Complete Form Handler
type Login struct {
    Email  string `form:"email" json:"email" validate:"required,email"`
    Passwd string `json:"passwd" validate:"required"`
}

app.Post("/login", func(c *xun.Context) error {
    it, err := form.BindForm[Login](c.Request)
    if err != nil {
        c.WriteStatus(http.StatusBadRequest)
        return xun.ErrCancelled
    }
    if !it.Validate(c.AcceptLanguage()...) {
        c.WriteStatus(http.StatusBadRequest)
        return c.View(it)
    }
    // process login
    return c.Redirect("/dashboard")
})

Section 15 — Extensions

15.1 Extensions Summary
Extension Import Registration Key Functions
acl ext/acl app.Use(acl.New(...)) AllowHosts, AllowIPNets, DenyCountries
autotls ext/autotls autotls.New(...).Configure(srv, srvTLS) New, WithCache, WithHosts, Configure
cache ext/cache cache.New() Get, Set, Delete
cookie ext/cookie — (stateless) Set, Get, SetSigned, GetSigned, Delete
csrf ext/csrf app.Use(csrf.New(secret)) New, WithJsToken, HandleFunc
form ext/form BindQuery, BindForm, BindJson
hsts ext/hsts app.Use(hsts.WriteHeader()) Redirect, WriteHeader
htmx ext/htmx xun.WithInterceptor(htmx.New()) New
proxyproto ext/proxyproto proxyproto.ListenAndServe(srv) ListenAndServe, ListenAndServeTLS
reqlog ext/reqlog app.Use(reqlog.New(...)) New, WithFormat, WithLogger
sse ext/sse ss := sse.New() New, Join, Send, Broadcast, Leave, Shutdown
import "github.com/yaitoo/xun/ext/cookie"

// Base64 encoded (not signed)
cookie.Set(c, http.Cookie{Name: "theme", Value: "dark"})
v, err := cookie.Get(c, "theme")

// HMAC signed
ts, err := cookie.SetSigned(c, http.Cookie{Name: "session", Value: "abc123"}, []byte("secret"))
v, ts, err := cookie.GetSigned(c, "session", []byte("secret"))

// Delete
cookie.Delete(c, http.Cookie{Name: "theme"})

Section 16 — Performance

  • Do NOT enable WithWatch() in production (not thread-safe).
  • Use xun.BufPool in custom Viewer implementations to reduce allocations.
  • Compressors create per-request writers. Always rely on framework's deferred Close().
  • app.Start() does not start the server (Rule 0.4).

Section 17 — Go 1.22 Router Syntax

xun uses Go's built-in http.ServeMux router from Go 1.22.

GET /users              matches /users
GET /users/             matches /users, /users/, /users/123
GET /users/{id}         matches /users/123, sets PathValue("id") = "123"
GET /users/{id}/posts   matches /users/123/posts
GET /posts/{$}          matches /posts/ ONLY (trailing slash required)

Section 18 — Gin/Echo/Chi Differences

18.1 What xun Does NOT Have

These do NOT exist on *xun.Context:

c.Query("name")              → c.Request.URL.Query().Get("name")
c.PostForm("email")          → form.BindForm[T](c.Request)
c.Cookie("name")            → c.Request.Cookie("name")
c.SetCookie(name, v, ...)   → http.SetCookie(c.Response, &http.Cookie{...})
c.JSON(200, data)           → c.View(data) (with JsonViewer)
c.HTML(200, "tpl", data)   → c.View(data, "views/tpl") (with HtmlViewer)
c.String(200, "ok")        → c.View("ok") (with StringViewer)
c.Data(200, mime, buf)     → c.View(buf) (with FileViewer)
c.Bind(&user)              → form.BindJson[T](c.Request), form.BindForm[T](c.Request)
c.ShouldBind(&user)        → same as above
c.FullPath()               → does not exist
c.HandlerName()             → does not exist
c.MustGet("key")           → c.Get("key") (returns nil if missing, no panic)
c.Abort()                  → does not exist
c.AbortWithStatusJSON(...) → does not exist
c.ClientIP()               → c.Request.RemoteAddr (no built-in proxy support)
18.2 Middleware Signature Difference
// Gin: c.Next() called INSIDE handler
func ginMiddleware(c *gin.Context) {
    // pre
    c.Next()
    // post
}

// xun: next() called EXPLICITLY, returns HandleFunc
func xunMiddleware(next xun.HandleFunc) xun.HandleFunc {
    return func(c *xun.Context) error {
        // pre
        err := next(c)
        // post
        return err
    }
}
18.3 Error Handling Difference
// Gin: refusing returns nothing, response handled
if !allowed {
    c.AbortWithStatus(http.StatusUnauthorized)
    return
}

// xun: refusing sets status and returns ErrCancelled
if !allowed {
    c.WriteStatus(http.StatusUnauthorized)
    return xun.ErrCancelled
}

Section 19 — Complete Minimal Examples

19.1 Minimal JSON API (No fs.FS)
package main

import (
    "net/http"

    "github.com/yaitoo/xun"
)

func main() {
    app := xun.New(
        xun.WithHandlerViewers(&xun.JsonViewer{}), // Rule 0.1
    )

    app.Get("/ping", func(c *xun.Context) error {
        return c.View(map[string]string{"message": "pong"})
    })

    app.Start()
    defer app.Close()
    http.ListenAndServe(":8080", http.DefaultServeMux)
}
19.2 JSON + HTML Same Route
app := xun.New(
    xun.WithHandlerViewers(&xun.JsonViewer{}, &xun.HtmlViewer{}),
)

app.Get("/user/{id}", func(c *xun.Context) error {
    id := c.Request.PathValue("id")
    return c.View(getUser(id))
})
// Accept: application/json → JsonViewer
// Accept: text/html → HtmlViewer
19.3 Production with embed.FS
//go:embed app
var fsys embed.FS

func main() {
    var dev bool
    flag.BoolVar(&dev, "dev", false, "dev")
    flag.Parse()

    var opts []xun.Option
    if dev {
        opts = []xun.Option{
            xun.WithFsys(os.DirFS("./app")),
            xun.WithWatch(),
            xun.WithHandlerViewers(&xun.HtmlViewer{}),
        }
    } else {
        sub, _ := fs.Sub(fsys, "app")
        opts = []xun.Option{
            xun.WithFsys(sub),
            xun.WithHandlerViewers(&xun.HtmlViewer{}),
        }
    }

    app := xun.New(opts...)
    app.Get("/{$}", func(c *xun.Context) error {
        return c.View(map[string]string{"hello": "xun"})
    })

    app.Start()
    defer app.Close()
    http.ListenAndServe(":80", http.DefaultServeMux)
}
19.4 Handler with Middleware Group
auth := app.Group("/admin")
auth.Use(func(next xun.HandleFunc) xun.HandleFunc {
    return func(c *xun.Context) error {
        cookie, err := c.Request.Cookie("session")
        if err != nil || cookie.Value == "" {
            c.Redirect("/login?return=" + c.Request.URL.String())
            return xun.ErrCancelled
        }
        c.Set("Session", cookie.Value)
        return next(c)
    }
})

auth.Get("/{$}", func(c *xun.Context) error {
    return c.View(map[string]any{"user": c.Get("Session")})
})

Section 20 — Quick Reference

Rule Index
Rule 0.1 — WithHandlerViewers() requires at least one argument
Rule 0.2 — Never write c.Response directly — always use c.View()
Rule 0.3 — On refusal: c.WriteStatus() + return ErrCancelled, never return error
Rule 0.4 — app.Start() does not start server
Rule 0.5 — Named viewer must match Accept header
Rule 0.6 — pages/* registers GET only
Rule 0.7 — {$} means trailing slash required
Section Index
Section 0  — Critical Rules
Section 1  — Types
Section 2  — App (creation, fields, options)
Section 3  — Group
Section 4  — Middleware
Section 5  — Context
Section 6  — Routing
Section 7  — Viewer
Section 8  — ViewEngine
Section 9  — Project Structure
Section 10 — Error Handling
Section 11 — Static Assets and Fingerprinting
Section 12 — Compression
Section 13 — Redirects and Interceptor
Section 14 — Form Binding and Validation
Section 15 — Extensions
Section 16 — Performance
Section 17 — Go 1.22 Router Syntax
Section 18 — Gin/Echo/Chi Differences
Section 19 — Complete Minimal Examples
Section 20 — Quick Reference

Documentation

Index

Constants

View Source
const (
	NavigationName   = "name"
	NavigationIcon   = "icon"
	NavigationAccess = "access"
)

Variables

View Source
var (
	ErrCancelled    = errors.New("xun: request_cancelled")
	ErrViewNotFound = errors.New("xun: view_not_found")
)
View Source
var StringViewerMime = &MimeType{Type: "text", SubType: "plain"}
View Source
var XmlViewerMime = &MimeType{Type: "text", SubType: "xml"}

Functions

func ComputeETag added in v1.1.3

func ComputeETag(r io.Reader) string

ComputeETag returns the ETag header value for the given reader content.

The value is computed by taking the crc32 of the content and encoding it as a hexadecimal string.

func ComputeETagWith added in v1.1.3

func ComputeETagWith(r io.Reader, h hash.Hash) string

ComputeETagWith returns the ETag header value for the given reader content using the provided hash function.

func WriteIfNoneMatch added in v1.1.3

func WriteIfNoneMatch(w http.ResponseWriter, r *http.Request) bool

Types

type App

type App struct {
	AssetURLs map[string]string
	// contains filtered or unexported fields
}

App is the main struct of the framework.

It is used to register routes, middleware, and view engines.

The application instance is initialized with a new http.ServeMux, and a handler that serves files from the current working directory is registered.

The application instance is ready to be used with the standard http.Server type.

func New

func New(opts ...Option) *App

New allocates an App instance and loads all view engines.

All view engines are loaded from root directory of given fs.FS. If watch is true, it will watch all file changes and reload all view engines if any files are changed. If watch is false, it won't watch any file changes.

func (*App) Close

func (app *App) Close()

Close safely locks the App instance, ensuring that no other goroutines can access it until the lock is released. This method should be called when the App instance is no longer needed to prevent any further operations on it.

func (*App) Delete

func (app *App) Delete(pattern string, hf HandleFunc, opts ...RoutingOption)

Delete registers a route handler for the given HTTP DELETE request pattern.

func (*App) Get

func (app *App) Get(pattern string, hf HandleFunc, opts ...RoutingOption)

Get registers a route handler for the given HTTP GET request pattern.

func (*App) Group

func (app *App) Group(prefix string) Router

Group creates a new router group with the specified prefix. It returns a Router interface that can be used to define routes within the group.

func (*App) HandleFile

func (app *App) HandleFile(name string, v *FileViewer)

HandleFile registers a route handler for serving a file.

This function associates a FileViewer with a given file name and registers the route in the application's routing table. If a route with the same pattern already exists, it returns immediately without making any changes.

func (*App) HandleFunc

func (app *App) HandleFunc(pattern string, hf HandleFunc, opts ...RoutingOption)

HandleFunc registers a route handler for the given HTTP request pattern.

The pattern is expected to be in the format "METHOD PATTERN", where METHOD is the HTTP method (e.g. "GET", "POST", etc.) and PATTERN is the URL path pattern.

The opts parameter is a list of RoutingOption functions that can be used to customize the route. See the RoutingOption type for more information.

func (*App) HandlePage

func (app *App) HandlePage(pattern string, viewName string, v Viewer)

HandlePage registers a route handler for a page view.

This function associates a Viewer with a given route pattern and registers the route in the application's routing table. If a route with the same pattern already exists, it updates the existing route with the new Viewer.

func (*App) Next

func (app *App) Next(hf HandleFunc) HandleFunc

Next applies the middlewares in the app to the given HandleFunc in reverse order. It returns the final HandleFunc after all middlewares have been applied.

func (*App) Post

func (app *App) Post(pattern string, hf HandleFunc, opts ...RoutingOption)

Post registers a route handler for the given HTTP POST request pattern.

func (*App) Put

func (app *App) Put(pattern string, hf HandleFunc, opts ...RoutingOption)

Put registers a route handler for the given HTTP PUT request pattern.

func (*App) Start

func (app *App) Start()

Start initializes and starts the application by locking the mutex, iterating through the routes, and logging the pattern and viewers for each route. It ensures thread safety by using a mutex lock.

func (*App) Use

func (app *App) Use(middleware ...Middleware)

Use registers one or more Middleware functions to be executed before any route handler. Middleware functions are useful for creating reusable pieces of code that can be composed together to create complex behavior. For example, a middleware function might be used to log each request, or to check if a user is authenticated before allowing access to a page.

The order of middleware functions matters. The first middleware function that is registered will be executed first, and the last middleware function that is registered will be executed last.

Middleware functions are executed in the order they are registered.

type BufferPool

type BufferPool struct {
	// contains filtered or unexported fields
}

BufferPool is a pool of *bytes.Buffer for reuse to reduce memory alloc.

var BufPool *BufferPool

BufPool is a pool of *bytes.Buffer for reuse to reduce memory alloc.

It is used by the Viewer to render the content. The pool is created with a size of 100, but you can change it by setting the BufPool variable before creating any Viewer instances.

func NewBufferPool

func NewBufferPool(size int) (bp *BufferPool)

NewBufferPool returns a new BufferPool with the given size.

The size determines how many buffers can be stored in the pool. If the pool is full and a new buffer is requested, a new buffer will be created.

func (*BufferPool) Get

func (bp *BufferPool) Get() (b *bytes.Buffer)

Get retrieves a buffer from the pool or creates a new one if the pool is empty.

If a buffer is available in the pool, it is returned for reuse, reducing memory allocations. If the pool is empty, a new buffer is created and returned.

func (*BufferPool) Put

func (bp *BufferPool) Put(b *bytes.Buffer)

Put returns a buffer to the pool for reuse or discards if the pool is full.

This function resets the buffer to clear any existing data before returning it to the pool. If the pool is already full, the buffer is discarded.

type Compressor added in v1.0.4

type Compressor interface {
	AcceptEncoding() string
	New(rw http.ResponseWriter) ResponseWriter
}

Compressor is an interface that defines methods for handling HTTP response compression. Implementations of this interface should provide the specific encoding type they support and a method to create a new ResponseWriter that applies the compression.

AcceptEncoding returns the encoding type that the compressor supports.

New takes an http.ResponseWriter and returns a ResponseWriter that applies the compression.

type Context

type Context struct {
	Routing  Routing
	App      *App
	Response ResponseWriter
	Request  *http.Request

	TempData TempData
}

Context is the primary structure for handling HTTP requests. It encapsulates the request, response, routing information, and application context. It offers various methods to work with request data, manipulate responses, and manage routing.

func (*Context) Accept

func (c *Context) Accept() (types []MimeType)

Accept returns a slice of strings representing the media types that the client accepts, in order of preference. The media types are normalized to lowercase and whitespace is trimmed.

func (*Context) AcceptLanguage

func (c *Context) AcceptLanguage() (languages []string)

AcceptLanguage returns a slice of strings representing the languages that the client accepts, in order of preference. The languages are normalized to lowercase and whitespace is trimmed.

func (*Context) Get

func (c *Context) Get(key string) any

Get retrieves a value from the context's TempData by key.

func (*Context) Redirect

func (c *Context) Redirect(url string, statusCode ...int)

Redirect redirects the user to the given url. It uses the given status code. If the status code is not provided, it uses http.StatusFound (302).

func (*Context) RequestReferer

func (c *Context) RequestReferer() string

RequestReferer returns the referer of the request.

func (*Context) Set

func (c *Context) Set(key string, value any)

Set assigns a value to the specified key in the context's TempData.

func (*Context) View

func (c *Context) View(data any, options ...string) error

View renders the specified data as a response to the client. It can be used to render HTML, JSON, XML, or any other type of response.

The first argument is the data to be rendered. The second argument is an optional list of viewer names. If the list is empty, the viewer associated with the current route will be used. If the list is not empty, the first viewer in the list that matches the current request will be used.

func (*Context) WriteHeader

func (c *Context) WriteHeader(key string, value string)

WriteHeader sets a response header.

If the value is an empty string, the header will be deleted.

func (*Context) WriteStatus

func (c *Context) WriteStatus(code int)

WriteStatus sets the HTTP status code for the response. It is used to return error or success status codes to the client. The status code will be sent to the client only once the response body is closed. If a status code is not set, the default status code is 200 (OK).

type Decoder added in v1.1.3

type Decoder interface {
	Decode(obj interface{}) error
}

type DeflateCompressor added in v1.0.4

type DeflateCompressor struct {
}

DeflateCompressor is a struct that provides functionality for compressing data using the DEFLATE algorithm.

func (*DeflateCompressor) AcceptEncoding added in v1.0.4

func (c *DeflateCompressor) AcceptEncoding() string

AcceptEncoding returns the encoding type that the DeflateCompressor supports. In this case, it returns the string "deflate".

func (*DeflateCompressor) New added in v1.0.4

New creates a new deflateResponseWriter that wraps the provided http.ResponseWriter. It sets the "Content-Encoding" header to "deflate" and initializes a flate.Writer with the default compression level.

type Encoder added in v1.1.3

type Encoder interface {
	Encode(val interface{}) error
}

type FileViewer

type FileViewer struct {
	// contains filtered or unexported fields
}

FileViewer is a viewer that serves a file from a file system.

You can use it to serve a file from a file system, or to serve a file from a zip file.

The file system is specified by the `fsys` field, and the path is specified by the `path` field.

For example, to serve a file from the current working directory, you can use the following code:

viewer := &FileViewer{
    fsys: os.DirFS("."),
    path: "example.txt",
}

app.HandleFile("example.txt", viewer)

func NewFileViewer added in v1.0.7

func NewFileViewer(fsys fs.FS, path string, isEmbed bool, etag, cache string) *FileViewer

NewFileViewer creates a new FileViewer instance.

func (*FileViewer) MimeType

func (*FileViewer) MimeType() *MimeType

MimeType returns the MIME type of the file.

The MIME type is determined by the file extension of the file.

func (*FileViewer) Render

func (v *FileViewer) Render(ctx *Context, data any) error

Render serves a file from the file system using the FileViewer. It writes the file to the http.ResponseWriter.

type GzipCompressor added in v1.0.4

type GzipCompressor struct {
}

GzipCompressor is a struct that provides methods for compressing and decompressing data using the Gzip algorithm.

func (*GzipCompressor) AcceptEncoding added in v1.0.4

func (c *GzipCompressor) AcceptEncoding() string

AcceptEncoding returns the encoding type that the GzipCompressor supports. In this case, it returns "gzip".

func (*GzipCompressor) New added in v1.0.4

New creates a new gzipResponseWriter that wraps the provided http.ResponseWriter. It sets the "Content-Encoding" header to "gzip" and returns the wrapped writer.

type HandleFunc

type HandleFunc func(c *Context) error

HandleFunc defines a function type that takes a Context pointer as an argument and returns an error. It is used to handle requests within the application.

type Handler

type Handler struct {
	Viewers []Viewer

	Pattern string // original string
	Method  string
	Host    string
}

Handler represents an HTTP handler.

type HtmlTemplate

type HtmlTemplate struct {
	// contains filtered or unexported fields
}

HtmlTemplate is a template that is loaded from a file system.

func NewHtmlTemplate

func NewHtmlTemplate(name, path string) *HtmlTemplate

NewHtmlTemplate creates a new HtmlTemplate with the given name and path.

func (*HtmlTemplate) Execute

func (t *HtmlTemplate) Execute(wr io.Writer, data any) error

Execute renders the template with the given data and writes the result to the provided writer.

If the template has a layout, it uses the layout to render the data. Otherwise, it renders the data using the template itself.

func (*HtmlTemplate) Load

func (t *HtmlTemplate) Load(fsys fs.FS, templates map[string]*HtmlTemplate, fm template.FuncMap) error

Load loads the template from the given file system.

It parses the file, and determines the dependencies of the template. The dependencies are stored in the `dependencies` field.

func (*HtmlTemplate) Reload

func (t *HtmlTemplate) Reload(fsys fs.FS, templates map[string]*HtmlTemplate, fm template.FuncMap) error

Reload reloads the template and all its dependents from the given file system.

It first reloads the current template and then recursively reloads all its dependents. If a dependency does not exist, it is removed from the list of dependents.

type HtmlViewEngine

type HtmlViewEngine struct {
	// contains filtered or unexported fields
}

HtmlViewEngine is a view engine that loads templates from a file system.

It supports 2 types of templates:

  • Components: These are templates that are loaded from the "components" directory.
  • Pages: These are templates that are loaded from the "layouts/views/pages/" directory.

Components are used to build up larger templates, while pages are used to render the final HTML that is sent to the client.

func (*HtmlViewEngine) FileChanged

func (ve *HtmlViewEngine) FileChanged(fsys fs.FS, app *App, event fsnotify.Event) error

FileChanged is called when a file has been changed.

It is used to reload templates when they have been changed.

func (*HtmlViewEngine) Load

func (ve *HtmlViewEngine) Load(fsys fs.FS, app *App)

Load loads all templates from the given file system.

It loads all components, layouts, pages and views from the given file system.

type HtmlViewer

type HtmlViewer struct {
	// contains filtered or unexported fields
}

HtmlViewer is a viewer that renders a html template.

It uses the `HtmlTemplate` type to render a template. The template is loaded from the file system when the viewer is created. The `Render` method renders the template with the given data and writes the result to the http.ResponseWriter.

func (*HtmlViewer) MimeType

func (*HtmlViewer) MimeType() *MimeType

MimeType returns the MIME type of the HTML content.

This implementation returns "text/html".

func (*HtmlViewer) Render

func (v *HtmlViewer) Render(ctx *Context, data any) error

Render renders the template with the given data and writes the result to the http.ResponseWriter.

This implementation uses the `HtmlTemplate.Execute` method to render the template. The rendered result is written to the http.ResponseWriter.

type Interceptor

type Interceptor interface {
	// RequestReferer returns the referer of the request.
	RequestReferer(c *Context) string

	// Redirect sends an HTTP redirect to the client.
	Redirect(c *Context, url string, statusCode ...int) bool
}

Interceptor is an interface that provides methods to intercept requests and response.

type JsonEncoding added in v1.1.3

type JsonEncoding interface {
	NewEncoder(writer io.Writer) Encoder
	NewDecoder(reader io.Reader) Decoder
}

JsonEncoding is the interface that defines the methods that the standard library encoding/json package provides.

var Json JsonEncoding = &stdJsonEncoding{}

type JsonViewer

type JsonViewer struct {
}

JsonViewer is a viewer that writes the given data as JSON to the http.ResponseWriter.

It sets the Content-Type header to "application/json".

func (*JsonViewer) MimeType

func (*JsonViewer) MimeType() *MimeType

MimeType returns the MIME type of the JSON content.

It returns "application/json".

func (*JsonViewer) Render

func (*JsonViewer) Render(ctx *Context, data any) error

Render renders the given data as JSON to the http.ResponseWriter.

It sets the Content-Type header to "application/json".

type Middleware

type Middleware func(next HandleFunc) HandleFunc

Middleware is a function type that takes a HandleFunc as an argument and returns a HandleFunc. It is used to wrap or decorate an existing HandleFunc with additional functionality.

type MimeType added in v1.0.6

type MimeType struct {
	Type    string
	SubType string
}

func GetMimeType added in v1.0.5

func GetMimeType(file string, buf []byte) (MimeType, string)

func NewMimeType added in v1.0.6

func NewMimeType(t string) MimeType

func (*MimeType) Match added in v1.0.6

func (m *MimeType) Match(accept MimeType) bool

func (*MimeType) String added in v1.0.6

func (m *MimeType) String() string

type Option

type Option func(*App)

Option is a function that takes a pointer to an App and modifies it. It is used to configure an App when calling the New function.

func WithBuildAssetURL added in v1.1.4

func WithBuildAssetURL(match func(string) bool) Option

WithBuildAssetURL adds a matcher function for identifying assets that need URL processing.

func WithCompressor added in v1.0.4

func WithCompressor(c ...Compressor) Option

WithCompressor is an option function that sets the compressors for the application. It takes a variadic parameter of Compressor type and assigns it to the app's compressors field.

Parameters:

c ...Compressor - A variadic list of Compressor instances to be used by the application.

Returns:

Option - A function that takes an App pointer and sets its compressors field.

func WithFsys

func WithFsys(fsys fs.FS) Option

WithFsys sets the fs.FS for the App. If not set, Page Router is disabled.

func WithHandlerViewers added in v1.0.6

func WithHandlerViewers(v ...Viewer) Option

WithHandlerViewers sets the Viewer for a route handler. If not set, it will use JsonViewer.

func WithInterceptor

func WithInterceptor(i Interceptor) Option

WithInterceptor returns an Option that sets the provided Interceptor to the App. This allows customization of the App's behavior by intercepting and potentially modifying requests or responses.

Parameters:

  • i: An Interceptor instance to be set in the App.

Returns:

  • Option: A function that takes an App pointer and sets its interceptor to the provided Interceptor.

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger sets the logger for the App. If not set, it will use slog.Default()

func WithMux

func WithMux(mux *http.ServeMux) Option

WithMux sets the http.ServeMux for the App. If not set, it will use http.DefaultServeMux.

func WithTemplateFunc added in v1.1.4

func WithTemplateFunc(name string, fn any) Option

WithTemplateFunc adds a custom template function to the application's function map.

func WithTemplateFuncMap added in v1.1.4

func WithTemplateFuncMap(fm template.FuncMap) Option

WithTemplateFuncMap adds multiple template functions from the provided map.

func WithViewEngines

func WithViewEngines(ve ...ViewEngine) Option

WithViewEngines sets the ViewEngines for the App. If not set, it will use the default ViewEngines.

func WithWatch

func WithWatch() Option

WithWatch enable hot reload feature, please don't enable it on production. It is not thread-safe.

type ResponseWriter added in v1.0.4

type ResponseWriter interface {
	http.ResponseWriter

	BodyBytesSent() int
	StatusCode() int
	Close()
}

ResponseWriter is an interface that extends the standard http.ResponseWriter interface with an additional Close method. It is used to write HTTP responses and perform any necessary cleanup or finalization when the response is complete.

func NewResponseWriter added in v1.1.1

func NewResponseWriter(rw http.ResponseWriter) ResponseWriter

NewResponseWriter creates a new instance of ResponseWriter that wraps the provided http.ResponseWriter. It returns a pointer to a stdResponseWriter, which implements the ResponseWriter interface.

type Router

type Router interface {
	Get(pattern string, h HandleFunc, opts ...RoutingOption)
	Post(pattern string, h HandleFunc, opts ...RoutingOption)
	Put(pattern string, h HandleFunc, opts ...RoutingOption)
	Delete(pattern string, h HandleFunc, opts ...RoutingOption)
	HandleFunc(pattern string, h HandleFunc, opts ...RoutingOption)
	Use(middlewares ...Middleware)
}

Router is the interface that wraps the minimum set of methods required for an effective router, namely methods for adding routes for different HTTP methods, a method for adding middleware, and a method for adding the router to the main app.

type Routing

type Routing struct {
	Pattern string
	Handle  HandleFunc

	Options *RoutingOptions
	Viewers []Viewer
	// contains filtered or unexported fields
}

Routing represents a single route in the router.

func (*Routing) Next

func (r *Routing) Next(ctx *Context) error

type RoutingOption

type RoutingOption func(*RoutingOptions)

RoutingOption is a function that takes a pointer to RoutingOptions and modifies it. It is used to customize the behavior of the router when adding routes.

func WithMetadata

func WithMetadata(key string, value any) RoutingOption

WithMetadata adds a key-value pair to the routing metadata. It creates a new map if the metadata map is nil.

func WithNavigation

func WithNavigation(name, icon, access string) RoutingOption

WithNavigation adds navigation-related metadata to the routing options. It sets the name, icon, and access level for the navigation element.

func WithViewer

func WithViewer(v ...Viewer) RoutingOption

WithViewer sets the viewer for the routing options.

type RoutingOptions

type RoutingOptions struct {
	// contains filtered or unexported fields
}

RoutingOptions holds metadata and a viewer for routing configuration.

func (*RoutingOptions) Get

func (ro *RoutingOptions) Get(name string) any

Get returns the value associated with the given name from the routing metadata. If the name does not exist, it returns nil.

func (*RoutingOptions) GetInt

func (ro *RoutingOptions) GetInt(name string) int

GetInt returns the value associated with the given name from the routing metadata as an integer. If the name does not exist, it returns 0.

func (*RoutingOptions) GetString

func (ro *RoutingOptions) GetString(name string) string

GetString returns the value associated with the given name from the routing metadata as a string. If the name does not exist, it returns an empty string.

type StaticViewEngine

type StaticViewEngine struct {
	// contains filtered or unexported fields
}

StaticViewEngine is a view engine that serves static files from a file system.

func (*StaticViewEngine) FileChanged

func (ve *StaticViewEngine) FileChanged(fsys fs.FS, app *App, event fsnotify.Event) error

FileChanged handles file changes for the given file system and updates the application accordingly. It is called by the watcher when a file is changed.

If the file changed is a Create event and the path is in the "public" directory, it will be registered with the application.

If the file changed is a Write/Remove event and the path is in the "public" directory, nothing will be done.

func (*StaticViewEngine) Load

func (ve *StaticViewEngine) Load(fsys fs.FS, app *App)

Load loads all static files from the given file system and registers them with the application.

It scans the "public" directory in the given file system and registers each file with the application. It also handles file changes for the "public" directory and updates the application accordingly.

type StringViewer added in v1.0.7

type StringViewer struct {
}

StringViewer is a viewer that writes the given data as string to the http.ResponseWriter.

It sets the Content-Type header to "text/plain".

func (*StringViewer) MimeType added in v1.0.7

func (*StringViewer) MimeType() *MimeType

MimeType returns the MIME type of the string content.

It returns "text/plain".

func (*StringViewer) Render added in v1.0.7

func (*StringViewer) Render(ctx *Context, data any) error

Render renders the given data as string to the http.ResponseWriter.

It sets the Content-Type header to "text/plain; charset=utf-8".

type TempData added in v1.1.2

type TempData map[string]any

type TextTemplate added in v1.0.5

type TextTemplate struct {
	// contains filtered or unexported fields
}

TextTemplate represents a text template that can be loaded from a file system and executed with data.

func NewTextTemplate added in v1.1.2

func NewTextTemplate(t *template.Template) *TextTemplate

func (*TextTemplate) Execute added in v1.0.5

func (t *TextTemplate) Execute(wr io.Writer, data any) error

Execute executes the template with the given data and writes the result to the given writer.

func (*TextTemplate) Load added in v1.0.5

func (t *TextTemplate) Load(fsys fs.FS, fm template.FuncMap) error

Load loads the template from the given file system.

func (*TextTemplate) Reload added in v1.0.5

func (t *TextTemplate) Reload(fsys fs.FS, fm template.FuncMap) error

Reload reloads the template from the given file system.

type TextViewEngine added in v1.0.5

type TextViewEngine struct {
	// contains filtered or unexported fields
}

TextViewEngine is a view engine that renders text-based templates. It watches the file system for changes to text files and updates the corresponding views.

func (*TextViewEngine) FileChanged added in v1.0.5

func (ve *TextViewEngine) FileChanged(fsys fs.FS, app *App, event fsnotify.Event) error

FileChanged is called when a file in the file system has changed. It checks if the change is a file creation event in the "text/" directory, and if so, calls the handle method to update the corresponding view in the app.

func (*TextViewEngine) Load added in v1.0.5

func (ve *TextViewEngine) Load(fsys fs.FS, app *App)

Load walks the file system and loads all text-based templates that match the TextViewEngine's pattern. It calls the handle method for each matching file to add the template to the app's viewers.

type TextViewer added in v1.0.5

type TextViewer struct {
	// contains filtered or unexported fields
}

TextViewer is a struct that holds an TextTemplate and is used to render text content.

func NewTextViewer added in v1.1.2

func NewTextViewer(t *TextTemplate) *TextViewer

func (*TextViewer) MimeType added in v1.0.5

func (v *TextViewer) MimeType() *MimeType

MimeType returns the MIME type for the text content rendered by the TextViewer.

func (*TextViewer) Render added in v1.0.5

func (v *TextViewer) Render(ctx *Context, data any) error

Render writes the text content rendered by the TextViewer to the provided http.ResponseWriter. It sets the Content-Type header to "text/plain; charset=utf-8" and writes the rendered content to the response. If there is an error executing the template, it is returned.

type ViewEngine

type ViewEngine interface {
	Load(fsys fs.FS, app *App)
	FileChanged(fsys fs.FS, app *App, event fsnotify.Event) error
}

ViewEngine is the interface that wraps the minimum set of methods required for an effective view engine, namely methods for loading templates from a file system and reloading templates when the file system changes.

type ViewModel added in v1.1.2

type ViewModel struct {
	TempData map[string]any
	Data     any
}

ViewModel holds the context and associated data for rendering.

type Viewer

type Viewer interface {
	MimeType() *MimeType
	Render(ctx *Context, data any) error
}

Viewer is the interface that wraps the minimum set of methods required for an effective viewer.

type XmlViewer added in v1.0.6

type XmlViewer struct {
}

XmlViewer is a viewer that writes the given data as xml to the http.ResponseWriter.

It sets the Content-Type header to "application/xml".

func (*XmlViewer) MimeType added in v1.0.6

func (*XmlViewer) MimeType() *MimeType

MimeType returns the MIME type of the xml content.

It returns "text/xml".

func (*XmlViewer) Render added in v1.0.6

func (*XmlViewer) Render(ctx *Context, data any) error

Render renders the given data as xml to the http.ResponseWriter.

It sets the Content-Type header to "text/xml; charset=utf-8".

Directories

Path Synopsis
ext
acl
cache
Package cache provides HTTP caching middleware for xun web applications.
Package cache provides HTTP caching middleware for xun web applications.
cookie
Package cookie provides functions for securely setting and retrieving HTTP cookies using the SecureCookie library.
Package cookie provides functions for securely setting and retrieving HTTP cookies using the SecureCookie library.
sse
Package sse provides a server implementation for Server-Sent Events (SSE).
Package sse provides a server implementation for Server-Sent Events (SSE).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL