server

package module
v2.11.2 Latest Latest
Warning

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

Go to latest
Published: Apr 21, 2024 License: MIT Imports: 36 Imported by: 1

README

Nix Server (v2)

Overview

This package provides an HTTP/S server that can be easily configured for serving static files in few lines of code, but also scaled to have a server that can handle multiple domains and subdomains, with their logic for every incoming request, backgound tasks and event logging.


Package structure

The package mainly relies on the Server object, which listens on a specific port and forwards every connection to the inner handler.

Basic Implementation

For a basic Server configuration that serves every connection with the content of the public folder located in the working directory, here is how to configure it:

package main

import (
	"os"
	"os/signal"

	"github.com/nixpare/server/v2"
)

func main() {
	// Create a new server on port 8080, not secure
	// (that means using http and not https) and empty
	// path (so the path is the working directory)
	srv, err := server.NewServer(8080, false, "")
	if err != nil {
		panic(err)
	}

	// Register a default route that serves every files inside
	// the /public folder
	srv.RegisterDefaultRoute("Default", server.SubdomainConfig{})
	// The server starts listening on the port
	srv.Start()

	// Listens for a Control-C
	exitC := make(chan os.Signal)
	signal.Notify(exitC, os.Interrupt)
	<- exitC
	// Stops the server after the Control-C
	srv.Stop()
}
Advanced Implementation

For a more anvanced implementation we need a Router that will manage every server listening on different ports and domains, along with a TaskManager that can be used for creating panic-safe goroutines running in the background.

So we first need a router

// new router with path set to the working directory and log output
// equal to the stdout
router, _ := server.NewRouter("", nil)

that will be used to create the servers (we will only implement one)

srv, _ := router.NewServer(443, true, server.Certificate{
	CertPemPath: "path/to/fullchain_key.pem",
	KeyPemPath: "path/to/private_key.pem",
}/*, other certificates concatenated ...*/)

then on each server we can register multiple domains and subdmains

mainDomain := srv.RegisterDomain("My Domain", "mydomain.com")
localDomain := srv.RegisterDomain("Localhost Connections", "localhost")

// This registers the subdomain sub.mydomain.com
// serveFunction will be explained below
mainDomain.RegisterSubdomain("sub", server.SubdomainConfig{
	ServeF: serveFunction,
	Website: server.Website{
		Name: "My Website",
		Dir: "Custom Dir holding files",
		PageHeaders: map[string][][2]string{
			"/": {
				{"header_name_1", "value for index page"},
				{"header_name_2", "other value for index page"}
			},
			"/page": {{"header_name", "value for page page"}},
		} // The PageHeaders field can be seen as an object that maps
		  // a string to a series of string couples, each rapresenting
		  // the key-value pair for an http header
	}
})

localDomain.RegisterSubdomain(...)

The serving function - Route

To manage every connection with your own logic, the only structures you need are the Route and Website ones, and in order to let the server know which function to call you have to provide one in the ServeF field of the SubdomainConfig. The function must have a signature as descrived by the type server.ServeFunction -> func(route *server.Route), that is a simple function taking in input just the Route. This structure holds everything you need for a web request:

  • functions to serve content (ServeFile, ServeData, Error)
  • functions to manage cookies (SetCookie, DeleteCookie, DecodeCookie)
  • other functions
  • reference to the underlying http.ResponseWriter and *http.Request for using any standard http function from the stardard library (or even third party ones that need those type of structures)

The TaskManager

It can used to manage background functions and processes running in background, with panic protection to avoid the crash of the entire program and the possibility to listen for the router shutdown in order to interrupt any sensitive procedure. This functions so have a lifecycle composed of:

  • a startup function that is ran when the task is started for the first time (unless it's later stopped)
  • a cleaup function that is run when the task is stopped
  • an exec function that can be ran manually or by the task timer

The task timer determines if and how ofter the task exec function should be called and can be changed at any time after creation

Other utility functions

Inside the utility.go file of the package there are some useful functions, but mostly I highlight these two of them:

  • RandStr, which generates a random string with the given length populated with the chosen sets of characters (see CharSet constants)
  • PanicToErr, which can be used to call any function returning an error and automatically wrapping them in a panic-safe environment that converts the panic into a simple error with a helpful stack trace for the panic (or just returns the simple error of the function)
func myFunction(arg string) error {
  if arg == "" {
  	panic("Empty arg")
  }
  return errors.New(arg)
}
myArg = "argument"
err := PanicToErr(func() error { return myFunction(myArg) })
// err.Error() is not nil both when the function returned an error
// or a panic has occurred, but:
//  - in the first case, only the err.Err field will be set
//  - in the second case, both the err.PanicErr and err.Stack will be set
if err.Error() != nil {
  // ...
}

Documentation

Overview

Package server provides an HTTP/S server that can be easily configured for serving static files in few lines of code, but also scaled to have a server that can handle multiple domains and subdomains, with their logic for every incoming request, backgound tasks and event logging.

Package structure

The package mainly relies on the Server object, which listens on a specific port and forwards every connection to the inner handler.

Basic Implementation:

For a basic `Server` configuration that serves every connection with the content of the `public` folder located in the working directory, here is how to configure it:

package main

import (
  "os"
  "os/signal"

  "github.com/nixpare/server/v2"
)

func main() {
  // Create a new server on port 8080, not secure
  // (that means using http and not https) and empty
  // path (so the path is the working directory)
  srv, err := server.NewServer(8080, false, "")
  if err != nil {
  	panic(err)
  }

  // Register a default route that serves every files inside
  // the /public folder
  srv.RegisterDefaultRoute("Default", server.SubdomainConfig{})
  // The server starts listening on the port
  srv.Start()

  // Listens for a Control-C
  exitC := make(chan os.Signal)
  signal.Notify(exitC, os.Interrupt)
  <- exitC
  // Stops the server after the Control-C
  srv.Stop()
}

Advanced Implementation:

For a more anvanced implementation we need a `Router` that will manage every server listening on different ports and domains, along with a `TaskManager` that can be used for creating panic-safe goroutines running in the background.

So we first need a router

// new router with path set to the working directory and log output
// equal to the stdout
router, _ := server.NewRouter("", nil)

that will be used to create the servers (we will only implement one)

srv, _ := router.NewServer(443, true, server.Certificate{
  CertPemPath: "path/to/fullchain_key.pem",
  KeyPemPath: "path/to/private_key.pem",
}, // other certificates concatenated ...)

then on each server we can register multiple domains and subdmains

mainDomain := srv.RegisterDomain("My Domain", "mydomain.com")
localDomain := srv.RegisterDomain("Localhost Connections", "localhost")
// This registers the subdomain sub.mydomain.com
// serveFunction will be explained below
mainDomain.RegisterSubdomain("sub", server.SubdomainConfig{
  ServeF: serveFunction,
  Website: server.Website{
  	 Name: "My Website",
  	 Dir: "Custom Dir holding files",
  	 PageHeaders: map[string][][2]string{
  	   "/": {
  	   	 {"header_name_1", "value for index page"},
  	   	 {"header_name_2", "other value for index page"}
  	   },
  	   "/page": {{"header_name", "value for page page"}},
  	 } // The PageHeaders field can be seen as an object that maps
  	   // a string to a series of string couples, each rapresenting
  	   // the key-value pair for an http header
  }
})
localDomain.RegisterSubdomain(...)

The serving function - Route

To manage every connection with your own logic, the only structures you need are the `Route` and `Website` ones, and in order to let the server know which function to call you have to provide one in the `ServeF` field of the `SubdomainConfig`. The function must have a signature as descrived by the type `server.ServeFunction -> func(route *server.Route)`, that is a simple function taking in input just the Route. This structure holds everything you need for a web request:

  • functions to serve content (`ServeFile`, `ServeData`, `Error`)
  • functions to manage cookies (`SetCookie`, `DeleteCookie`, `DecodeCookie`)
  • other functions
  • reference to the underlying `http.ResponseWriter` and `*http.Request` for using any standard http function from the stardard library (or even third party ones that need those type of structures)

The TaskManager

It can used to manage background function running in background, with panic protection to avoid the crash of the entire program and the possibility to listen for the router shutdown in order to interrupt any sensitive procedure. This functions so have a lifecycle composed of:

  • a startup function that is ran when the task is started for the first time (unless it's later stopped)
  • a cleaup function that is run when the task is stopped
  • an exec function that can be ran manually or by the task timer

The task timer determines if and how ofter the task exec function should be called and can be changed at any time after creation

Other utility functions

Inside the `utility.go` file of the package there are some useful functions, but mostly I highlight these two of them:

  • `RandStr`, which generates a random string with the given length populated with the chosen sets of characters (see `CharSet` constants)
  • `PanicToErr`, which can be used to call any function returning an error and automatically wrapping them in a panic-safe environment that converts the panic into a simple error with a helpful stack trace for the panic (or just returns the simple error of the function)

Here is a usage example:

func myFunction(arg string) error {
  if arg == "" {
    panic("Empty arg")
  }
  return errors.New(arg)
}
myArg = "argument"
err := PanicToErr(func() error { return myFunction(myArg) })
// err.Error() is not nil both when the function returned an error
// or a panic has occurred, but:
//  - in the first case, only the err.Err field will be set
//  - in the second case, both the err.PanicErr and err.Stack will be set
if err.Error() != nil {
  // Handle the error
}

Index

Constants

View Source
const (
	// TASK_TIMER_10_SECONDS determines a Task execution interval of 10 seconds
	TASK_TIMER_10_SECONDS = TaskTimer(time.Second / 1000000000 * 10)
	// TASK_TIMER_1_MINUTE determines a Task execution interval of 1 minute
	TASK_TIMER_1_MINUTE = TaskTimer(time.Minute / 1000000000 * 1)
	// TASK_TIMER_10_MINUTES determines a Task execution interval of 10 minutes
	TASK_TIMER_10_MINUTES = TaskTimer(time.Minute / 1000000000 * 10)
	// TASK_TIMER_30_MINUTES determines a Task execution interval of 30 minutes
	TASK_TIMER_30_MINUTES = TaskTimer(time.Minute / 1000000000 * 30)
	// TASK_TIMER_1_HOUR determines a Task execution interval of 1 hour
	TASK_TIMER_1_HOUR = TaskTimer(time.Hour / 1000000000)
	// TASK_TIMER_INACTIVE deactivates the Task automatic execution
	TASK_TIMER_INACTIVE = -1
)

Variables

View Source
var (
	HashKeyString  = "NixPare Server"
	BlockKeyString = "github.com/nixpare/server"
)
View Source
var (
	ErrNotFound          = errors.New("not found")
	ErrAlreadyRegistered = errors.New("already registered")
)
View Source
var (
	CachedExtensions = []string{"", "txt", "html", "css", "js", "json"}
)
View Source
var (
	TimeFormat = "2006-01-02 15:04:05.00" // TimeFormat defines which timestamp to use with the logs. It can be modified.
)
View Source
var WebsocketUpgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

Functions

func DecodeCookie added in v2.2.0

func DecodeCookie[T any](route *Route, name string) (value T, found bool, err error)

DecodeCookie decodes a previously set cookie with the given name using the method route.SetCookie and returns the saved value. This is a duplicate function of the method route.DecodeCookie as a type parameter function.

More informations provided in that method

func DecodeCookiePerm added in v2.2.0

func DecodeCookiePerm[T any](route *Route, name string) (value T, found bool, err error)

DecodeCookiePerm decodes a previously set cookie with the given name using the method route.SetCookiePerm and returns the saved value. This is a duplicate function of the method route.DecodeCookiePerm as a type parameter function

More informations provided in that method

func DisableFileCache added in v2.8.3

func DisableFileCache()

func EnableFileCache added in v2.8.3

func EnableFileCache()

func GenerateHashString

func GenerateHashString(data []byte) string

GenerateHashString generate a hash with sha256 from data

func GenerateTSLConfig added in v2.5.0

func GenerateTSLConfig(certs []Certificate) (*tls.Config, error)

func RandStr

func RandStr(length int, randType CharSet) string

RandStr generates a random string with the given length. The string can be made of differente sets of characters: see CharSet type

func ReadJSON

func ReadJSON[T any](route *Route) (value T, err error)

ReadJSON unmarshals the response body and returns the value

func SetFileCacheTTL added in v2.11.0

func SetFileCacheTTL(ttl time.Duration)

func TCPPipe added in v2.10.1

func TCPPipe(conn1, conn2 net.Conn)

Types

type BeforeServeFunction added in v2.1.0

type BeforeServeFunction func(route *Route) bool

BeforeServeFunction defines the type of the function executed whenever a connection is received in the domain, even before any error handling (like domain and subdomain check), so every field inside Route is ready. The function returns a boolean, which will tell if the connection was handled by it and so the serve function must not be called. One use case for this function is in this setup: imagine having two server, one HTTP on port 80 and one HTTPS on port 443, and you want to redirect every connection that is not internal from HTTP to HTTPS (see Router.SetInternalConnFilter function)

insecureServer := route.Server(80)
insecureDomain := insecureServer.Domain("mydomain.com")

insecureDomain.SetBeforeServeF(func(route *server.Route) bool {
	if route.IsInternalConn() {
		return false 		// this is an internal connection, so it must continue inside the HTTP server
	}

	dest := "https://" + route.R.Host + route.R.RequestURI

	route.AvoidLogging = true
	http.Redirect(route.W, route.R, dest, http.StatusPermanentRedirect)

	return true 			// the external connection already received a redirect, so nothing else should happen
})

type Certificate

type Certificate struct {
	CertPemPath string // CertPemPath is the path to the full chain public key
	KeyPemPath  string // KeyPemPath is the path to the private key
}

Certificate rapresents a standard PEM certicate composed of a full chain public key and a private key. This is used when creating an HTTPS server

type CharSet added in v2.2.0

type CharSet int

CharSet groups the possible output of the function RandStr. For the possible values see the constants

const (
	NUM               CharSet = iota // Digits from 0 to 9
	ALPHA                            // Latin letters from A to z (Uppercase and Lowercase)
	ALPHA_LOW                        // Latin letters from a to z (Lowercase)
	ALPHA_NUM                        // Combination of NUM and ALPHA
	ALPHA_LOW_NUM                    // Combination of NUM and ALPHA_LOW
	ALPHA_NUM_SPECIAL                // Combines ALPHA_LOW with this special character: !?+*-_=.&%$€#@
)

type ConnHandlerFunc added in v2.5.0

type ConnHandlerFunc func(srv *TCPServer, conn net.Conn)

func TCPProxy added in v2.5.0

func TCPProxy(address string, port int) (ConnHandlerFunc, error)

type Domain

type Domain struct {
	Name string

	// BeforeServeF sets a function that will be executed before every connection.
	// If this function returns true, the serve function of the subdomain will not be
	// executed
	BeforeServeF BeforeServeFunction
	// contains filtered or unexported fields
}

Domain rapresents a website domain with all its subdomains. It's possible to set:

  • a function that will be executed (in case there are no errors) before every other logic
  • global headers, that will be applied in every connection
  • an error template, that will be used in case your logic will throw any error, so you will have a constant look

func (*Domain) DefaultSubdomain

func (d *Domain) DefaultSubdomain() *Subdomain

DefaultSubdomain returns the default subdomain, if set

func (*Domain) DisableSubdomain

func (d *Domain) DisableSubdomain(name string) error

DisableSubdomain sets a subdomain to offline state

func (*Domain) EnableSubdomain

func (d *Domain) EnableSubdomain(name string) error

EnableSubdomain sets a subdomain to online state

func (*Domain) Headers added in v2.2.0

func (d *Domain) Headers() http.Header

Headers returns the default headers of the domain

func (*Domain) RegisterDefaultSubdomain

func (d *Domain) RegisterDefaultSubdomain(c SubdomainConfig) (*Subdomain, error)

RegisterDefaultSubdomain registers a subdomain that is called if no other one matches perfectly the incoming connection for the same domain

func (*Domain) RegisterSubdomain

func (d *Domain) RegisterSubdomain(subdomain string, c SubdomainConfig) (*Subdomain, error)

RegisterSubdomain registers a subdomain in the domain. It's asked to specify the subdomain name (with or without trailing dot) and its configuration. It the Website Dir field is empty it will be used the default value of "<srv.Path>/public", instead if it's not absolute it will be relative to the srv.Path

func (*Domain) RemoveHeader

func (d *Domain) RemoveHeader(name string)

RemoveHeader removes a header with the given name

func (*Domain) SetErrorTemplate

func (d *Domain) SetErrorTemplate(content string) error

SetErrorTemplate sets the error template used server-wise. It's required an HTML that contains two specific fields, a .Code one and a .Message one, for example like so:

<h2>Error {{ .Code }}</h2>
<p>{{ .Message }}</p>

func (*Domain) SetHeader

func (d *Domain) SetHeader(name, value string)

SetHeader adds a header to the collection of headers used in every connection

func (*Domain) SetHeaders

func (d *Domain) SetHeaders(headers [][2]string)

SetHeaders adds headers to the collection of headers used in every connection. This is a faster way to set multiple headers at the same time, instead of using domain.SetHeader. The headers must be provided in this way:

headers := [][2]string {
	{ "name1", "value1" },
	{ "name2", "value2" },
}
d.SetHeaders(headers)

func (*Domain) Subdomain

func (d *Domain) Subdomain(name string) *Subdomain

Subdomain returns the subdomain with the given name, if found

type HTTPServer added in v2.5.0

type HTTPServer struct {
	// Secure is set to indicate whether the server is using
	// the HTTP or HTTPS protocol
	Secure bool

	// Online tells wheter the server is responding to external requests
	Online bool
	// OnlineTime reports the last time the server was activated or resumed
	OnlineTime time.Time
	// Server is the underlying HTTP server from the standard library
	Server *http.Server
	// HTTP3Server is the QUIC Server, if is nil if the Server is not secure
	HTTP3Server *http3.Server

	// Router is a reference to the Router (is the server was created through it).
	// This should not be set by hand.
	Router *Router
	Logger logger.Logger
	// IsInternalConn can be used to additionally add rules used to determine whether
	// an incoming connection must be treated as from a client in the local network or not.
	// This is used both for the method route.IsInternalConn and for accessing other domains
	// via the http queries from desired IPs. By default, only the connection coming from
	// "localhost", "127.0.0.1" and "::1" are treated as local connections.
	IsInternalConn func(remoteAddress string) bool
	IsLocalhost    func(host string) bool

	// ServerPath is the path provided on server creation. It is used as the log location
	// for this specific server
	ServerPath string
	// contains filtered or unexported fields
}

HTTPServer is a single HTTP server listening on a TCP port. It can handle multiple domains and subdomains. To manage multiple servers listening on different ports use a Router.

Before creating any server you should change the HashKeyString and BlockKeyString global variables: see Route.SetCookiePerm method

func NewHTTPServer added in v2.5.0

func NewHTTPServer(address string, port int, secure bool, path string, certs ...Certificate) (*HTTPServer, error)

NewServer creates a new server

func (*HTTPServer) DefaultDomain added in v2.5.0

func (srv *HTTPServer) DefaultDomain() *Domain

DefaultDomain returns the default domain, if set

func (*HTTPServer) Domain added in v2.5.0

func (srv *HTTPServer) Domain(domain string) *Domain

Domain returns the domain with the given name registered in the server, if found

func (*HTTPServer) Header added in v2.5.0

func (srv *HTTPServer) Header() http.Header

Header returns the underlying http.Header intance

func (*HTTPServer) IsRunning added in v2.5.0

func (srv *HTTPServer) IsRunning() bool

IsRunning tells whether the server is running or not

func (*HTTPServer) Port added in v2.5.0

func (srv *HTTPServer) Port() int

Port returns the TCP port listened by the server

func (*HTTPServer) RegisterDefaultDomain added in v2.5.0

func (srv *HTTPServer) RegisterDefaultDomain(displayName string) (*Domain, error)

RegisterDefaultDomain registers a domain that is called if no other domain matches perfectly the incoming connection

func (*HTTPServer) RegisterDefaultRoute added in v2.5.0

func (srv *HTTPServer) RegisterDefaultRoute(displayName string, c SubdomainConfig) (*Domain, *Subdomain, error)

RegisterDefaultRoute is a shortcut for registering the default logic applied for every connection not matching any other specific domain and subdomain. It's the combination of srv.RegisterDefaultDomain(displayName).RegisterDefaultSubdomain(c)

func (*HTTPServer) RegisterDomain added in v2.5.0

func (srv *HTTPServer) RegisterDomain(displayName, domain string) (*Domain, error)

RegisterDomain registers a domain in the server. It's asked to specify a display name used in the logs and the effective URL of the domain (do not specify any protocol or port). If the domain name is an empy string it will be treated as the default domain (see srv.RegisterDefaultDomain)

func (*HTTPServer) RemoveHeader added in v2.5.0

func (srv *HTTPServer) RemoveHeader(name string) *HTTPServer

RemoveHeader removes an HTTP header with the given name from the Server specific headers

func (*HTTPServer) SetErrorTemplate added in v2.5.0

func (srv *HTTPServer) SetErrorTemplate(content string) error

SetErrorTemplate sets the error template used server-wise. It's required an HTML that contains two specific fields, a .Code one and a .Message one, for example like so:

<h2>Error {{ .Code }}</h2>
<p>{{ .Message }}</p>

func (*HTTPServer) SetHeader added in v2.5.0

func (srv *HTTPServer) SetHeader(name, value string) *HTTPServer

SetHeader adds an HTTP header that will be set at every connection accepted by the Server

func (*HTTPServer) SetHeaders added in v2.5.0

func (srv *HTTPServer) SetHeaders(headers [][2]string) *HTTPServer

SetHeaders accepts a matrix made of couples of string, each of one rapresents an HTTP header with its key and value. This is a shorthand for not calling multiple times Server.SetHeader. It can be used like this:

srv.SetHeaders([][2]string{
	{"header_name_1", "header_value_1"},
	{"header_name_2", "header_value_2"},
})

func (*HTTPServer) Start added in v2.5.0

func (srv *HTTPServer) Start()

Start prepares every domain and subdomain and starts listening on the TCP port

func (*HTTPServer) Stop added in v2.5.0

func (srv *HTTPServer) Stop()

Stop cleans up every domain and subdomain and stops listening on the TCP port

type InitCloseFunction

type InitCloseFunction func(srv *HTTPServer, domain *Domain, subdomain *Subdomain) error

InitCloseFunction defines the type of the function executed when a new subdomain is created or removed, usually when the relative server is started or stopped. Bear in mind that if you use the same function on multiple subdomain, maybe belonging to different servers, you could have to manually check that this function is done only once

type LifeCycle added in v2.5.0

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

func NewLifeCycleState added in v2.5.0

func NewLifeCycleState() *LifeCycle

func (*LifeCycle) AlreadyStarted added in v2.5.0

func (state *LifeCycle) AlreadyStarted() bool

func (*LifeCycle) AlreadyStopped added in v2.5.0

func (state *LifeCycle) AlreadyStopped() bool

func (*LifeCycle) GetLock added in v2.5.0

func (state *LifeCycle) GetLock() *sync.RWMutex

func (*LifeCycle) GetState added in v2.5.0

func (state *LifeCycle) GetState() LifeCycleState

func (*LifeCycle) SetState added in v2.5.0

func (state *LifeCycle) SetState(s LifeCycleState)

type LifeCycleState added in v2.5.0

type LifeCycleState int
const (
	LCS_STOPPED LifeCycleState = iota
	LCS_STOPPING
	LCS_STARTING
	LCS_STARTED
)

type PHPProcessor added in v2.4.0

type PHPProcessor struct {
	Process *process.Process
	Logger  logger.Logger
	// contains filtered or unexported fields
}

func NewPHPProcessor added in v2.5.2

func NewPHPProcessor(port int, args ...string) (php *PHPProcessor, err error)

func (*PHPProcessor) Start added in v2.4.0

func (php *PHPProcessor) Start() error

func (*PHPProcessor) Stop added in v2.4.0

func (php *PHPProcessor) Stop() error

type ResponseWriter

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

ResponseWriter is just a wrapper for the standard http.ResponseWriter interface, the only difference is that it keeps track of the bytes written and the status code, so that can be logged

func (*ResponseWriter) Header

func (w *ResponseWriter) Header() http.Header

Header is the equivalent of the http.ResponseWriter method

func (*ResponseWriter) Write

func (w *ResponseWriter) Write(data []byte) (int, error)

Write is the equivalent of the http.ResponseWriter method

func (*ResponseWriter) WriteHeader

func (w *ResponseWriter) WriteHeader(statusCode int)

WriteHeader is the equivalent of the http.ResponseWriter method but handles multiple calls, using only the first one used

type Route

type Route struct {
	// W wraps the [http.ResponseWriter] returned by the standard
	// [http.Server], handles multiple W.WriteHeader calls and captures
	// metrics for the connection (time, bytes written and status code).
	// Can be used in conjunction with R to use the standard functions
	// provided by Go, such as [http.ServeContent]
	W *ResponseWriter
	// R is the standard [http.Request] without any modification
	R *http.Request
	// Srv is a reference to the server this connection went through
	Srv *HTTPServer
	// Router is a reference to the router this server connection belongs to
	Router *Router
	Logger logger.Logger
	// Secure is set to tell wheather the current connection is using HTTP (false)
	// or HTTPS(true), so you can use one Routing function for secure and unsecure
	// websites
	Secure bool
	// Secure is set to tell wheather the current connection is using HTTP/3
	IsHTTP3 bool
	// Host contains the address used by the client, so it could be an IP address
	// or a domain name. This is generated from the R field
	Host string
	// Remote address contains the IP address of the client connecting (without ports).
	// This is generated from the R field
	RemoteAddress string
	// Website is a reference to the Website structure provided at startup when registering
	// the server domains and subdomains
	Website *Website
	// DomainName contains the request domain name parsed from the Host
	DomainName string
	// SubdomainName contains the request subdomain name parsed from the Host
	SubdomainName string
	// Domain is the domain the connection went through
	Domain *Domain
	// Subdomain is the subdomain the connection went through inside the domain
	Subdomain *Subdomain
	// RequestURI is the http.Request uri sanitized, parsed and separated from the queries
	// in order to have a clean path
	RequestURI string
	// Method tells which HTTP method the connection is using
	Method string

	// QueryMap contains all the queries retreived from the request uri
	QueryMap map[string]string
	// ConnectionTime is the timestamp that refers to the request arrival
	ConnectionTime time.Time
	// AvoidLogging is a flag that can be set by the user to tell that this connection should not be logged;
	// however this only happens when the connection returns a successful status code
	AvoidLogging bool
	// contains filtered or unexported fields
}

Route wraps the incoming HTTP connection and provides simple and commonly use HTTP functions, some coming also from the standard library. It contains the standard http.ResponseWriter and *http.Request elements used to handle a connection with the standard library, but adds more to integrate with the router -> server -> website structure of this package

func (*Route) CompressedServe added in v2.9.2

func (route *Route) CompressedServe(serveF http.HandlerFunc, compressLevel int)

func (*Route) DecodeCookie

func (route *Route) DecodeCookie(name string, value any) (found bool, err error)

DecodeCookie decodes a previously set cookie with the given name using the method route.SetCookie.

If the cookie was not found, it will return false and the relative error (probably an http.ErrNoCookie), otherwise it will return true and, possibly, the decode error. It happends when:

  • the server was restarted, so the keys used for decoding are different
  • you provided the wrong value type
  • the cookie was not set by the server

The argument value must be a pointer, otherwise the value will not be returned. A workaround might be using the type parametric function server.DecodeCookie

func (*Route) DecodeCookiePerm

func (route *Route) DecodeCookiePerm(name string, value any) (found bool, err error)

DecodeCookiePerm decodes a previously set cookie with the given name using the method route.SetCookiePerm.

If the cookie was not found, it will return false and the relative error (probably an http.ErrNoCookie), otherwise it will return true and, possibly, the decode error. It happends when:

  • you provided the wrong value type
  • the cookie was not set by the server

The argument value must be a pointer, otherwise the value will not be returned. A workaround might be using the type parametric function server.DecodeCookiePerm

func (*Route) DeleteCookie

func (route *Route) DeleteCookie(name string)

DeleteCookie instantly removes a cookie with the given name before set with route.SetCookie or route.SetCookiePerm

func (*Route) Error

func (route *Route) Error(statusCode int, message any, a ...any)

Error is used to manually report an HTTP error to send to the client.

It sets the http status code (so it should not be set before) and if the connection is done via a GET request, it will try to serve the html error template with the status code and error message in it, otherwise if the error template does not exist or the request is done via another method (like POST), the error message will be sent as a plain text.

The last optional list of elements can be used just for logging or debugging: the elements will be saved in the logs

func (*Route) Errorf added in v2.2.2

func (route *Route) Errorf(statusCode int, message string, format string, a ...any)

Errorf is like the method Route.Error but you can format the output to the Log. Like the Route.Logf, everything that is after the first line feed will be used to populate the extra field of the Log

func (*Route) IsInternalConn

func (route *Route) IsInternalConn() bool

IsInternalConn tells wheather the incoming connection should be treated as a local connection. The user can add a filter that can extend this selection to match their needs

func (*Route) IsLocalhost added in v2.5.3

func (route *Route) IsLocalhost() bool

func (*Route) NewReverseProxy added in v2.5.3

func (route *Route) NewReverseProxy(dest string) (*httputil.ReverseProxy, error)

func (*Route) RespBody added in v2.2.0

func (route *Route) RespBody() ([]byte, error)

RespBody returns the response body bytes

func (*Route) RespBodyString added in v2.2.0

func (route *Route) RespBodyString() (string, error)

RespBodyString returns the response body as a string

func (*Route) ReverseProxy

func (route *Route) ReverseProxy(dest string) error

ReverseProxy runs a reverse proxy to the provided url. Returns an error is the url could not be parsed or if an error has occurred during the connection

func (*Route) ServeCompressedContent added in v2.9.2

func (route *Route) ServeCompressedContent(name string, modtime time.Time, content io.ReadSeeker, compressLevel int)

func (*Route) ServeContent added in v2.11.0

func (route *Route) ServeContent(name string, modtime time.Time, content io.ReadSeeker)

func (*Route) ServeCustomFile

func (route *Route) ServeCustomFile(fileName string, data []byte)

ServeCustomFile serves a pseudo-file saved in memory. The name of the file is important for MIME type detection

func (*Route) ServeCustomFileWithTime

func (route *Route) ServeCustomFileWithTime(fileName string, data []byte, t time.Time)

ServeCustomFileWithTime will serve a pseudo-file saved in memory specifing the last modification time. The name of the file is important for MIME type detection

func (*Route) ServeData

func (route *Route) ServeData(data []byte)

ServeData serves raw bytes to the client

func (*Route) ServeFile

func (route *Route) ServeFile(filePath string)

ServeFile will serve a file in the file system. If the path is not absolute, it will first try to complete it with the website directory (if set) or with the server path

func (*Route) ServePHP added in v2.4.0

func (route *Route) ServePHP(php *PHPProcessor)

func (*Route) ServeText

func (route *Route) ServeText(text string)

ServeText serves a string (as raw bytes) to the client

func (*Route) ServeWS added in v2.5.0

func (route *Route) ServeWS(wsu websocket.Upgrader, h func(route *Route, conn *websocket.Conn))

func (*Route) SetCookie

func (route *Route) SetCookie(name string, value any, maxAge int) error

SetCookie creates a new cookie with the given name and value, maxAge can be used to sex the expiration date:

  • maxAge = 0 means no expiration specified
  • maxAge > 0 sets the expiration date from the current date adding the given time in seconds (- maxAge < 0 will remove the cookie instantly, like route.DeleteCookie)

The cookie value is encoded and encrypted using a pair of keys created randomly at server creation, so if the same cookie is later decoded between server restart, it can't be decoded. To have such a behaviour see SetCookiePerm.

The encoding of the value is managed by the package encoding/gob. If you are just encoding and decoding plain structs and each field type is a primary type or a struct (with the same rules), nothing should be done, but if you are dealing with interfaces, you must first register every concrete structure or type implementing that interface before encoding or decoding

func (*Route) SetCookiePerm

func (route *Route) SetCookiePerm(name string, value any, maxAge int) error

SetCookiePerm creates a new cookie with the given name and value, maxAge can be used to sex the expiration date:

  • maxAge = 0 means no expiration specified
  • maxAge > 0 sets the expiration date from the current date adding the given time in seconds (- maxAge < 0 will remove the cookie instantly, like route.DeleteCookie)

The cookie value is encoded and encrypted using a pair of keys at package level that MUST be set at program startup. This differs for the method route.SetCookie to ensure that even after server restart these cookies can still be decoded.

func (*Route) SetErrorCapture added in v2.9.12

func (route *Route) SetErrorCapture(enable bool)

func (*Route) StaticServe

func (route *Route) StaticServe(serveHTML bool)

StaticServe tries to serve a file for every connection done via a GET request, following all the options provided in the Website configuration. This means it will not serve any file inside (also nested) a hidden folder, it will serve an HTML file only with the flag argument set to true, it will serve index.html automatically for connection with request uri empty or equal to "/", it will serve every file inside the AllFolders field of the Website

type Router

type Router struct {

	// The Path provided when creating the Router or the working directory
	// if not provided. This defines the path for every server registered
	Path string

	TaskManager *TaskManager
	Logger      logger.Logger
	// contains filtered or unexported fields
}

Router is the main element of this package and is used to manage all the servers and the background tasks.

func NewRouter

func NewRouter(l logger.Logger, routerPath string) (router *Router, err error)

NewRouter returns a new Router ready to be set up. If routerPath is not provided, the router will try to get the working directory; if logger is nil, the standard logger.DefaultLogger will be used

func (*Router) HTTPServer added in v2.5.0

func (router *Router) HTTPServer(port int) *HTTPServer

Server returns the HTTP server running on the given port

func (*Router) IsRunning added in v2.2.1

func (router *Router) IsRunning() bool

func (*Router) NewHTTPServer added in v2.5.0

func (router *Router) NewHTTPServer(address string, port int, secure bool, path string, certs ...Certificate) (*HTTPServer, error)

NewServer creates a new HTTP/HTTPS Server linked to the Router. See NewServer function for more information

func (*Router) NewTCPServer added in v2.5.0

func (router *Router) NewTCPServer(address string, port int, secure bool, certs ...Certificate) (*TCPServer, error)

NewServer creates a new TCP Server linked to the Router. See NewTCPServer function for more information

func (*Router) Start

func (router *Router) Start()

Start starts all the registered servers and the background task manager

func (*Router) StartTime added in v2.6.9

func (router *Router) StartTime() time.Time

func (*Router) Stop

func (router *Router) Stop()

Stop starts the shutdown procedure of the entire router with all the servers registered, the background programs and tasks and lastly executes the router.CleanupF function, if set

func (*Router) TCPServer added in v2.5.0

func (router *Router) TCPServer(port int) *TCPServer

Server returns the TCP server running on the given port

type ServeFunction

type ServeFunction func(route *Route)

ServeFunction defines the type of the function that is executed every time a connection is directed to the relative website / subdomain. It just takes the Route as a parameter, but it can be used for everything, like the method, a parsed request uri, a map with all the queries (removed from the request uri so the latter is a clean relative path) and even the http.ResponseWriter and *http.Request in order to use the standard HTTP package functions. However, Route provide a lot of prebuild functions for comodity, all base on the HTTP standard package

type Subdomain

type Subdomain struct {
	Name    string
	Website *Website
	// contains filtered or unexported fields
}

Subdomain rapresents a particular subdomain in a domain with all the logic. It's required a serve function, which will determine the logic of the website, and a Website, with all its options. It's possible to set:

  • default headers, that will be applied in every connection
  • an error template, that will be used in case your logic will throw any error, so you will have a constant look
  • the subdomain offline state
  • an initializer function, called when the server is starting up
  • a cleanup function, called when the server is shutting down

func (*Subdomain) Disable

func (sd *Subdomain) Disable()

Disable sets the subdomain to offline state

func (*Subdomain) Enable

func (sd *Subdomain) Enable() error

Enable sets the subdomain to online state

func (*Subdomain) Header

func (sd *Subdomain) Header() http.Header

Header returns the default headers

func (*Subdomain) RemoveHeader

func (sd *Subdomain) RemoveHeader(name string)

RemoveHeader removes a header with the given name

func (*Subdomain) SetErrorTemplate

func (sd *Subdomain) SetErrorTemplate(content string) error

SetErrorTemplate sets the error template used server-wise. It's required an HTML that contains two specific fields, a .Code one and a .Message one, for example like so:

<h2>Error {{ .Code }}</h2>
<p>{{ .Message }}</p>

func (*Subdomain) SetHeader

func (sd *Subdomain) SetHeader(name, value string)

SetHeader adds a header to the collection of headers used in every connection

func (*Subdomain) SetHeaders

func (sd *Subdomain) SetHeaders(headers [][2]string)

SetHeaders adds headers to the collection of headers used in every connection. This is a faster way to set multiple headers at the same time, instead of using subdomain.SetHeader. The headers must be provided in this way:

headers := [][2]string {
	{ "name1", "value1" },
	{ "name2", "value2" },
}
d.SetHeaders(headers)

type SubdomainConfig

type SubdomainConfig struct {
	// Website will be copied to the subdomain and later will be
	// linked in every connection
	Website Website
	// ServeF is the function holding the logic behind the website
	ServeF ServeFunction
	// InitF is the function called upon server startup
	InitF InitCloseFunction
	// CloseF is the function called upon server shutdown
	CloseF InitCloseFunction
}

SubdomainConfig is used to create a Subdomain. The Website should not be an empty struct and if ServeF is not set the server will serve every content inside the Website.Dir folder (see Route.StaticServe(true) for the logic and Domain.RegisterSubdomain for the folder behaviour), however InitF and CloseF are optional

type TCPServer added in v2.5.0

type TCPServer struct {
	Online bool

	ConnHandler ConnHandlerFunc
	Router      *Router
	Logger      logger.Logger
	// contains filtered or unexported fields
}

func NewTCPServer added in v2.5.0

func NewTCPServer(address string, port int, secure bool, certs ...Certificate) (*TCPServer, error)

func (*TCPServer) Address added in v2.5.0

func (srv *TCPServer) Address() string

func (*TCPServer) IsSecure added in v2.5.0

func (srv *TCPServer) IsSecure() bool

func (*TCPServer) Port added in v2.5.0

func (srv *TCPServer) Port() int

func (*TCPServer) Start added in v2.5.0

func (srv *TCPServer) Start()

func (*TCPServer) Stop added in v2.5.0

func (srv *TCPServer) Stop() error

type Task

type Task struct {
	InitF    TaskFunc  // InitF is the function called when the Task is started
	ExecF    TaskFunc  // ExecF is the function called every time the Task must be executed (from the timer or manually)
	CleanupF TaskFunc  // CleanupF is the function called when the Task is removed from the TaskManager or when the TaskManager is stopped (e.g. on Router shutdown)
	Timer    TaskTimer // TaskTimer is the Task execution interval, that is how often the function ExecF is called

	TaskManager *TaskManager
	Logger      logger.Logger
	// contains filtered or unexported fields
}

Task is composed of a name set upon creation and of 3 functions necessary of the correct execution of a kind of program. Every function is panic-protected, this means that the entire server will not crash when some parts of the task fails badly; this does not mean that you can't handle panics by yourself, but if they are not handled its like catching them and returning their message as an error. If a function returns an error, this will be logged, providing the task name and which function called it automatically (the task will be disabled if the initialization fails, but you can do it manually, see router.SetBackgroundTaskState)

func (*Task) Init added in v2.6.0

func (t *Task) Init() error

Init runs the initialization function, catching every possible error or panic, and then sets the flag Task.initDone to true. If the function fails it deactivates the task

func (*Task) IsReady

func (t *Task) IsReady() bool

func (*Task) IsRunning

func (t *Task) IsRunning() bool

func (*Task) ListenForExit

func (t *Task) ListenForExit() bool

ListenForExit waits until the exit signal is received from the manager. This signal is sent when you manually stop a task or the server is shutting down: in the last case the manager will wait for a maximum of 10 seconds, after those, if the execution is not finished, it will first kill the task and then call the cleanup function. This function is intended to be called in a goroutine listening for the signal: considering that the goroutine could stay alive even after the task exec function has exited, if this function returns true, this means that the signal is received correctly for that execution and you should exit, otherwise this means that the execution of the task has already terminated and thus you should not do anything Example:

execF = func(tm *server.TaskManager, t *server.Task) error {
	go func () {
		if !t.ListenForExit() {
			return 		// doing nothing because it returned false
		}
		// DO SOME FAST RECOVERY
	}()
	// SOME LONG RUNNING EXECUTION
}

func (*Task) Name

func (t *Task) Name() string

Name returns the name of the function

func (*Task) String added in v2.6.0

func (t *Task) String() string

func (*Task) Wait

func (t *Task) Wait()

type TaskFunc

type TaskFunc func(t *Task) error

TaskFunc is the executable part of the program. You can modify the timer of the task and also the functions themselves! See TaskInitFunc, NewTask and router.RegisterBackgroundTask for the creation of a Task

type TaskInitFunc

type TaskInitFunc func() (initF, execF, cleanupF TaskFunc)

TaskInitFunc is called when creating a task and is provided by the user. This function need to return 3 TaskFunc (they can be nil) and they will be set to the created task. The kind of functions are:

  • the initialization function: called only upon creation, if it fails (panics or returns an error) the task will be disabled automatically
  • the exec function: called every time, could be interrupted if the server is shutting down; in this case, you will receive a signal on Task.ListenForExit, after that you will have 10 seconds before the server will call the cleanup function and exit
  • the cleanup function: called when the server is shutting down, this must not be potentially blocking (must end in a reasonable time)

Example of usage:

 func() {
	taskInitF := func() (initF, execF, cleanupF TaskFunc) {
		var myNeededValiable package.AnyType
		initF = func(tm *server.TaskManager, t *server.Task) {
			myNeededVariable = package.InitializeNewValiable()
			// DO SOME OTHER STUFF WITH router AND t
		}
		execF = func(tm *server.TaskManager, t *server.Task) {
			myNeededVariable.UseValiable()
			// DO SOME OTHER STUFF WITH router AND t
		}
		cleaunpF = func(tm *server.TaskManager, t *server.Task) {
			// DO SOME OTHER STUFF WITH router AND t
			myNeededVariable.DestroyValiable()
		}
		return
	}
	task := tm.NewTask("myTask", taskInitF, server.TaskTimerInactive)
}

type TaskManager

type TaskManager struct {
	Router *Router
	Logger logger.Logger
	// contains filtered or unexported fields
}

TaskManager is a component of the Router that controls the execution of external processes and tasks registered by the user

func (*TaskManager) ExecTask

func (tm *TaskManager) ExecTask(name string) error

ExecTask runs the Task immediatly

func (*TaskManager) FindProcess added in v2.3.0

func (tm *TaskManager) FindProcess(name string) (*process.Process, error)

FindProcess finds if a process with the given name is registered in the process map

func (*TaskManager) GetProcess added in v2.6.0

func (tm *TaskManager) GetProcess(name string) *process.Process

GetProcess returns the process registered with the given name, or nil if not found

func (*TaskManager) GetProcessesNames added in v2.3.0

func (tm *TaskManager) GetProcessesNames() []string

GetProcessesNames returns a slice containing all the names of the registered processes

func (*TaskManager) GetTask added in v2.6.0

func (tm *TaskManager) GetTask(name string) *Task

func (*TaskManager) GetTasksNames

func (tm *TaskManager) GetTasksNames() []string

GetTasksNames returns all the names of the registered tasks in the TaskManager

func (*TaskManager) KillProcess added in v2.3.0

func (tm *TaskManager) KillProcess(name string) error

KillProcess forcibly kills the process with the given name

func (*TaskManager) KillTask added in v2.6.0

func (tm *TaskManager) KillTask(name string) error

RemoveTask runs the cleanup function provided and removes the Task from the TaskManager

func (*TaskManager) NewProcess added in v2.3.0

func (tm *TaskManager) NewProcess(name, dir string, execName string, args ...string) error

NewProcess creates a new Process with the given parameters. The process name must be a unique. It's possible to wait for its termination on multiple goroutines by calling the Wait method, and craceful shutdown is implemented in every operating system

The underlying process is described in the package github.com/nixpare/process

func (*TaskManager) NewTask

func (tm *TaskManager) NewTask(name string, f TaskInitFunc, timer TaskTimer) error

NewTask creates and registers a new Task with the given name, displayName, initialization function (f TaskInitFunc) and execution timer, the TaskManager initialize it calling the initF function provided by f (if any). If it returns an error the Task will not be registered in the TaskManager.

func (*TaskManager) ProcessIsRunning added in v2.3.0

func (tm *TaskManager) ProcessIsRunning(name string) (bool, error)

ProcessIsRunning tells if the process is running or not

func (*TaskManager) RemoveTask

func (tm *TaskManager) RemoveTask(name string) error

RemoveTask runs the cleanup function provided and removes the Task from the TaskManager

func (*TaskManager) RestartProcess added in v2.3.0

func (tm *TaskManager) RestartProcess(name string) error

RestartProcess first gracefully stops the process and then starts it again

func (*TaskManager) StartProcess added in v2.3.0

func (tm *TaskManager) StartProcess(name string) error

StartProcess starts an already registered process if it's not running. This method just waits for the successful start-up of the process, but It does not wait for the termination. For this, call the Wait method.

Also, this function starts the Process enabling the pipe for the standard input and the capture of the standard output, disables any real input/output and automatically logs an error if the exit status is not successfull. You can always manually call the Start method on the Process

func (*TaskManager) StopProcess added in v2.3.0

func (tm *TaskManager) StopProcess(name string) error

StopProcess tries to gracefully stop the process with the given name

func (*TaskManager) WaitProcess added in v2.3.0

func (tm *TaskManager) WaitProcess(name string) (process.ExitStatus, error)

WaitProcess waits for the termination of the process and returns process information

type TaskTimer

type TaskTimer int

TaskTimer tells the TaskManager how often a Task should be executed. See the constants for the values accepted by the TaskManager

type VirtualFile added in v2.11.0

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

func NewVirtualFile added in v2.11.0

func NewVirtualFile(size int) *VirtualFile

func (*VirtualFile) Len added in v2.11.0

func (vf *VirtualFile) Len() int

func (*VirtualFile) NewReader added in v2.11.0

func (vf *VirtualFile) NewReader() io.ReadSeeker

func (*VirtualFile) Size added in v2.11.0

func (vf *VirtualFile) Size() int

func (*VirtualFile) Write added in v2.11.0

func (vf *VirtualFile) Write(b []byte) (n int, err error)

type Website

type Website struct {
	// Name is used in the log information
	Name string
	// Dir is the root folder of the Website. If it's not set
	// while creating the Website, this is set to the server path + /public
	// folder of the domain in which is registered, otherwise if it's a
	// relative path, it's considered relative to the server path.
	// This is also used by the function Route.ServeStatic to
	// automatically serve any content (see AllFolders attribute)
	Dir string
	// MainPages sets which are the pages that we want to keep track
	// of the statistics. For now the logic is not there yet, so this
	// field is not used
	MainPages []string
	// NoLogPages sets which are the requestURIs that we do not want to
	// register logs about
	NoLogPages []string
	// AllFolders specify which folders can be used by the route.ServeStatic
	// to automatically serve content. The selection is recursive. If you want
	// to serve any content in the Website.Dir is possible to fill this field
	// with just an empty string
	AllFolders []string
	// HiddenFolders it's the opposite of AllFolders, but has a higher priority:
	// it specifies which folders must not be used by the route.ServeStatic
	// to automatically serve content, even if a AllFolder entry could match
	HiddenFolders []string
	// PageHeaders is used to set automatic HTTP headers to the corresponding
	// requestURI. This can be set like so:
	/*
		var w Website
		w.PageHeader = map[string][][2]string {
			"/": {
				{"Header name", "Header value"},
				{"Header name 1", "Header value 1"},
			},
			"/address": {{"Header name 2", "Header value 2"}},
		}
	*/
	PageHeaders map[string][][2]string
	// XFiles maps requested file paths to existing files that can be used to create and serve XFile
	// virtual files. If the value in the map is an empty string, this means that the file used to
	// create the XFile corresponds, otherwise you can map it to a completely different file. The value
	// can be a relative path (to the Website.Dir) or an absolute path, but the key part must be the
	// desired request uri to match for the resource
	//
	// For example: if the XFiles attribute is set to
	//   XFiles: map[string]string{ "/assets/css/index.css": "assets/css/X_INDEX.css" }
	// and a request comes with a URI of https://<my_domain>/assets/css/index.css, the server will
	// use the file Website.Dir + / + assets/css/X_INDEX.css to create the XFile and then serve it
	XFiles map[string]string
	// AvoidMetricsAndLogging disables any type of log for every connection and error regarding
	// this website (if not explicitly done by the logic calling Route.Log)
	AvoidMetricsAndLogging bool
}

Website is used in a subdomain to serve content with its logic, in combination with a ServeFunction and (optionally) a pair of InitCloseFunction

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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