xun

package module
v1.1.5 Latest Latest
Warning

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

Go to latest
Published: Aug 13, 2025 License: Apache-2.0 Imports: 27 Imported by: 1

README

Xun

Xun is a web framework built on Go's built-in html/template and net/http package’s router. It is designed to be lightweight, fast, and easy to use. Xun provides a simple and intuitive API for building web applications, while also offering advanced features such as middleware, routing, and template rendering.

Xun [ʃʊn] (pronounced 'shoon'), derived from the Chinese character 迅, signifies being lightweight and fast.

Tests Codecov Go Report Card Go Reference GitHub Release License PRs welcome

Features

  • Works with Go's built-in net/http.ServeMux router that was introduced in 1.22. Routing Enhancements for Go 1.22.
  • Works with Go's built-in html/template. It is built-in support for Server-Side Rendering (SSR).
  • Built-in response compression support for gzip and deflate.
  • Built-in Form and Validate feature with i18n support.
  • Built-in AutoTLS feature. It automatic SSL certificate issuance and renewal through Let's Encrypt and other ACME-based CAs
  • Support Page Router in StaticViewEngine and HtmlViewEngine.
  • Support multiple viewers by ViewEngines: StaticViewEngine, JsonViewEngine and HtmlViewEngine. You can feel free to add custom view engine, eg XmlViewEngine.
  • Support to reload changed static files automatically in development environment.

Getting Started

See full source code on xun-examples

Install Xun
  • install latest commit from main branch
go get github.com/yaitoo/xun@main
  • install latest release
go get github.com/yaitoo/xun@latest
Project structure

Xun has some specified directories that is used to organize code, routing and static assets.

  • public: Static assets to be served.
  • components A partial view that is shared between layouts/pages/views.
  • views: An internal page view that can be referenced in context.View to render different UI for current routing.
  • layouts: A layout is shared between multiple pages/views
  • pages: A public page view that will create public page routing automatically.
  • text: An internal text view that can be referenced in context.View to render with a data model.

NOTE: All html files(component,layout, view and page) will be parsed by html/template. You can feel free to use all built-in Actions,Pipelines and Functions, and your custom functions that is registered in HtmlViewEngine.

Layouts and Pages

Xun uses file-system based routing, meaning you can use folders and files to define routes. This section will guide you through how to create layouts and pages, and link between them.

Creating a page

A page is UI that is rendered on a specific route. To create a page, add a page file(.html) inside the pages directory. For example, to create an index page (/):

└── app
    └── pages
        └── index.html

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Xun-Admin</title>
  </head>
  <body>
    <div id="app">hello world</div>
  </body>
</html>
Creating a layout

A layout is UI that is shared between multiple pages/views.

You can create a layout(.html) file inside the layouts directory.

└── app
    ├── layouts
    │   └── home.html
    └── pages
        └── index.html

layouts/home.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Xun-Admin</title>
  </head>
  <body>
    {{ block "content" .}} {{ end }}
  </body>
</html>

pages/index.html

<!--layout:home-->
{{ define "content" }}
    <div id="app">hello world</div>
{{ end }}
Static assets

You can store static files, like images, fonts, js and css, under a directory called public in the root directory. Files inside public can then be referenced by your code starting from the base URL (/).

NOTE: public/index.html will be exposed by / instead of /index.html.

Creating a component

A component is a partial view that is shared between multiple layouts/pages/views.

└── app
    ├── components
    │   └── assets.html
    ├── layouts
    │   └── home.html
    ├── pages
    │   └── index.html
    └── public
        ├── app.js
        └── skin.css

components/assets.html

<link rel="stylesheet" href="/skin.css">
<script type="text/javascript" src="/app.js"></script>

layouts/home.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Xun-Admin</title>
    {{ block "components/assets" . }} {{ end }}
  </head>
  <body>
    {{ block "content" .}} {{ end }}
  </body>
</html>
Text View

A text view is UI that is referenced in context.View to render the view with a data model.

NOTE: Text files are parsed using the text/template package. This is different from the html/template package used in pages/layouts/views/components. While text/template is designed for generating textual output based on data, it does not automatically secure HTML output against certain attacks. Therefore, please ensure your output is safe to prevent code injection.

Creating a text view
└── app
    ├── components
    │   └── assets.html
    ├── layouts
    │   └── home.html
    ├── pages
    │   └── index.html
    └── public
    │   ├── app.js
    │   └── skin.css
    └── text
        ├── sitemap.xml
Render the view with a data model
	app.Get("/sitemap.xml", func(c *xun.Context) error {
		return c.View(Sitemap{
			LastMod: time.Now(),
		}, "text/sitemap.xml") // use `text/sitemap.xml` as current Viewer to render
	})

curl --header "Accept: application/xml, text/xml,text/plain, /" -v http://127.0.0.1/sitemap.xml

*   Trying 127.0.0.1:80...
* Connected to 127.0.0.1 (127.0.0.1) port 80
> GET /sitemap.xml HTTP/1.1
> Host: 127.0.0.1
> User-Agent: curl/8.7.1
> Accept: application/xml, text/xml,text/plain, */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Date: Wed, 15 Jan 2025 11:51:56 GMT
< Content-Length: 277
< Content-Type: text/xml; charset=utf-8
<
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
  <loc>https://github.com/yaitoo/xun</loc>
  <lastmod>2025-01-15T19:51:56+08:00</lastmod>
  <changefreq>hourly</changefreq>
  <priority>1.0</priority>
  </url>
* Connection #0 to host 127.0.0.1 left intact
</urlset>%

Building your application

Routing
Route Handler

Page Router only serve static content from html files. We have to define router handler in go to process request and bind data to the template file via HtmlViewer.

pages/index.html

<!--layout:home-->
{{ define "content" }}
    <div id="app">hello {{.Data.Name}}</div>
{{ end }}

main.go

	app.Get("/{$}", func(c *xun.Context) error {
		return c.View(map[string]string{
			"Name": "go-xun",
		})
	})

NOTE: An /index.html always be registered as /{$} in routing table. See more detail on Routing Enhancements for Go 1.22.

There is one last bit of syntax. As we showed above, patterns ending in a slash, like /posts/, match all paths beginning with that string. To match only the path with the trailing slash, you can write /posts/{$}. That will match /posts/ but not /posts or /posts/234.

Dynamic Routes

When you don't know the exact segment names ahead of time and want to create routes from dynamic data, you can use Dynamic Segments that are filled in at request time. {var} can be used in folder name and file name as same as router handler in http.ServeMux.

For examples, below patterns will be generated automatically, and registered in routing table.

  • /user/{id}.html generates pattern /user/{id}
  • /{id}/user.html generates pattern /{id}/user
├── app
│   ├── components
│   │   └── assets.html
│   ├── layouts
│   │   └── home.html
│   ├── pages
│   │   ├── index.html
│   │   └── user
│   │       └── {id}.html
│   └── public
│       ├── app.js
│       └── skin.css
├── go.mod
├── go.sum
└── main.go

pages/user/{id}.html

<!--layout:home-->
{{ define "content" }}
    <div id="app">hello {{.Data.Name}}</div>
{{ end }}

main.go

	app.Get("/user/{id}", func(c *xun.Context) error {
		id := c.Request.PathValue("id")
		user := getUserById(id)
		return c.View(user)
	})
Multiple Viewers

In our application, a route can support multiple viewers. The response is rendered based on the Accept request header. If no viewer matches the Accept header, first registered viewer is used. For more examples, see the Tests.

curl -v http://127.0.0.1
> GET / HTTP/1.1
> Host: 127.0.0.1
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Date: Thu, 26 Dec 2024 07:46:13 GMT
< Content-Length: 19
< Content-Type: text/plain; charset=utf-8
<
{"Name":"go-xun"}

curl --header "Accept: text/html; */*" http://127.0.0.1

> GET / HTTP/1.1
> Host: 127.0.0.1
> User-Agent: curl/8.7.1
> Accept: text/html; */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Date: Thu, 26 Dec 2024 07:49:47 GMT
< Content-Length: 343
< Content-Type: text/html; charset=utf-8
<
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Xun-Admin</title>
    <link rel="stylesheet" href="/skin.css">
<script type="text/javascript" src="/app.js"></script>
  </head>
  <body>

    <div id="app">hello go-xun</div>

  </body>
</html>
Middleware

Middleware allows you to run code before a request is completed. Then, based on the incoming request, you can modify the response by rewriting, redirecting, modifying the request or response headers, or responding directly.

Integrating Middleware into your application can lead to significant improvements in performance, security, and user experience. Some common scenarios where Middleware is particularly effective include:

  • Authentication and Authorization: Ensure user identity and check session cookies before granting access to specific pages or API routes.
  • Server-Side Redirects: Redirect users at the server level based on certain conditions (e.g., locale, user role).
  • Path Rewriting: Support A/B testing, feature rollout, or legacy paths by dynamically rewriting paths to API routes or pages based on request properties.
  • Bot Detection: Protect your resources by detecting and blocking bot traffic.
  • Logging and Analytics: Capture and analyze request data for insights before processing by the page or API.
  • Feature Flagging: Enable or disable features dynamically for seamless feature rollout or testing.

Authentication

	admin := app.Group("/admin")

	admin.Use(func(next xun.HandleFunc) xun.HandleFunc {
		return func(c *xun.Context) error {
			token := c.Request.Header.Get("X-Token")
			if !checkToken(token) {
				c.WriteStatus(http.StatusUnauthorized)
				return xun.ErrCancelled
			}
			return next(c)
		}
	})

Logging

	app.Use(func(next xun.HandleFunc) xun.HandleFunc {
		return func(c *xun.Context) error {
			n := time.Now()
			defer func() {
				duration := time.Since(n)

				log.Println(c.Routing.Pattern, duration)
			}()
			return next(c)
		}
	})
Multiple VirtualHosts

net/http package's router supports multiple host names that resolve to a single address by precedence rule. For examples

 mux.HandleFunc("GET /", func(w http.ResponseWriter, req *http.Request) {...})
 mux.HandleFunc("GET abc.com/", func(w http.ResponseWriter, req *http.Request) {...})
 mux.HandleFunc("GET 123.com/", func(w http.ResponseWriter, req *http.Request) {...})

In Page Router, we use @ in top folder name to setup host rules in routing table. See more examples on Tests

├── app
│   ├── components
│   │   └── assets.html
│   ├── layouts
│   │   └── home.html
│   ├── pages
│   │   ├── @123.com
│   │   │   └── index.html
│   │   ├── index.html
│   │   └── user
│   │       └── {id}.html
│   └── public
│       ├── @abc.com
│       │   └── index.html
│       ├── app.js
│       └── skin.css
Form and Validate

In an api application, we always need to collect data from request, and validate them. It is integrated with i18n feature as built-in feature now.

check full examples on Tests

type Login struct {
		Email  string `form:"email" json:"email" validate:"required,email"`
		Passwd string `json:"passwd" validate:"required"`
	}
BindQuery
	app.Get("/login", func(c *Context) error {
		it, err := form.BindQuery[Login](c.Request)
		if err != nil {
			c.WriteStatus(http.StatusBadRequest)
			return ErrCancelled
		}

		if it.Validate(c.AcceptLanguage()...) && it.Data.Email == "[email protected]" && it.Data.Passwd == "123" {
			return c.View(it)
		}
		c.WriteStatus(http.StatusBadRequest)
		return ErrCancelled
	})
BindForm
app.Post("/login", func(c *Context) error {
		it, err := form.BindForm[Login](c.Request)
		if err != nil {
			c.WriteStatus(http.StatusBadRequest)
			return ErrCancelled
		}

		if it.Validate(c.AcceptLanguage()...) && it.Data.Email == "[email protected]" && it.Data.Passwd == "123" {
			return c.View(it)
		}
		c.WriteStatus(http.StatusBadRequest)
		return ErrCancelled
	})
BindJson
app.Post("/login", func(c *Context) error {
		it, err := form.BindJson[Login](c.Request)
		if err != nil {
			c.WriteStatus(http.StatusBadRequest)
			return ErrCancelled
		}

		if it.Validate(c.AcceptLanguage()...) && it.Data.Email == "[email protected]" && it.Data.Passwd == "123" {
			return c.View(it)
		}
		c.WriteStatus(http.StatusBadRequest)
		return ErrCancelled
	})
Validate Rules

Many baked-in validations are ready to use. Please feel free to check docs and write your custom validation methods.

i18n

English is default locale for all validate message. It is easy to add other locale.

import(
  "github.com/go-playground/locales/zh"
  ut "github.com/go-playground/universal-translator"
  trans "github.com/go-playground/validator/v10/translations/zh"

)

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

check more translations on here

Extensions
GZip/Deflate handler

Set up the compression extension to interpret and respond to Accept-Encoding headers in client requests, supporting both GZip and Deflate compression methods.

app := xun.New(WithCompressor(&GzipCompressor{}, &DeflateCompressor{}))
AutoTLS

Use autotls.Configure to set up servers for automatic obtaining and renewing of TLS certificates from Let's Encrypt.

mux := http.NewServeMux()

app := xun.New(xun.WithMux(mux))

//...

httpServer := &http.Server{
	Addr: ":http",
	//...
}

httpsServer := &http.Server{
	Addr: ":https",
	//...
}

autotls.
	New(autotls.WithCache(autocert.DirCache("./certs")),
		autotls.WithHosts("abc.com", "123.com")).
	Configure(httpServer, httpsServer)

go httpServer.ListenAndServe()
go httpsServer.ListenAndServeTLS("", "")

Cookies are a way to store information at the client end. see more examples

Write cookie with base64(URL Encoding) to client

cookie.Set(ctx,  http.Cookie{Name: "test", Value: "value"}) // Set-Cookie: test=dmFsdWU=

Read and decoded cookie from client's request

v, err := cookie.Get(ctx,"test")

fmt.Println(v) // value

When signed, the cookies can't be forged, because their values are validated using HMAC.

ts, err := cookie.SetSigned(ctx,http.Cookie{Name: "test", Value: "value"},[]byte("secret")) // ts is current timestamp

v, ts, err := cookie.GetSigned(ctx, "test",[]byte("secret")) // v is value, ts is the timestamp that was signed

Delete a cookie

cookie.Delete(ctx, http.Cookie{Name: "test", Value: "dmFsdWU="}) // Set-Cookie: test=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0
HSTS

HTTP Strict Transport Security (HSTS) is a simple and widely supported standard to protect visitors by ensuring that their browsers always connect to a website over HTTPS.

Redirect redirects plain HTTP requests to HTTPS. DON'T use it if HTTPs is unsupported on your server.

app.Use(hsts.Redirect())

Write HSTS header if it is a HTTPs request. It is only applied in HTTPs request.

app.Use(hsts.WriteHeader())
Proxy Protocol

The PROXY protocol allows our application to receive client connection information that is passed through proxy servers and load balancers. Both PROXY protocol versions 1 and 2 are supported.

How to use the Proxy Protocol to preserve a client's ip address?

Security Note: Do not enable the PROXY protocol on your servers unless they are located behind a proxy server or load balancer. If the PROXY protocol is enabled without such intermediaries, any client could potentially send fake IP addresses or other misleading information, posing a security risk.

ListenAndServe

	mux := http.NewServeMux()

	srv := &http.Server{
		Addr:    ":80",
		Handler: mux,
	}

	app := xun.New(WithMux(mux))
	app.Start()
	defer app.Close()

	//   srv.ListenAndServe() 
	proxyproto.ListenAndServe(srv)

ListenAndServeTLS

	httpsServer := &http.Server{
		Addr:    ":443",
		Handler: mux,
	}

	autotls.New(autotls.WithCache(autocert.DirCache("./certs")),
		autotls.WithHosts("yaitoo.cn", "www.yaitoo.cn")).
		Configure(srv, httpsServer)

  // httpsServer.ListenAndServeTLS( "", "") 
	proxyproto.ListenAndServeTLS(httpsServer, "", "") 
Logging

Logs each incoming request to the provided logger. The format of the log messages is customizable using the Format option. The default format is the combined log format (XLF/ELF).

Enable reqlog middleware

func main(){
 	//....
  logger, _ := setupLogger()

  app.Use(reqlog.New(reqlog.WithLogger(logger),
		reqlog.WithUser(getUserID),
		reqlog.WithVisitor(getVisitorID),
		reqlog.WithFormat(reqlog.Combined))))
 	//...
}

func setupLogger() (*log.Logger, error) {
	logFile, err := os.OpenFile("./access.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return nil, err
	}
	return log.New(logFile, "", 0), nil
}

func getVisitorID(c *xun.Context) string {
	v, err := c.Request.Cookie("visitor_id") // use fingerprintjs to generate visitor id in client's cookie
	if err != nil {
		return ""
	}

	return v.Value
}

func getUserID(c *xun.Context) string {
	v, _, err := cookie.GetSigned(c, "session_id", secretKey)
	if err != nil {
		return ""
	}

	return v
}

Install GoAccess to generate real-time analysis report

How to install GoAccess

goaccess ./access.log --geoip-database=./GeoLite2-ASN.mmdb --geoip-database=./GeoLite2-City.mmdb -o ./realtime.html --log-format=COMBINED --real-time-html

Serve the online real-time analysis report

	app.Get("/reports/realtime.html", func(c *xun.Context) error {
		http.ServeFile(c.Response, c.Request, "./realtime.html")
		return nil
	})
CSRF Token

A CSRF (Cross-Site Request Forgery) token is a unique security measure designed to protect web applications from unauthorized or malicious requests. see more examples

Enable csrf middleware

func main(){
 	//....
  secretKey := []byte("your-secret-key")

  app.Use(csrf.New(secretKey))
 	//...
}

Enable JsToken to prevent bot requests on POST/PUT/DELETE

  • enable csrf with JsToken
func main(){
 	//....
  secretKey := []byte("your-secret-key")
  app.Use(csrf.New(secretKey,csrf.WithJsToken()))
  // ...
  app.Get("/assets/csrf.js",csrf.HandleFunc(secretKey))
  //...
}
  • load csrf.js on html
<script type="text/javascript" src="/assets/csrf.js" defer></script>	
Access Control List (ACL)

The ACL filters and monitors HTTP traffic through granular rule sets, designed to protect web applications/APIs from malicious bots, exploit attempts, and unauthorized access.

Core Filtering Dimensions
  • Host-Based Filtering (AllowHosts)

    Restrict access to explicitly permitted domains/subdomains

  • IP Range Control (AllowIPNets/DenyIPNets)

    Allow/block traffic from specific IP addresses or CIDR-notated subnets. IPv4/IPv6 are both supported.

  • Geolocation Filtering (AllowCountries/DenyCountries)

    Permit/restrict access based on client geolocation

Enforcement Actions
  • Block unauthorized requests with 403 Forbidden status

  • Host Redirection (Conditional):

    When AllowHosts validation fails:

    • Redirect to HostRedirectURL
    • Use customizable HTTP status HostRedirectStatusCode (e.g., 307 Temporary Redirect)
Code Examples

see more examples

AllowHosts

app.Use(acl.New(acl.AllowHosts("abc.com","123.com"), acl.WithHostRedirect("https://abc.com", 302)))

Whitelist Mode by IPNets

app.Use(acl.New(acl.AllowIPNets("172.0.0.1","2000::1/8")),acl.DenyIPNets("*")) 

Whitelist Mode by Countries

func lookup(ip string)string {
	db, _ := geoip2.Open("./GeoLite2-City.mmdb")
	nip := net.ParseIP(ip)

	c, _ := db.cityDB.City(nip)

	return c.CountryCode
}

app.Use(acl.New(acl.WithLookupFunc(lookup),
	acl.AllowCountries("CN"),acl.DenyCountries("*")))

Blacklist Mode by IPNets

app.Use(acl.DenyIPNets("172.0.0.0/24")) 

Blacklist Mode by Countries

app.Use(acl.New(acl.WithLookupFunc(lookup),acl.DenyCountries("us","cn")))
Config Example

The optimal solution is to load the rules from a configuration file rather than hard-coding them. The ACL system also monitors the configuration file for changes and automatically reloads the rules. see more examples

config file

[allow_hosts]
abc.com
www.abc.com
[allow_ipnets]
89.207.132.170/24
# ::1  
; 127.0.0.1
[deny_ipnets]
*
[allow_countries]

[deny_countries]
us

[host_redirect]
url=http://yaitoo.cn
status_code=302

use middleware with config

app.Use(acl.New(acl.WithConfig("./acl.ini")))
Server-Sent Events (SSE)

Server-Sent Events (SSE) is a server push technology enabling a client to receive automatic updates from a server via an HTTP connection.

use sse extension to handle SSE request

ss := sse.New()

app.Get("/topic/{id}", func(ctx *xun.Context)error {
	id := c.Get("SessionID").(string)
	s, err := ss.Join(c.Request.Context(), id, c.Response)
	if err != nil {
		c.WriteStatus(http.StatusBadRequest)
		return xun.ErrCancelled
	}

	s.Wait()

	ss.Leave(id)

	return nil
})

push an event to the user

u := ss.Get("user_id")
if u != nil {
	u.Send(sse.TextEvent{
		Name:"showMessage",
		Data:"Hello",
	})
}

broadcast an event to all users

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
ss.Broadcast(ctx, sse.TextEvent{
	Name:"shutdown",
	Data:"Server is shutting down",
}

shutdown server and close all user connections

ss.Shutdown()

use htmx-ext-sse extension to send SSE request


<script src="https://cdnjs.cloudflare.com/ajax/libs/htmx/2.0.4/ext/sse.min.js" integrity="sha512-uROW42fbC8XT6OsVXUC00tuak//shtU8zZE9BwxkT2kOxnZux0Ws8kypRr2UV4OhTEVmUSPIoUOrBN5DXeRNAQ==" 
crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<div class="w-full" hx-ext="sse" sse-connect="/topic/{id}" >
...
</div>
Works with tailwindcss
1. Install Tailwind CSS

Install tailwindcss via npm, and create your tailwind.config.js file.

npm install -D tailwindcss
npx tailwindcss init
2. Configure your template paths

Add the paths to all of your template files in your tailwind.config.js file.

tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./app/**/*.{html,js}"],
  theme: {
    extend: {},
  },
  plugins: [],
}
3. Add the Tailwind directives to your CSS

Add the @tailwind directives for each of Tailwind’s layers to your main CSS file.

app/tailwind.css

@tailwind base;
@tailwind components;
@tailwind utilities;
4. Start the Tailwind CLI build process

Run the CLI tool to scan your template files for classes and build your CSS.

npx tailwindcss -i ./app/tailwind.css -o ./app/public/theme.css --watch
5. Start using Tailwind in your HTML

Add your compiled CSS file to the assets.html and start using Tailwind’s utility classes to style your content.

components/assets.html

<link rel="stylesheet" href="/skin.css">
<link rel="stylesheet" href="/theme.css">
<script type="text/javascript" src="/app.js"></script>
Works with htmx.js
1. Add new pages

pages/admin/index.html and pages/login.html

├── app
│   ├── components
│   │   └── assets.html
│   ├── layouts
│   │   └── home.html
│   ├── pages
│   │   ├── @123.com
│   │   │   └── index.html
│   │   ├── admin
│   │   │   └── index.html
│   │   ├── index.html
│   │   ├── login.html
│   │   └── user
│   │       └── {id}.html
│   ├── public
│   │   ├── @abc.com
│   │   │   └── index.html
│   │   ├── app.js
│   │   ├── skin.css
│   │   └── theme.css
│   ├── tailwind.css
2. Serve htmx-ext.js library

The library to enable seamless integration between native JavaScript methods and htmx features, enhancing interactive capabilities without compromising core functionality.

	app.Get("/htmx-ext.js", htmx.HandleFunc())
3. Install htmx.js and htmx-ext.js

components/assets.html

<link rel="stylesheet" href="/skin.css">
<link rel="stylesheet" href="/theme.css">
<script src="https://unpkg.com/[email protected]" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script>
<script type="text/javascript" src="/htmx-ext.js"></script>
<script type="text/javascript" src="/app.js" defer></script>
4. Enabled htmx feature on pages

pages/index.html

<!--layout:home-->
{{ define "content" }}
    <div id="app" class="text-3xl font-bold underline" hx-boost="true">

			{{ if .TempData.Session }}
				Hello {{ .TempData.Session }}, go <a href="/admin">Admin</>
			{{ else }}
        Hello guest, please <a href="/login">Login</a>	
			{{ end }}    
    </div>

{{ end }}

pages/login.html

<!--layout:home-->
{{ define "content" }}

<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
  <div class="sm:mx-auto sm:w-full sm:max-w-sm">
    <h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-gray-900">Sign in to your account</h2>
  </div>

  <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
    <form class="space-y-6" action="#" method="POST" hx-post="/login">
      <div>
        <label for="email" class="block text-sm/6 font-medium text-gray-900">Email address</label>
        <div class="mt-2">
          <input type="email" name="email" id="email" autocomplete="email" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
        </div>
      </div>

      <div>
        <div class="flex items-center justify-between">
          <label for="password" class="block text-sm/6 font-medium text-gray-900">Password</label>
        </div>
        <div class="mt-2">
          <input type="password" name="password" id="password" autocomplete="current-password" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
        </div>
      </div>

      <div>
        <button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Sign in</button>
      </div>
    </form>
  </div>
</div>

{{ end }}

pages/admin/index.html

<!--layout:home-->
{{ define "content" }}
    <div id="app" class="text-3xl font-bold underline">
				Hello admin: {{ .Data.Name }}
			</div>
{{ end }}
5. Setup Hx-Trigger listener

app.js

$x.ready(function(evt) {
	document.addEventListener("showMessage", function(evt){
    alert(evt.detail);
  })
},'body');

6. Apply htmx interceptor

	app := xun.New(xun.WithInterceptor(htmx.New()))

7. Create router handler to process request

create an admin group router, and apply a middleware to check if it's logged. if not, redirect to /login.

	admin := app.Group("/admin")

	admin.Use(func(next xun.HandleFunc) xun.HandleFunc {
		return func(c *xun.Context) error {
			s, err := c.Request.Cookie("session")
			if err != nil || s == nil || s.Value == "" {
				c.Redirect("/login?return=" + c.Request.URL.String())
				return xun.ErrCancelled
			}

			// set session in Context.TempData, 
			// and get it by `.TempData.Session on text/html template files
			c.Set("Session", s.Value)
			return next(c)
		}
	})

	admin.Get("/{$}", func(c *xun.Context) error {
		return c.View(User{
			Name: c.Get("session").(string),
		})
	})

	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)
		}

		if it.Data.Email != "[email protected]" || it.Data.Password != "123" {
			htmx.WriteHeader(c,htmx.HxTrigger, htmx.HxHeader[string]{
				"showMessage": "Email or password is incorrect",
			})
			c.WriteStatus(http.StatusBadRequest)
			return c.View(it)
		}

		cookie := http.Cookie{
			Name:     "session",
			Value:    it.Data.Email,
			Path:     "/",
			MaxAge:   3600,
			HttpOnly: true,
			Secure:   true,
			SameSite: http.SameSiteLaxMode,
		}

		http.SetCookie(c.Response, &cookie)

    u, _ := url.Parse(c.RequestReferer())

		c.Redirect(u.Query().Get("return"))
		return nil
	})

Deploy your application

Leveraging Go's built-in //go:embed directive and the standard library's fs.FS interface, we can compile all static assets and configuration files into a single self-contained binary. This dependency-free approach enables seamless deployment to any server environment.


//go:embed app
var fsys embed.FS

func main() {
	var dev bool
	flag.BoolVar(&dev, "dev", false, "it is development environment")

	flag.Parse()

	var opts []xun.Option
	if dev {
		// use local filesystem in development, and watch files to reload automatically
		opts = []xun.Option{xun.WithFsys(os.DirFS("./app")), xun.WithWatch()}
	} else {
		// use embed resources in production environment
		views, _ := fs.Sub(fsys, "app")
		opts = []xun.Option{xun.WithFsys(views)}
	}

	app := xun.New(opts...)
	//...

	app.Start()
	defer app.Close()

	if dev {
		slog.Default().Info("xun-admin is running in development")
	} else {
		slog.Default().Info("xun-admin is running in production")
	}

	err := http.ListenAndServe(":80", http.DefaultServeMux)
	if err != nil {
		panic(err)
	}
}

Contributing

Contributions are welcome! If you're interested in contributing, please feel free to contribute to Xun

License

Apache-2.0 license

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