ui

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 22, 2021 License: MIT Imports: 13 Imported by: 0

README

web-ui-tools

Introduction:

This package provides tooling for HTML templates, cache-busting of static files, and user sessions for golang webapps.

Details:

This package was designed for a specific webapp structure:

  • Golang HTML templates stored in one main directory (optional subdirectories).
  • Known names and paths to static assets (i.e: CSS, JS files) that are already compiled (and most likely minified).
  • You have write access to the disk.
  • You handle user sessions via storing data in browser cookies.

The configuration, and related functionality, of HTML templates, cache busting, and sessions is separated since they are mostly unrelated tasks. This adds a bit of extra code but keeps the code clearer. It also allows you to implement only a subset of the tooling in this package (ex.: cache busting but not templates).

The configurations assume a lot of default values to reduce the amount of fields that need to be provided in most circumstances. Please read the individual files for details on all of the available fields for each configuration.

HTML Templates:

Templates are handled by parsing files, building the golang representation of the templates, and caching the templates for future use in displaying the UI to the end user browser. You can store template files in multiple directories for organization purposes and to allow using the same template name (file name or {{template}} declaration) multiple times in the same app.

To build the templates when your app starts, use code similar to the following in your main.go init(). Upon success, the built templates will be stored internally in the package for future use with Show().

//Define a configuration.
//Given the following directory structure:
/*
/path/to/templates/
├─ header.html
├─ footer.html
├─ docs/
│  ├─ index.html
│  ├─ faq.html
├─ app/
│  ├─ index.html
│  ├─ users.html
*/
cfg := ui.TemplateConfig{
    TemplatesRootPath: filepath.Join("path", "to", "templates"),
    TemplatesSubDirs: []string{
        "app",
        "docs",
    }
}

//Validate the config, build the templates, save the config and templates for future use.
err := cfg.Build()
if err != nil {
    log.Fatalln(err)
    return
}

When you want to display a page, use code similar to the following:

func MyHttpHandler(w http.ResponseWriter, r *http.Request) {
    //nil is an interface{} value and can be replaced with anything to
    //be injected into your templates under the .Data field.
    ui.Show(w, "app", "users", nil)
}

Cache Busting of Static Files (CSS, JS, etc.)

Cache busting works by creating a hash of a static file, creating a copy of the file, prepending a portion of the hash to the beginning of the file's name, and storing a mapping of original file name to cache bust file name for use when displaying an HTML template.

Example:

  • Original file name: script.min.js.
  • Cache bust file name: A1B2C3D4.script.min.js.

Note that due to the copying of each static file, you must have write access to where the static files are stored.

To create the cache busting files and file names, use code similar to the following in your main.go init(). Upon success the cache busting files will be created on disk and the file name pairings will be made available in your templates in the .CacheBustFiles field.

//Define a configuration:
cfg := ui.CacheBustConfig{
    StaticFilePaths: []string{
        filepath.Join("path", "to", "styles.min.css"),
        filepath.Join("path", "to", "script.min.js"),
    }
}
err := cfg.CreateCacheBustingFiles()
if err != nil {
    log.Fatalln(err)
    return
}

Use the created cache busting file(s) in a manner similar to the following. This works by taking the known minified file name, styles.min.css, looking it up in the pairings of cache busted files, retrieving the matching cache busting file name, and using the cache busting file name in the HTML code.

{{if .Development}}
    <!-- Development is useful for when you are changing things often and have the cache disabled in the browser. -->
    <link rel="stylesheet" href="/static/css/styles.css">
{{else}}
    {{$minifiedFile := "styles.min.css"}}
    {{$cache := .CacheBustedFiles}}
    
    <!-- Check if the minified file name exists in the list of cache busting files, if yes {{.}} stores the cache busting file name, if no use the standard minified file name so the file still loads. -->
    {{with index $cache $minifiedFile}}
        <link rel="stylesheet" href="/static/css/{{.}}">
    {{else}}
        <link rel="stylesheet" href="/static/css/{{$minifiedFile}}">
    {{end}}
{{end}}

Sessions:

Sessions are used for storing data about a logged in user or similar. This package handles sessions by storing data in a browser cookie. This package does not decide what data to store in the session or cookie, it simply provides the boilerplate to get sessions initialized.

Initialize sessions using code similar to the following:

cfg := ui.SessionConfig{
    MaxAge: 3600, //1 hour in seconds
    Domain: "myapp.example.com",

    //In development, or in situations where your app stops and starts
    //often, you will also want to set the following fields to prevent
    //existing sessions (i.e: logged in users) from becoming invalid each
    //time the app restarts.
    // AuthKey: []byte("random 64 char long string"),
    // EncryptKey: []byte("random 32 char long string"),
    
}
err := cfg.Start()
if err != nil {
    log.Fatalln(err)
    return
}

You can then store data in a session, for example a session ID stored in your database, using:

func MyHandler(w http.ResponseWriter, r *http.Request) {
    err := AddToSession(w, r, "my-key-name", "some-kind-of-session-id")
    if err != nil {
        log.Println(err)
        return
    }
}

You can retrieve data from a session using:

func MyHandler(w http.ResponseWriter, r *http.Request) {
    value, err := GetFromSession(r, "my-key-name")
    if err != nil {
        log.Println(err)
        return
    }

    log.Println("Value for key 'my-key-name':", value)
}

Documentation

Overview

Package ui provides tools for handling HTML templates, cache busting of static (JS and CSS) files, and user session management. This package is intended for use in golang web-apps.

This file specifically handles cache busting.

Cache busting is done at runtime by calculating a hash of each defined file (for example, script.min.js), saving a copy of this file to disk with a subset of the hash prepended to the file's name, and providing this new filename to your templates for use with <link> or <script> tags. The copy of the file is stored in the same directory where the defined file is located. Due to this, your application must have write permission to this directory.

To use the cache busted version of each file, modify your html templates to replace usage of a minified file with the cache busted version by matching up the original name of the minified file. For example: <html>

  <head>
    {{$minifiedFile := "styles.min.css"}}
	{{$cacheBustFiles := .CacheBustFiles}}

	{{/*If the key "styles.min.css" exists in $cacheBustFiles, then the associated cache-busted filename will be returned as {{.}}. *\/}}
	{{with index $cacheBustFiles $minifiedFile}}
	  <link rel="stylesheet" href="/static/css/{{.}}">
    {{else}}
      <link rel="stylesheet" href="/static/css/{{$minifiedFile}}">
    {{end}}
  </head>

</html>

Package ui provides tools for handling HTML templates, cache busting of static (JS and CSS) files, and user session management. This package is intended for use in golang web-apps.

This file specifically handles session management.

User sessions are managed through cookies stored in the user's browser. The cookies are encrypted and hashed for security against viewing their contents and altering the data stored in the cookie.

Package ui provides tools for handling HTML templates, cache busting of static (JS and CSS) files, and user session management. This package is intended for use in golang web-apps.

This file specifically handles HTML templates.

Handling of HTML templates is done by parsing files in given directories and caching them for future use within your application. Templates can be stored in numerous subdirectories for ease of organization and allowing the same filename or template declaration ({{define}}) to be used. Files stored at the root templates directory are inherited into each subdirectory; this is useful for storing files with shared {{declare}} blocks that are imported into other templates stored in numerous subdirectories (ex.: header and footers).

Serving of a template is done by providing the subdirectory and name of the template (aka filename). Note that due to this, you cannot serve templates from the root directory. Again, the root directory is for storing templates shared templates between multiple subdirectories.

An example of a directory structure for storing templates is below. templates/ ├─ header.html ├─ footer.html ├─ docs/ │ ├─ index.html │ ├─ faq.html │ ├─ how-to.html ├─ app/ │ ├─ index.html │ ├─ users.html │ ├─ widgits.html

Package ui provides tools for handling HTML templates, cache busting of static (JS and CSS) files, and user session management. This package is intended for use in golang web-apps.

This file holds common elements between the other files within this package. We use numerous separate files for better organization.

Note that this package requires full paths to a number of directories and files. It is best to provide these either with flags or with a config file, either of which are read prior to initializing this package within your application. This will ensure you do not have any hardcoded paths that may cause issues if your application is installed on a different system or OS. Alternatively, you can have your application determine the correct paths based on your expected filesystem layout at runtime, again prior to initializing usage of this package.

Index

Constants

This section is empty.

Variables

View Source
var (
	//ErrNoCacheBustingInDevelopment is returned when CreateCacheBustingFiles() is called but this package's
	//config.Development is set to true.
	ErrNoCacheBustingInDevelopment = errors.New("ui: not creating cache busting files in development")

	//ErrInvalidStaticFilePath is returned when a path to
	ErrInvalidStaticFilePath = errors.New("ui: empty or all whitespace string provided for StaticFilePaths is not allowed")

	//ErrHashLengthToShort is returned when user provided a hash length that is below our defined minimum length
	ErrHashLengthToShort = errors.New("ui: hash length to short, must be at least " + strconv.FormatUint(uint64(minHashLength), 10))
)

errors

View Source
var (
	//ErrAuthKeyWrongSize is returned when the provided AuthKey is not the correct length.
	ErrAuthKeyWrongSize = errors.New("ui: AuthKey is invalid, must be exactly 64 characters")

	//ErrEncyptKeyWrongSize is returned when the provided EncryptKey is not the correct length.
	ErrEncyptKeyWrongSize = errors.New("ui: EncryptKey is invalid, must exactly 32 characters")

	//ErrNoSessionKeyValues is returned when user is trying to add data to a session but didn't
	//provide any key value pairs.
	ErrNoSessionKeyValues = errors.New("ui: no key value pair(s) provided")

	//ErrSessionIsNewUnexpected is returned when adding data to a session but the session was just created.
	ErrSessionIsNewUnexpected = errors.New("ui: session is new, this is unexpected")

	//ErrKeyNotFound is returned when a desired key is not found in the session
	ErrKeyNotFound = errors.New("ui: the key you are looking for does not exist in the session")
)

errors

View Source
var (
	//ErrTemplatesRootPathNotSet is returned if a user calls Save() and not path to the
	//templates was provided.
	ErrTemplatesRootPathNotSet = errors.New("ui: no value set for TemplatesRootPath")

	//ErrNoTemplateSubDirsProvided is returned when no subdirectories were provided. As of
	//now we require at least one subdirectory.
	ErrNoTemplateSubDirsProvided = errors.New("ui: no template subdirectories were provided, at least one must be")

	//ErrInvalidTemplateSubDir is returned if a user calls Save() and the provided
	//subdirectory cannot be found.
	ErrInvalidTemplateSubDir = errors.New("ui: empty or all whitespace string provided for TemplatesSubDirs is not allowed")
)

errors

View Source
var (
	//ErrNoRuntimeEditsAllowed is returned if a user calls Save() after the configuration
	//has already been saved and the EnableRuntimeEdits field wasn't set to true initially.
	ErrNoRuntimeEditsAllowed = errors.New("ui: editing of the configuration is not allowed once it has been set")
)

common errors

Functions

func AddDefaultKeysToSession

func AddDefaultKeysToSession(w http.ResponseWriter, r *http.Request, username, userID, token, sessionID string) (err error)

AddDefaultKeysToSession saved the values for the default/typical keys to the session. You do not have to use this function and you can provide your own keys using AddToSession... This func is just a helper for default/typical use cases. If you provide "" for any of they values, then the key will not be saved to avoid confusion with blank values rather then an error for when the key does not exist.

func AddToSession

func AddToSession(w http.ResponseWriter, r *http.Request, key, value string) (err error)

AddToSession adds a key value pair to a session. This is a helper func around AddToSessionMulti which only adds a single key value pair to the session. This is useful when you are only adding a single key value data set so you don't have to create a SessionKeyValues variable.

func AddToSessionMulti

func AddToSessionMulti(w http.ResponseWriter, r *http.Request, pairs ...SessionKeyValues) (err error)

AddToSessionMulti adds multiple key value pairs to a session. This looks up the session, if it exists, adds the data, and saves the data to the existing session. The keys and values must be strings so that we don't have to deal with interface conversion here.

This handles adding multiple pairs of data to a session which can be useful upon a user logging in and you wanting to store a bunch of data into the session initially (session id, user id, log in time, tokens, etc.). Use this instead of calling AddToSession() multiple times and having to check returned errors multiple times.

func DestroySession

func DestroySession(w http.ResponseWriter, r *http.Request) (err error)

DestroySession deletes a session from a request. This is typically used during the user "log out" process.

func ExtendExpiration

func ExtendExpiration(w http.ResponseWriter, r *http.Request) (err error)

ExtendExpiration pushes out the expiration time of the cookie. This is typically used to keep a user logged in for a certain amount of inactivity.

func GetCacheBustingPairs

func GetCacheBustingPairs() pairs

GetCacheBustingPairs returns the list of original filename to cache bust filename pairs. This is useful for when you aren't using the template functionality included in this package and want to use the cache busting tooling separately.

func GetFromSession

func GetFromSession(r *http.Request, key string) (value string, err error)

GetFromSession retrieves a value give the key from the session. See the helper GetKey... funcs below for commonly used key/value pairs stored in sessions.

func GetSession

func GetSession(r *http.Request) (s *sessions.Session, err error)

GetSession gets an existing session from an http request, if it exists, or creates a new session if none existed. The field "IsNew" on the returned data will be "true" if this session was just created.

func GetSessionID

func GetSessionID(r *http.Request) (value string, err error)

GetSessionID is a helper func to retrieve the value for the sessionKeyUserID from the session (if this key was provided).

func GetSessionIDAsInt

func GetSessionIDAsInt(r *http.Request) (value int64, err error)

GetSessionIDAsInt is a helper func to retrieve the value for the sessionKeySessionID from the session (if this key was provided). This returns the session ID as an integer, if conversion is possible, so that you do not need to call ParseInt separately after GetSessionID.

func GetSessionToken

func GetSessionToken(r *http.Request) (value string, err error)

GetSessionToken is a helper func to retrieve the value for the sessionKeyUserID from the session (if this key was provided).

func GetSessionUserID

func GetSessionUserID(r *http.Request) (value string, err error)

GetSessionUserID is a helper func to retrieve the value for the sessionKeyUserID from the session (if this key was provided).

func GetSessionUserIDAsInt

func GetSessionUserIDAsInt(r *http.Request) (value int64, err error)

GetSessionUserIDAsInt is a helper func to retrieve the value for the sessionKeyUserID from the session (if this key was provided). This returns the user ID as an integer, if conversion is possible, so that you do not need to call ParseInt separately after GetSessionUserID.

func GetSessionUsername

func GetSessionUsername(r *http.Request) (value string, err error)

GetSessionUsername is a helper func to retrieve the value for the sessionKeyUsername from the session (if this key was provided).

func Show

func Show(w http.ResponseWriter, subdir, templateName string, injectedData interface{})

Show returns a template as HTML. This returns the page to the user's browser. This works by taking a subdirectory's name subdir and the name of a template (filename) templateName and looks up the associated template that was parsed earlier returning it with any injected data. Note that the user provided injectedData will be available at {{.Data}} in HTML templates.

Types

type CacheBustConfig

type CacheBustConfig struct {
	//Development is used to disable cache busting when you are in development mode
	//to prevent caching issues (where you are rapidly changing files and files are
	//being cached mistakenly. Plus, we save a bit of time since we don't have to
	//create the files/hash/etc.
	Development bool

	//StaticFilePaths is a list of the complete paths to each file you want to cache
	//bust. Typically this list contains a path to your script.min.js and styles.min.css
	//files.
	StaticFilePaths []string

	//StaticsHashLength is the number of characters prepended to the begining of each
	//static file's name when saved to disk and served in your templates. This defaults
	//to 8 which is a save middleground between shorter values with possible collisions
	//(unlikely regardless) and longer values which are messy (not really valid since
	//users would never have to interact with the filenames directly). Providing a value
	//longer than the hash length will result in just the full hash being used (no extra
	//characters added to make up length).
	StaticHashLength uint

	//EnableRuntimeEdits allows this configuration to be modified while your application
	//is running. By default, this is false. It would be a very rare circumstance where
	//this you need to modify your configuration once your application is serving requests.
	//The value for this field is remembered upon the first time you call the Save() func
	//so the setting of the field can never be "undone".
	EnableRuntimeEdits bool
	// contains filtered or unexported fields
}

CacheBustConfig is the set of configuration settings for cache busting.

func (*CacheBustConfig) CreateCacheBustingFiles

func (c *CacheBustConfig) CreateCacheBustingFiles() (err error)

CreateCacheBustingFiles handles creation of the cache busting files and saving the new filenames for use when returning templates to the user/browser via Show(). This func reads the list of provided static files to be cache busted (from config), creates a hash of each, and saves a copy of each file with a partial hash prepended to the beginning of the filenames returning pairs of file names with the minified filename matched up to the cache bust file name. This func also saves the config to the package level since we need to store the list of file pairs.

Note that this function will also remove any old cache busting files from the directory where the static file is stored (and where the cache busting files will be saved) so that this directory does not get filled up with old versions of the cache busting files.

This is really the oly func that needs to be called since it will call Validate() internally.

func (*CacheBustConfig) Validate

func (c *CacheBustConfig) Validate() (err error)

Validate handles validation of the provided config data. The data is not saved to the package level variable and must be saved with CreateCacheBustingFiles() if needed (this was done so that we could separate out the tests better).

type SessionConfig

type SessionConfig struct {
	//Development is a placeholder for now.
	Development bool

	//CookieName is the name of the cookie stored in the user's browser. The
	//default value "ui_session" is used if this field is not provided.
	CookieName string

	//AuthKey and EncryptKey are used for storing the cookie value more securely.
	//If they are not provided, random values are used. You will want to provide
	//these values if you are restarting your app often, or in development, since
	//otherwise these values will change each time the app starts causing users
	//to be kicked out due to an invalid session. If you provide them, make sure
	//they are sufficiently random and AuthKey is 64 characters long while EncryptKey
	//is 32 characters long.
	AuthKey    []byte
	EncryptKey []byte

	//MaxAge is the number of seconds a cookie will be valid for and thus the
	//user's session will be valid. The default value is 3600 (1 hour).
	MaxAge uint

	//NotSecure is true when you want the cookie to be set/sent to browser in non-
	//https browsers. Typically this is true in development. We use the opposite of
	//the "Secure" field in &sessions.Options{} so that the default value of "false"
	//for this field "NotSecure" sets "Secure" to true.
	NotSecure bool

	//SameSite handles preventing the browser from leaking data in cross site requests.
	//This defaults to http.SameSiteStrictMode.
	SameSite http.SameSite

	//Domain and Path are restrictions on where the cookie can be served/used. You should
	//take care to at least set the Domain so that you don't get errors in the browser
	//or leak the cookie. Domain defaults to "." and Path defaults to "/".
	Domain string
	Path   string
	// contains filtered or unexported fields
}

func (*SessionConfig) Start

func (c *SessionConfig) Start() (err error)

Start initializes the session store and saves the config to the package level.

func (*SessionConfig) Validate

func (c *SessionConfig) Validate() (err error)

Validate handles validation of the provided config data. The data is not saved to the pacakge level variable and the session store is not initialized, this must be done with the Start() func (this was done so that we could separate out the tests better).

type SessionKeyValues

type SessionKeyValues struct {
	Key, Value string
}

SessionKeyValues holds a key value pair to be saved to the session. This is used so that we can add multiple pairs of data to a session at one time versus repeated calls to AddToSession() and repeated checks of errors.

type TemplateConfig

type TemplateConfig struct {
	//Development is a value passed into each template when the HTML is being
	//sent to the user so that the HTML can be altered based on if you are
	//running your application for development. Typically this is used to show
	//a "development" banner on the browser-dislayed page and prevents usage of
	//cache-busted versions of static files to prevent odd caching issues. This
	//also turns on logging output for diagnostics.
	Development bool

	//UseLocalFiles is a value passed into each template when the HTML is being
	//sent to the user so that the HTML can be altered to use server hosted
	//versions of JS and CSS libraries instead of using version served from a
	//CDN. Typically this is used in air-gapped or heavily firewalled areas or
	//when you simply would rather serve as many assets as possible from a local
	//server.
	UseLocalFiles bool

	//TemplatesRootPath is the full path to the directory where template files
	//are stored not including any subdirectories. There should be at least one
	//template file at this path (probably many). Typically this is the path to
	//a directory called "templates". Note that files in this directory will be
	//inherited into each subdirectory; this is particularly useful for handling
	//template files that define shared elements such as headers and footers.
	TemplatesRootPath string

	//TemplatesSubDirs is a list of subdirectories of the TemplatesRootPath where
	//you store template files. This may be empty if you have no subdirectories.
	//This must only be the actual directory names, not full paths. Full paths will
	//be constructed from TemplatesRootPath.
	TemplatesSubDirs []string

	//TemplatesFilenameExtension is the extension you use for your HTML files. This
	//defaults to "html".
	TemplatesFilenameExtension string

	//EnableRuntimeEdits allows this configuration to be modified while your application
	//is running. By default, this is false. It would be a very rare circumstance where
	//this you need to modify your configuration once your application is serving requests.
	//The value for this field is remembered upon the first time you call the Save() func
	//so the setting of the field can never be "undone".
	EnableRuntimeEdits bool

	//Debug turns on some logging output for diagnosing issues.
	Debug bool
	// contains filtered or unexported fields
}

TemplateConfig is the set of configuration settings for working with templates.

func (*TemplateConfig) Build

func (c *TemplateConfig) Build() (err error)

Build handles finding the HTML files, parsing them, and building the golang templates. This func calls Save() automatically after building the templates, and also calls Validate() if the config hasn't already been validated; therefore this is really the only func that needs to be run on a config.

This func works by looking for files with the correct extension (set via the TemplatesFilenameExtension field) in the TemplatesRootPath and in subdirectories built from TemplatesRootPath and TemplatesSubDirs. Templates in subdirectories inherit templates from the root directory (for usage of common templates such as headers, footers). Files in each subdirectory are handled independently and cannot reference a template from another subdirectory (which allows for templates that use the same name or same filename).

func (*TemplateConfig) Save

func (c *TemplateConfig) Save() (err error)

Save saves the provided config to the package level variable for use in the future when showing/returning templates as HTML to a browser.

func (*TemplateConfig) Validate

func (c *TemplateConfig) Validate() (err error)

Validate handles validation of the provided config data. The data is not saved to the package level variable and must be saved with Save() if needed (this was done so that we could separate out the tests better).

Jump to

Keyboard shortcuts

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