xun

package module
v1.0.6 Latest Latest
Warning

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

Go to latest
Published: Jan 19, 2025 License: Apache-2.0 Imports: 25 Imported by: 1

README

Xun

Xun is an HTTP web framework built on Go's built-in html/template and net/http package’s router.

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

License Tests Go Reference Codecov GitHub Release Go Report Card

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 {{.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 {{.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 := xun.BindQuery[Login](c.Request())
		if err != nil {
			c.WriteStatus(http.StatusBadRequest)
			return ErrCancelled
		}

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

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

		if it.Validate(c.AcceptLanguage()...) && it.Data.Email == "xun@yaitoo.cn" && 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("", "")
Works with tailwindcss
Install Tailwind CSS

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

npm install -D tailwindcss
npx tailwindcss init
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: [],
}
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;
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
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
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
Install htmx.js

components/assets.html

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

pages/index.html

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

        <a href="/admin/">admin</a>
    </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: {{.Name}}</div>
{{ end }}
Setup Hx-Trigger listener

app.js

window.addEventListener("DOMContentLoaded", (event) => {
  document.body.addEventListener("showMessage", function(evt){
    alert(evt.detail.value);
  })
});

Apply htmx interceptor

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

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
			}

			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 := xun.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 != "xun@yaitoo.cn" || 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.Writer(), &cookie)

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

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

FuncMap is a map of functions that are available to templates.

View Source
var XmlViewerMime = &MimeType{Type: "text", SubType: "xml"}

Functions

This section is empty.

Types

type App

type App struct {
	// 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
	// contains filtered or unexported fields
}

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 values map by key. If the values map is nil or the key does not exist, it returns nil.

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

func (c *Context) Request() *http.Request

Request returns the HTTP request associated with the current context. It allows access to the request data and headers.

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 values map. If the values map is nil, it initializes a new map.

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

func (*Context) Writer

func (c *Context) Writer() http.ResponseWriter

Writer returns the http.ResponseWriter associated with the current context. It allows writing to the HTTP response body and setting response headers.

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 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 (*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(w http.ResponseWriter, r *http.Request, 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) 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) 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) error

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(w http.ResponseWriter, r *http.Request, 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 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(w http.ResponseWriter, r *http.Request, 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 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 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

	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.

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

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

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 TEntity

type TEntity[T any] struct {
	Data   T                 `json:"data"`
	Errors map[string]string `json:"errors"`
}

TEntity is a struct that contains the data and errors.

It is used by the Bind functions to return the data and errors.

func BindForm

func BindForm[T any](req *http.Request) (*TEntity[T], error)

BindForm binds the request body to the given struct.

It supports application/x-www-form-urlencoded, multipart/form-data.

If the request body is empty or the decoding fails, it returns an error.

func BindJson

func BindJson[T any](req *http.Request) (*TEntity[T], error)

BindJson binds the JSON request body to the given struct.

It attempts to decode the JSON body into the specified type.

If the decoding fails, it returns an error.

func BindQuery

func BindQuery[T any](req *http.Request) (*TEntity[T], error)

BindQuery binds the query string to the given struct.

func (*TEntity[T]) Validate

func (t *TEntity[T]) Validate(languages ...string) bool

Validate checks the data against the validation rules and populates errors if any.

It uses the specified languages to find an appropriate validator and translates the error messages. Returns true if validation passes, otherwise false.

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 (*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) 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) 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, 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) error

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 (*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(w http.ResponseWriter, r *http.Request, 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 Validator

type Validator struct {
	*validator.Validate
	Translator ut.Translator
}

Validator validates struct and field values.

It uses the specified languages to find an appropriate validator and translates the error messages.

func AddValidator

func AddValidator(trans ut.Translator, register func(v *validator.Validate, trans ut.Translator) (err error)) *Validator

AddValidator adds a new validator and translator to the map.

It also registers the translations for the default locale.

type ViewEngine

type ViewEngine interface {
	Load(fsys fs.FS, app *App) error
	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 Viewer

type Viewer interface {
	MimeType() *MimeType
	Render(w http.ResponseWriter, r *http.Request, 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(w http.ResponseWriter, r *http.Request, 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

Jump to

Keyboard shortcuts

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