server

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2021 License: LGPL-3.0 Imports: 9 Imported by: 0

README

Teal.Finance/Server

logo Opinionated boilerplate HTTP server with CORS, OPA, Prometheus, rate-limiter… for API and static website.

Origin

This library was originally developed as part of the project Rainbow during hackathons, based on older Teal.Finance products, and then moved to its own repository.

Features

Teal.Finance/Server supports:

  • Metrics server exporting data to Prometheus or other monitoring services ;
  • File server intended for static web files ;
  • HTTP/REST server for API endpoints (compatible any Go-standard HTTP handlers) ;
  • Chained middlewares (fork of github.com/justinas/alice)
  • Auto-completed error response in JSON format ;
  • Middleware: authentication rules based on Datalog/Rego files using Open Policy Agent ;
  • Middleware: rate limiter to prevent flooding by incoming requests ;
  • Middleware: logging of incoming requests ;
  • Middleware: Cross-Origin Resource Sharing (CORS).

License

LGPL-3.0-or-later: GNU Lesser General Public License v3.0 or later (tl;drLegal, Choosealicense.com). See the LICENSE file.

Except the two example files under CC0-1.0 (Creative Commons Zero v1.0 Universal) and the file chain.go (fork) under the MIT License.

Easy usage

See easy-example_test.go.

The following source code uses the all-in-one function Server.RunServer() that does the same thing as the longer source code of the next chapter.

package main

import (
    "log"

    "github.com/teal-finance/server"
)

func main() {
    s := server.Server{
        Version:        "MyApp-1.2.3",
        Resp:           "https://my-dns.com/doc",
        AllowedOrigins: []string{"http://my-dns.com"},
        OPAFilenames:   []string{"rego.json"},
    }

    h := myHandler()

    // main port 8080, export port 9093, rate limiter 10 20, debug mode 
    log.Fatal(s.RunServer(h, 8080, 9093, 10, 20, true))
}

Fined-control usage

See the cmd/server/main.go in the repository Rainbow for a complete example.

See also the local file full-example_test.go.

The following source code could be replaced by the all-in-one function Server.RunServer() presented in the previous chapter. The following source code is intended to show that the Teal.Finance/Server can be customized to meet specific requirements.

package main

import (
    "log"
    "net"
    "net/http"
    "time"

    "github.com/teal-finance/server"
    "github.com/teal-finance/server/chain"
    "github.com/teal-finance/server/cors"
    "github.com/teal-finance/server/export"
    "github.com/teal-finance/server/limiter"
    "github.com/teal-finance/server/opa"
    "github.com/teal-finance/server/reserr"
)

func main() {
    middlewares, connState := setMiddlewares()

    h := myHandler()
    h = middlewares.Then(h)

    runServer(h, connState)
}

func setMiddlewares() (middlewares chain.Chain, connState func(net.Conn, http.ConnState)) {
    // Uniformize error responses with API doc
    respError := reserr.New("https://my-dns.com/doc")

    // Start a metrics server in background if export port > 0.
    // The metrics server is for use with Prometheus or another compatible monitoring tool.
    metrics := export.Metrics{}
    middlewares, connState = metrics.StartServer(9093, true)

    // Limit the input request rate per IP
    reqLimiter := limiter.New(10, 20, true, respError)
    middlewares = middlewares.Append()

    // Endpoint authentication rules (Open Policy Agent)
    policy, err := opa.New(respError, []string{"rego.json"})
    if err != nil {
        log.Fatal(err)
    }

    // CORS
    allowedOrigins := []string{"http://my-dns.com"}

    middlewares = middlewares.Append(
        server.LogRequests,
        reqLimiter.Limit,
        server.Header("MyServerName-1.2.3"),
        policy.Auth,
        cors.HandleCORS(allowedOrigins),
    )

    return middlewares, connState
}

// runServer runs in foreground the main server.
func runServer(h http.Handler, connState func(net.Conn, http.ConnState)) {
    server := http.Server{
        Addr:              ":8080",
        Handler:           h,
        TLSConfig:         nil,
        ReadTimeout:       1 * time.Second,
        ReadHeaderTimeout: 1 * time.Second,
        WriteTimeout:      1 * time.Second,
        IdleTimeout:       1 * time.Second,
        MaxHeaderBytes:    222,
        TLSNextProto:      nil,
        ConnState:         connState,
        ErrorLog:          log.Default(),
        BaseContext:       nil,
        ConnContext:       nil,
    }

    log.Print("Server listening on http://localhost", server.Addr)

    log.Fatal(server.ListenAndServe())
}

Documentation

Overview

Example
// CC0-1.0: Creative Commons Zero v1.0 Universal
// No Rights Reserved - (CC) ZERO - (0) PUBLIC DOMAIN
//
// To the extent possible under law, the Teal.Finance contributors
// have waived all copyright and related or neighboring rights
// to this file "full-example_test.go" to be copied without restrictions.
// Refer to https://creativecommons.org/publicdomain/zero/1.0

package main

import (
	"log"
	"net"
	"net/http"
	"time"

	"github.com/teal-finance/server"
	"github.com/teal-finance/server/chain"
	"github.com/teal-finance/server/cors"
	"github.com/teal-finance/server/limiter"
	"github.com/teal-finance/server/metrics"
	"github.com/teal-finance/server/opa"
	"github.com/teal-finance/server/reserr"
)

func main() {
	// Uniformize error responses with API doc
	resErr := reserr.New("https://my-dns.com/doc")

	middlewares, connState := setMiddlewares(resErr)

	// Handles both REST API and static web files
	h := handler(resErr)
	h = middlewares.Then(h)

	runServer(h, connState)
}

func setMiddlewares(resErr reserr.ResErr) (middlewares chain.Chain, connState func(net.Conn, http.ConnState)) {
	// Start a metrics server in background if export port > 0.
	// The metrics server is for use with Prometheus or another compatible monitoring tool.
	metrics := metrics.Metrics{}
	middlewares, connState = metrics.StartServer(9093, true)

	// Limit the input request rate per IP
	reqLimiter := limiter.New(10, 20, true, resErr)
	middlewares = middlewares.Append()

	// Endpoint authentication rules (Open Policy Agent)
	policy, err := opa.New(resErr, []string{"rego.json"})
	if err != nil {
		log.Fatal(err)
	}

	// CORS
	allowedOrigins := []string{"http://my-dns.com"}

	middlewares = middlewares.Append(
		server.LogRequests,
		reqLimiter.Limit,
		server.Header("MyServerName-1.2.3"),
		policy.Auth,
		cors.HandleCORS(allowedOrigins),
	)

	return middlewares, connState
}

// runServer runs in foreground the main server.
func runServer(h http.Handler, connState func(net.Conn, http.ConnState)) {
	server := http.Server{
		Addr:              ":8080",
		Handler:           h,
		TLSConfig:         nil,
		ReadTimeout:       1 * time.Second,
		ReadHeaderTimeout: 1 * time.Second,
		WriteTimeout:      1 * time.Second,
		IdleTimeout:       1 * time.Second,
		MaxHeaderBytes:    222,
		TLSNextProto:      nil,
		ConnState:         connState,
		ErrorLog:          log.Default(),
		BaseContext:       nil,
		ConnContext:       nil,
	}

	log.Print("Server listening on http://localhost", server.Addr)

	log.Fatal(server.ListenAndServe())
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Header(version string) func(next http.Handler) http.Handler

Header sets the Server HTTP header in the response.

func LogRequests

func LogRequests(next http.Handler) http.Handler

LogRequests logs the incoming HTTP requests.

Types

type Server

type Server struct {
	Version string
	ResErr  reserr.ResErr

	// CORS
	AllowedOrigins []string // used for CORS

	// OPA
	OPAFilenames []string
	// contains filtered or unexported fields
}
Example
// CC0-1.0: Creative Commons Zero v1.0 Universal
// No Rights Reserved - (CC) ZERO - (0) PUBLIC DOMAIN
//
// To the extent possible under law, the Teal.Finance contributors
// have waived all copyright and related or neighboring rights
// to this file "easy-example_test.go" to be copied without restrictions.
// Refer to https://creativecommons.org/publicdomain/zero/1.0

package main

import (
	"log"
	"net/http"

	"github.com/go-chi/chi"
	"github.com/teal-finance/server"
	"github.com/teal-finance/server/fileserver"
	"github.com/teal-finance/server/reserr"
)

func main() {
	s := server.Server{
		Version:        "MyApp-1.2.3",
		ResErr:         "https://my-dns.com/doc",
		AllowedOrigins: []string{"http://my-dns.com"},
		OPAFilenames:   []string{"rego.json"},
	}

	// Handles both REST API and static web files
	h := handler(s.ResErr)

	log.Fatal(s.RunServer(h, 8080, 9093, 10, 20, true))
}

// handler creates the mapping between the endpoints and the handler functions.
func handler(resErr reserr.ResErr) http.Handler {
	r := chi.NewRouter()

	// Static website files
	fs := fileserver.FileServer{Dir: "/var/www/my-site", ResErr: resErr}
	r.Get("/", fs.ServeFile("index.html", "text/html; charset=utf-8"))
	r.Get("/js/*", fs.ServeDir("text/javascript; charset=utf-8"))
	r.Get("/css/*", fs.ServeDir("text/css; charset=utf-8"))
	r.Get("/images/*", fs.ServeImages())

	// API
	r.Get("/api/v1/items", items)
	r.Get("/api/v1/ducks", resErr.NotImplemented)

	// Other endpoints
	r.NotFound(resErr.InvalidPath)

	return r
}

func items(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	_, _ = w.Write([]byte(`["item1","item2","item3"]`))
}

func (*Server) RunServer

func (s *Server) RunServer(h http.Handler, port, expPort, maxReqBurst, maxReqPerMinute int, devMode bool) error

RunServer runs the HTTP server in foreground. Optionally it also starts a metrics server in background (if export port > 0). The metrics server is for use with Prometheus or another compatible monitoring tool.

Directories

Path Synopsis
package opa manages the Open Policy Agent.
package opa manages the Open Policy Agent.

Jump to

Keyboard shortcuts

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