app

package module
v1.0.2 Latest Latest
Warning

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

Go to latest
Published: Sep 15, 2020 License: MIT Imports: 23 Imported by: 0

README

gowww/app

gowww/app is a full featured HTTP framework for any web app.
It greatly increases productivity by providing helpers at all levels while maintaining best performance.

Start

  1. Install Go

  2. Install gowww/app:

    go get github.com/gowww/app
    
  3. Import it in your new app:

    import "github.com/gowww/app"
    

Routing

There are methods for common HTTP methods:

app.Get("/", func(c *app.Context) {
	// Write response for GET /
})

app.Post("/", func(c *app.Context) {
	// Write response for POST /
})

app.Put("/", func(c *app.Context) {
	// Write response for PUT /
})

app.Patch("/", func(c *app.Context) {
	// Write response for PATCH /
})

app.Delete("/", func(c *app.Context) {
	// Write response for DELETE /
})

Path parameters

Named

A named parameter begins with : and matches any value until the next / in path.

To retrieve the value, ask Context.PathValue.
It will return the value as a string (empty if the parameter doesn't exist).

Example, with a parameter id:

app.Get("/users/:id", func(c *app.Context) {
	id := c.PathValue("id")
	fmt.Fprintf(w, "Page of user #%s", id)
}))
Regular expressions

If a parameter must match an exact pattern (digits only, for example), you can also set a regular expression constraint just after the parameter name and another ::

app.Get(`/users/:id:^\d+$`, func(c *app.Context) {
	id := c.PathValue("id")
	fmt.Fprintf(w, "Page of user #%s", id)
}))

If you don't need to retrieve the parameter value but only use a regular expression, you can omit the parameter name.

Wildcard

A trailing slash behaves like a wildcard by matching the beginning of the request path and keeping the rest as a parameter value, under *:

rt.Get("/files/", func(c *app.Context) {
	filepath := c.PathValue("*")
	fmt.Fprintf(w, "Get file %s", filepath)
}))

For more details, see gowww/router.

Groups

A routing group works like the top-level router but prefixes all subroute paths:

api := app.Group("/api")
{
	v1 := api.Group("/v1")
	{
		v1.Get("/user", func(c *app.Context) { /* Write response for GET /api/v1/user */ })
		v1.Get("/item", func(c *app.Context) { /* Write response for GET /api/v1/item */ })
	}

	v2 := api.Group("/v2")
	{
		v2.Get("/user", func(c *app.Context) { /* Write response for GET /api/v2/user */ })
		v2.Get("/item", func(c *app.Context) { /* Write response for GET /api/v2/item */ })
	}
}

Errors

You can set a custom "not found" handler with NotFound:

app.NotFound(func(c *app.Context) {
	c.Status(http.StatusNotFound)
	c.View("notFound")
})

The app is also recovered from panics so you can set a custom "serving error" handler (which is used only when the response is not already written) with Error and retrieve the recovered error value with Context.Error:

app.Error(func(c *app.Context) {
	c.Status(http.StatusInternalServerError)
	if c.Error() == ErrCannotOpenFile {
		c.View("errorStorage")
		return
	}
	c.View("error")
})

Context

A Context is always used inside a Handler.
It contains the original request and response writer but provides all the necessary helpers to access them:

Request

Use Context.Req to access the original request:

app.Get("/", func(c *app.Context) {
	r := c.Req
})

Use Context.FormValue to access a value from URL or body.
You can also use Context.HasFormValue to check its existence:

app.Get("/", func(c *app.Context) {
	if c.HasFormValue("id") {
		id := c.FormValue("id")
	}
})

Response

Use Context.Res to access the original response writer:

app.Get("/", func(c *app.Context) {
	w := c.Res
})

Use Context.Text or Context.Bytes to send a string:

app.Get("/", func(c *app.Context) {
	c.Text("Hello")
	c.Bytes([]byte("World"))
})

Use Context.JSON to send a JSON formatted response (if implemented by argument, JSON() interface{} will be used):

app.Get(`/users/:id:^\d+$/files/`, func(c *app.Context) {
	c.JSON(map[string]interface{}{
		"userID":   c.PathValue("id"),
		"filepath": c.PathValue("*"),
	})
})

Use Context.Status to set the response status code:

app.Get("/", func(c *app.Context) {
	c.Status(http.StatusCreated)
})

Use Context.NotFound to send a "not found" response:

app.Get("/", func(c *app.Context) {
	c.NotFound()
})

Use Context.Panic to log an error and send a "serving error" response:

app.Get("/", func(c *app.Context) {
	c.Panic("database connection failed")
})

Use Context.Redirect to redirect the client:

app.Get("/old", func(c *app.Context) {
	c.Redirect("/new", http.StatusMovedPermanently)
})

Use Context.Push to initiate an HTTP/2 server push:

app.Get("/", func(c *app.Context) {
	c.Push("/static/main.css", nil)
})

Values

You can use context values kept inside the context for future usage downstream (like views or subhandlers).

Use Context.Set to set a value:

app.Get("/", func(c *app.Context) {
	c.Set("clientCountry", "UK")
})

Use Context.Get to retrieve a value:

app.Get("/", func(c *app.Context) {
	clientCountry := c.Get("clientCountry")
})

Views

Views are standard Go HTML templates and must be stored inside the views directory.
They are automatically parsed during launch.

Use Context.View to send a view:

app.Get("/", func(c *app.Context) {
	c.View("home")
})

Data

Use a ViewData map to pass data to a view.

You can also use GlobalViewData to set data for all views:

app.GlobalViewData(app.ViewData{
	"appName": "My app",
})

app.Get("/", func(c *app.Context) {
	user := &User{
		ID:   1,
		Name: "John Doe",
	}
	c.View("home", app.ViewData{
		"user": user,
	})
})

In views/home.gohtml:

{{define "home"}}
	<h1>Hello {{.user.Name}} ({{.c.Req.RemoteAddr}}) and welcome on {{.appName}}!</h1>
{{end}}
Built-in

This data is always passed to the views, out of the box:

Data Description
.c The current Context.
.envProduction Tells if the app is run with the production flag.
.errors See validation.

Functions

Use GlobalViewFuncs to set functions for all views:

app.GlobalViewFuncs(app.ViewFuncs{
	"pathescape": url.PathEscape,
})

app.Get("/posts/new", func(c *app.Context) {
	c.View("postsNew")
})

In views/posts.gohtml:

{{define "postsNew"}}
	<a href="/sign-in?return-to={{pathescape "/posts/new"}}">Sign in</a>
{{end}}
Built-in

In addition to the functions provided by the standard template package, these function are also available out of the box:

Function Description Usage
asset Appends the file hash to the name of a static file from the static directory. {{asset "videos/loop.mp4"}}
googlefonts Sets HTML tag for Google Fonts stylesheet and given font(s). {{googlefonts "Open+Sans:400,700|Spectral"}}
nl2br Converts \n to HTML <br>. {{nl2br "line one\nline two"}}
safehtml Prevents string to be escaped. Be careful. {{safehtml "<strong>word</strong>"}}
script Sets HTML tag for a script from the static/script directory. {{script "main.js"}}
style Sets HTML tag for a stylesheet from the static/style directory. {{style "main.css"}}

Validation

Validation is handled by gowww/check.

Firstly, make a Checker with rules for keys:

userChecker := check.Checker{
	"email":   {check.Required, check.Email, check.Unique(db, "users", "email", "?")},
	"phone":   {check.Phone},
	"picture": {check.MaxFileSize(5000000), check.Image},
}

The rules order is significant so for example, it's smarter to check the format of a value before its uniqueness, avoiding some useless database requests.

Use Context.Check to check the request against a checker:

errs := c.Check(userChecker)

Use Errors.Empty or Errors.NotEmpty to know if there are errors and handle them like you want.
You can also translate error messages with Context.TErrors:

if errs.NotEmpty() {
	c.Status(http.StatusBadRequest)
	c.View(view, app.ViewData{"errors": errs})
	return
}

But usually, when a check fails, you only want to send a response with error messages.
Here comes the BadRequest shortcut which receives a checker and a view name.
If you don't provide a view name (empty string), the response will be a JSON errors map.

If the check fails, it sets the status to "400 Bad Request", sends the response (view or JSON) and returns true, allowing you to exit from the handler:

app.Post("/join", func(c *app.Context) {
	if c.BadRequest(userChecker, "join") {
		return
	}
	// Handle request confidently
})

In views, you can retrieve the TranslatedErrors map under key errors which is never nil in view data:

<input type="email" name="email" value="{{.email}}">
{{if .errors.Has "email"}}
	<div class="error">{{.errors.First "email"}}</div>
{{end}}

Internationalization

Internationalization is handled by gowww/i18n.

Firstly, make your translations map (string to string, for each language):

locales := i18n.Locales{
	language.English: {
		"hello": "Hello!",
	},
	language.French: {
		"hello": "Bonjour !",
	},
}

Use Localize to register it and set the default locale (used as a fallback):

app.Localize(locales, language.English)

Methods Context.T, Context.Tn, Context.THTML and Context.TnHTML are now operational.
As the Context is always part of the view data, you can use these methods in views:

<h1>{{.c.T "hello"}}</h1>

Static files

Static files must be stored inside the static directory.
They are automatically accessible from the /static/ path prefix.

Running

Call Run at the end of your main function:

app.Run()

By default, your app will listen and serve on :8080.
But you can change this address by using flag -a when running your app:

./myapp -a :1234

Middlewares

Custom middlewares can be used if they are compatible with standard interface net/http.Handler.
They can be set for:

  • The entire app:

    app.Run(hand1, hand2, hand3)
    
  • A group:

    api := app.Group("/api", hand1, hand2, hand3)
    
  • A single route:

    api := app.Get("/", func(c *app.Context) {
    	// Write response for GET /
    }, hand1, hand2, hand3)
    

First handler wraps the second and so on.



GoDoc Build Coverage Go Report Status Unstable

Documentation

Overview

Package app provides a full featured framework for any web app.

Example
package main

import (
	"log"
	"net/http"

	"github.com/gowww/app"
	"github.com/gowww/i18n"
	"golang.org/x/text/language"
)

func main() {
	var locales = i18n.Locales{
		language.English: {
			"hello": "Hello!",
		},
		language.French: {
			"hello": "Bonjour !",
		},
	}

	app.Localize(locales, language.English)

	app.Get("/", func(c *app.Context) {
		c.View("home")
	})

	app.Post("/user/:id/files/", func(c *app.Context) {
		c.Status(http.StatusCreated)
		c.JSON(map[string]interface{}{
			"id":       c.PathValue("id"),
			"filepath": c.PathValue("*"),
		})
	})

	v1 := app.Group("/v1")
	{
		v1.Get("/user", func(c *app.Context) { c.Text("User for V1") })
		v1.Get("/item", func(c *app.Context) { c.Text("Item for V1") })
	}

	v2 := app.Group("/v2")
	{
		v2.Get("/user", func(c *app.Context) { c.Text("User for V2") })
		v2.Get("/item", func(c *app.Context) { c.Text("Item for V2") })
	}

	if !app.EnvProduction() {
		log.Printf("Developing app on %s\n", app.Address())
	}

	app.Run()
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Address

func Address() string

Address gives the address on which the app is running. It ensures that flags are parsed so don't use this function before setting your own flags with gowww/cli or they will be ignored.

func Delete

func Delete(path string, handler Handler, middlewares ...Middleware)

Delete makes a route for DELETE method.

func Encrypter

func Encrypter() crypto.Encrypter

Encrypter returns the global encrypter.

func EnvProduction

func EnvProduction() bool

EnvProduction tells if the app is run with the production flag. It ensures that flags are parsed so don't use this function before setting your own flags with gowww/cli or they will be ignored.

func Error

func Error(handler Handler)

Error registers the "internal error" handler.

Using Context.Error, you can retrieve the error value stored in request's context during recovering.

func Get

func Get(path string, handler Handler, middlewares ...Middleware)

Get makes a route for GET method.

func GlobalViewData

func GlobalViewData(data ViewData)

GlobalViewData adds global data for view templates.

func GlobalViewFuncs

func GlobalViewFuncs(funcs ViewFuncs)

GlobalViewFuncs adds functions for view templates.

func Localize

func Localize(locs i18n.Locales, fallback language.Tag, parsers ...i18n.Parser)

Localize sets app locales with fallback and client locale parsers. Order is mandatory and defaults are: ParseCookie, ParseFormValue, ParseAcceptLanguage.

func NotFound

func NotFound(handler Handler)

NotFound registers the "not found" handler.

func Patch

func Patch(path string, handler Handler, middlewares ...Middleware)

Patch makes a route for PATCH method.

func Post

func Post(path string, handler Handler, middlewares ...Middleware)

Post makes a route for POST method.

func Put

func Put(path string, handler Handler, middlewares ...Middleware)

Put makes a route for PUT method.

func Route

func Route(method, path string, handler Handler, middlewares ...Middleware)

Route makes a route for method and path.

func Run

func Run(mm ...Middleware)

Run ensures that flags are parsed, sets the middlewares and starts the server.

func Secret

func Secret(key string)

Secret sets the secret key used for encryption. The key must be 32 bytes long.

func Secure

func Secure(o *secure.Options)

Secure sets security options.

Types

type Context

type Context struct {
	Res http.ResponseWriter
	Req *http.Request
}

A Context contains the data for a handler.

func (*Context) BadRequest

func (c *Context) BadRequest(checker check.Checker, view string, data ...ViewData) bool

BadRequest uses a check.Checker to validate request form data, and a view name to execute on fail. If you don't provide a view name (empty string), the response will be a JSON errors map.

If the check fails, it sets the status to "400 Bad Request" and returns true, allowing you to exit from the handler.

Example
package main

import (
	"net/http"

	"github.com/gowww/app"
	"github.com/gowww/check"
)

func main() {
	userChecker := check.Checker{
		"email": {check.Required, check.Email},
		"phone": {check.Phone},
	}

	app.Post("/users", func(c *app.Context) {
		if c.BadRequest(userChecker, "") {
			return
		}
		c.Status(http.StatusCreated)
	})
}
Output:

func (*Context) Bytes

func (c *Context) Bytes(b []byte)

Bytes writes the response with a bytes slice.

func (*Context) Check

func (c *Context) Check(checker check.Checker) check.Errors

Check uses a check.Checker to validate request's data and always returns the non-nil errors map.

func (*Context) Cookie

func (c *Context) Cookie(name string) string

Cookie returns the value of the named cookie. If multiple cookies match the given name, only one cookie value will be returned. If the secret key is set for app, value will be decrypted before returning. If cookie is not found or the decryption fails, an empty string is returned.

func (*Context) DeleteCookie

func (c *Context) DeleteCookie(name string)

DeleteCookie removes a cookie from the client.

func (*Context) Error

func (c *Context) Error() error

Error returns the error value stored in request's context after a recovering or a Context.Error call.

func (*Context) FmtNumber

func (c *Context) FmtNumber(n interface{}) string

FmtNumber returns a formatted number with decimal and thousands marks.

func (*Context) FormValue

func (c *Context) FormValue(key string) string

FormValue gets the form value from the request.

func (*Context) Get

func (c *Context) Get(key interface{}) interface{}

Get returns a context value.

func (*Context) HasFormValue

func (c *Context) HasFormValue(key string) bool

HasFormValue checks if the form value exists in the request.

func (*Context) JSON

func (c *Context) JSON(v interface{})

JSON writes the response with a marshalled JSON. If v has a JSON() interface{} method, it will be used.

func (*Context) Locale

func (c *Context) Locale() language.Tag

Locale returns the locale used for the client.

func (*Context) Log

func (c *Context) Log(msg string)

Log logs the message with the client address.

func (*Context) NotFound

func (c *Context) NotFound()

NotFound responds with the "not found" handler.

func (*Context) Panic

func (c *Context) Panic(err error)

Panic logs error with stack trace and responds with the error handler if set.

func (*Context) PathValue

func (c *Context) PathValue(key string) string

PathValue returns the value of path parameter.

func (*Context) Push

func (c *Context) Push(target string, opts *http.PushOptions)

Push initiates an HTTP/2 server push if supported. See net/http.Pusher for documentation.

func (*Context) Redirect

func (c *Context) Redirect(url string, status int)

Redirect redirects the client to the url with status code.

func (*Context) Set

func (c *Context) Set(key, val interface{})

Set sets a context value.

func (*Context) SetCookie

func (c *Context) SetCookie(cookie *http.Cookie)

SetCookie sets a cookie to the response. If the secret key is set for app, value will be encrypted. If the app is not in a production environment, the "secure" flag will be set to false.

func (*Context) Status

func (c *Context) Status(code int) *Context

Status sets the HTTP status of the response.

func (*Context) T

func (c *Context) T(key string, a ...interface{}) string

T returns the translation associated to key, for the client locale.

func (*Context) TErrors

func (c *Context) TErrors(errs check.Errors) check.TranslatedErrors

TErrors returns translated checking errors.

func (*Context) THTML

func (c *Context) THTML(key string, a ...interface{}) template.HTML

THTML works like T but returns an HTML unescaped translation. An "nl2br" function is applied to the result.

func (*Context) Text

func (c *Context) Text(s string)

Text writes the response with a string.

func (*Context) Textf

func (c *Context) Textf(s string, a ...interface{})

Textf writes the response with a formatted string.

func (*Context) Tn

func (c *Context) Tn(key string, n int, args ...interface{}) string

Tn returns the translation associated to key, for the client locale. If the translation defines plural forms (zero, one, other), it uses the most appropriate. All i18n.TnPlaceholder in the translation are replaced with number n. If translation is not found, an empty string is returned.

func (*Context) TnHTML

func (c *Context) TnHTML(key string, n int, args ...interface{}) template.HTML

TnHTML works like Tn but returns an HTML unescaped translation. An "nl2br" function is applied to the result.

func (*Context) View

func (c *Context) View(name string, data ...ViewData)

View writes the response with a rendered view. This data is always part of the rendering:

.	the GlobalViewData
.c	the Context
.errors	the translated errors map

func (*Context) Write

func (c *Context) Write(b []byte) (int, error)

Write writes the response.

type Handler

type Handler func(*Context)

A Handler handles a request.

func (Handler) ServeHTTP

func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

type Middleware

type Middleware func(http.Handler) http.Handler

A Middleware is a handler that wraps another one.

type RouterGroup

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

RouterGroup contains the first path part for a routes group.

func Group

func Group(path string, middlewares ...Middleware) *RouterGroup

Group initiates a routing group. All subroutes paths will be prefixed with the group path.

func (*RouterGroup) Delete

func (rg *RouterGroup) Delete(path string, handler Handler, middlewares ...Middleware)

Delete makes a route for DELETE method.

func (*RouterGroup) Get

func (rg *RouterGroup) Get(path string, handler Handler, middlewares ...Middleware)

Get makes a route for GET method.

func (*RouterGroup) Group

func (rg *RouterGroup) Group(path string, middlewares ...Middleware) *RouterGroup

Group contains the first path part for a routes subgroup.

func (*RouterGroup) Patch

func (rg *RouterGroup) Patch(path string, handler Handler, middlewares ...Middleware)

Patch makes a route for PATCH method.

func (*RouterGroup) Post

func (rg *RouterGroup) Post(path string, handler Handler, middlewares ...Middleware)

Post makes a route for POST method.

func (*RouterGroup) Put

func (rg *RouterGroup) Put(path string, handler Handler, middlewares ...Middleware)

Put makes a route for PUT method.

func (*RouterGroup) Route

func (rg *RouterGroup) Route(method, path string, handler Handler, middlewares ...Middleware)

Route makes a route for method and path.

type ViewData

type ViewData map[string]interface{}

ViewData represents data for a view rendering.

type ViewFuncs

type ViewFuncs map[string]interface{}

ViewFuncs is a map of functions passed to all view renderings.

Directories

Path Synopsis
cmd
gowww
gowww is the CLI of the gowww/app framework.
gowww is the CLI of the gowww/app framework.

Jump to

Keyboard shortcuts

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