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
- Variables
- func GenerateTSLConfig(certs []Certificate) (*tls.Config, error)
- func PrepSubdomainName(name string) string
- func RandStr(length int, randType CharSet) string
- func SplitAddrPort(host string) string
- func TCPPipe(conn1, conn2 net.Conn)
- type Certificate
- type CharSet
- type ConnHandlerFunc
- type HTTPServer
- type Router
- func (router *Router) CustomServer(port int) Server
- func (router *Router) HTTPServer(port int) *HTTPServer
- func (router *Router) IsRunning() bool
- func (router *Router) NewHTTPServer(address string, port int, certs ...Certificate) (*HTTPServer, error)
- func (router *Router) NewTCPServer(address string, port int, secure bool, certs ...Certificate) (*TCPServer, error)
- func (router *Router) RegisterCustomServer(srv Server) error
- func (router *Router) Start()
- func (router *Router) StartTime() time.Time
- func (router *Router) Stop()
- func (router *Router) TCPServer(port int) *TCPServer
- type Server
- type ServerRouter
- type TCPServer
- type Task
- type TaskFunc
- type TaskInitFunc
- type TaskManager
- func (tm *TaskManager) ExecTask(name string) error
- func (tm *TaskManager) FindProcess(name string) (*process.Process, error)
- func (tm *TaskManager) GetProcess(name string) *process.Process
- func (tm *TaskManager) GetProcessesNames() []string
- func (tm *TaskManager) GetTask(name string) *Task
- func (tm *TaskManager) GetTasksNames() []string
- func (tm *TaskManager) KillProcess(name string) error
- func (tm *TaskManager) NewProcess(name, dir string, execName string, args ...string) (*process.Process, error)
- func (tm *TaskManager) NewTask(name string, f TaskInitFunc, timer TaskTimer) error
- func (tm *TaskManager) ProcessIsRunning(name string) (bool, error)
- func (tm *TaskManager) RemoveTask(name string) error
- func (tm *TaskManager) RestartProcess(name string) error
- func (tm *TaskManager) StartProcess(name string) error
- func (tm *TaskManager) StopProcess(name string) error
- func (tm *TaskManager) StopTask(name string) error
- func (tm *TaskManager) WaitProcess(name string) (process.ExitStatus, error)
- type TaskTimer
Constants ¶
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 ¶
var ( ErrNotFound = errors.New("not found") ErrAlreadyRegistered = errors.New("already registered") )
var ( TLSDefaultCipherSuiteIDs []uint16 TLSDefaultCurvePreferences []tls.CurveID )
Used for creating a secure HTTP Server. See the tls.Config struct in the standard library for more informations
var (
TimeFormat = "2006-01-02 15:04:05.00" // TimeFormat defines which timestamp to use with the logs. It can be modified.
)
Functions ¶
func GenerateTSLConfig ¶
func GenerateTSLConfig(certs []Certificate) (*tls.Config, error)
func PrepSubdomainName ¶
PrepSubdomainName sanitizes the subdomain name
func RandStr ¶
RandStr generates a random string with the given length. The string can be made of differente sets of characters: see CharSet type
func SplitAddrPort ¶
Types ¶
type Certificate ¶
type Certificate struct { FullChainCert string // CertPemPath is the path to the full chain public key PrivateKey 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 ¶
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 ¶
type HTTPServer ¶
type HTTPServer struct { // 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 Logger *logger.Logger Handler http.Handler Online bool OnlineTime time.Time // 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 ¶
func NewHTTPServer(address string, port int, certs ...Certificate) (*HTTPServer, error)
NewServer creates a new server
func (*HTTPServer) IsRunning ¶
func (srv *HTTPServer) IsRunning() bool
IsRunning tells whether the server is running or not
func (*HTTPServer) Port ¶
func (srv *HTTPServer) Port() int
Port returns the TCP port listened by the server
func (*HTTPServer) Router ¶
func (srv *HTTPServer) Router() *Router
func (*HTTPServer) ServeHTTP ¶
func (srv *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
func (*HTTPServer) Start ¶
func (srv *HTTPServer) Start() error
Start prepares every domain and subdomain and starts listening on the TCP port
func (*HTTPServer) Stop ¶
func (srv *HTTPServer) Stop() error
Stop cleans up every domain and subdomain and stops listening on the TCP port
type Router ¶
type Router struct { 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) *Router
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) CustomServer ¶
Server returns the TCP server running on the given port
func (*Router) HTTPServer ¶
func (router *Router) HTTPServer(port int) *HTTPServer
Server returns the HTTP server running on the given port
func (*Router) NewHTTPServer ¶
func (router *Router) NewHTTPServer(address string, port int, certs ...Certificate) (*HTTPServer, error)
NewServer creates a new HTTP/HTTPS Server linked to the Router. See NewServer function for more information
func (*Router) NewTCPServer ¶
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) RegisterCustomServer ¶
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
type ServerRouter ¶
type ServerRouter interface {
Router() *Router
}
type TCPServer ¶
type TCPServer struct { Online bool ConnHandler ConnHandlerFunc Router *Router Logger *logger.Logger // contains filtered or unexported fields }
func NewTCPServer ¶
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) Exec ¶
Exec 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) Init ¶
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) ListenForExit ¶
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 }
type TaskFunc ¶
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 ¶
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 ¶
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 ¶
func (tm *TaskManager) GetProcessesNames() []string
GetProcessesNames returns a slice containing all the names of the registered processes
func (*TaskManager) GetTask ¶
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 ¶
func (tm *TaskManager) KillProcess(name string) error
KillProcess forcibly kills the process with the given name
func (*TaskManager) NewProcess ¶
func (tm *TaskManager) NewProcess(name, dir string, execName string, args ...string) (*process.Process, 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 ¶
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 stops the task, if running, runs the cleanup function provided and removes the Task from the TaskManager
func (*TaskManager) RestartProcess ¶
func (tm *TaskManager) RestartProcess(name string) error
RestartProcess first gracefully stops the process (not implemented, see StopProcess method) and then starts it again
func (*TaskManager) StartProcess ¶
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 ¶
func (tm *TaskManager) StopProcess(name string) error
StopProcess tries to gracefully stop the process with the given name
func (*TaskManager) StopTask ¶
func (tm *TaskManager) StopTask(name string) error
StopTask stops the task and wait for the exit
func (*TaskManager) WaitProcess ¶
func (tm *TaskManager) WaitProcess(name string) (process.ExitStatus, error)
WaitProcess waits for the termination of the process and returns process information