feature: customizable colors (#1977)

*  feature: customizable colors

*  feature: customizable colors

*  feature: customizable colors
pull/1987/head
M. Efe Çetin 2022-08-01 09:24:37 +03:00 committed by GitHub
parent ff1e0109a3
commit 4103f9463d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 739 additions and 623 deletions

425
app.go
View File

@ -10,30 +10,20 @@ package fiber
import (
"bufio"
"bytes"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
"os"
"path/filepath"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
"text/tabwriter"
"time"
"encoding/json"
"github.com/gofiber/fiber/v2/internal/colorable"
"github.com/gofiber/fiber/v2/internal/isatty"
"github.com/gofiber/fiber/v2/utils"
"github.com/valyala/fasthttp"
)
@ -371,6 +361,11 @@ type Config struct {
// If set to true, will print all routes with their method, path and handler.
// Default: false
EnablePrintRoutes bool `json:"enable_print_routes"`
// You can define custom color scheme. They'll be used for startup message, route list and some middlewares.
//
// Optional. Default: DefaultColors
ColorScheme Colors `json:"color_scheme"`
}
// Static defines configuration options when defining static assets.
@ -523,6 +518,9 @@ func New(config ...Config) *App {
app.handleTrustedProxy(ipAddress)
}
// Override colors
app.config.ColorScheme = defaultColors(app.config.ColorScheme)
// Init appList
app.appList[""] = app
@ -758,154 +756,6 @@ func NewError(code int, message ...string) *Error {
return err
}
// Listener can be used to pass a custom listener.
func (app *App) Listener(ln net.Listener) error {
// Prefork is supported for custom listeners
if app.config.Prefork {
addr, tlsConfig := lnMetadata(app.config.Network, ln)
return app.prefork(app.config.Network, addr, tlsConfig)
}
// prepare the server for the start
app.startupProcess()
// Print startup message
if !app.config.DisableStartupMessage {
app.startupMessage(ln.Addr().String(), getTlsConfig(ln) != nil, "")
}
// Print routes
if app.config.EnablePrintRoutes {
app.printRoutesMessage()
}
// Start listening
return app.server.Serve(ln)
}
// Listen serves HTTP requests from the given addr.
//
// app.Listen(":8080")
// app.Listen("127.0.0.1:8080")
func (app *App) Listen(addr string) error {
// Start prefork
if app.config.Prefork {
return app.prefork(app.config.Network, addr, nil)
}
// Setup listener
ln, err := net.Listen(app.config.Network, addr)
if err != nil {
return err
}
// prepare the server for the start
app.startupProcess()
// Print startup message
if !app.config.DisableStartupMessage {
app.startupMessage(ln.Addr().String(), false, "")
}
// Print routes
if app.config.EnablePrintRoutes {
app.printRoutesMessage()
}
// Start listening
return app.server.Serve(ln)
}
// ListenTLS serves HTTPS requests from the given addr.
// certFile and keyFile are the paths to TLS certificate and key file:
// app.ListenTLS(":8080", "./cert.pem", "./cert.key")
func (app *App) ListenTLS(addr, certFile, keyFile string) error {
// Check for valid cert/key path
if len(certFile) == 0 || len(keyFile) == 0 {
return errors.New("tls: provide a valid cert or key path")
}
// Prefork is supported
if app.config.Prefork {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return fmt.Errorf("tls: cannot load TLS key pair from certFile=%q and keyFile=%q: %s", certFile, keyFile, err)
}
config := &tls.Config{
MinVersion: tls.VersionTLS12,
Certificates: []tls.Certificate{
cert,
},
}
return app.prefork(app.config.Network, addr, config)
}
// Setup listener
ln, err := net.Listen(app.config.Network, addr)
if err != nil {
return err
}
// prepare the server for the start
app.startupProcess()
// Print startup message
if !app.config.DisableStartupMessage {
app.startupMessage(ln.Addr().String(), true, "")
}
// Print routes
if app.config.EnablePrintRoutes {
app.printRoutesMessage()
}
// Start listening
return app.server.ServeTLS(ln, certFile, keyFile)
}
// ListenMutualTLS serves HTTPS requests from the given addr.
// certFile, keyFile and clientCertFile are the paths to TLS certificate and key file:
// app.ListenMutualTLS(":8080", "./cert.pem", "./cert.key", "./client.pem")
func (app *App) ListenMutualTLS(addr, certFile, keyFile, clientCertFile string) error {
// Check for valid cert/key path
if len(certFile) == 0 || len(keyFile) == 0 {
return errors.New("tls: provide a valid cert or key path")
}
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return fmt.Errorf("tls: cannot load TLS key pair from certFile=%q and keyFile=%q: %s", certFile, keyFile, err)
}
clientCACert, err := ioutil.ReadFile(filepath.Clean(clientCertFile))
if err != nil {
return err
}
clientCertPool := x509.NewCertPool()
clientCertPool.AppendCertsFromPEM(clientCACert)
config := &tls.Config{
MinVersion: tls.VersionTLS12,
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: clientCertPool,
Certificates: []tls.Certificate{
cert,
},
}
// Prefork is supported
if app.config.Prefork {
return app.prefork(app.config.Network, addr, config)
}
// Setup listener
ln, err := tls.Listen(app.config.Network, addr, config)
if err != nil {
return err
}
// prepare the server for the start
app.startupProcess()
// Print startup message
if !app.config.DisableStartupMessage {
app.startupMessage(ln.Addr().String(), true, "")
}
// Print routes
if app.config.EnablePrintRoutes {
app.printRoutesMessage()
}
// Start listening
return app.server.Serve(ln)
}
// Config returns the app config as value ( read-only ).
func (app *App) Config() Config {
return app.config
@ -1137,262 +987,3 @@ func (app *App) startupProcess() *App {
app.mutex.Unlock()
return app
}
// startupMessage prepares the startup message with the handler number, port, address and other information
func (app *App) startupMessage(addr string, tls bool, pids string) {
// ignore child processes
if IsChild() {
return
}
const (
cBlack = "\u001b[90m"
// cRed = "\u001b[91m"
cCyan = "\u001b[96m"
// cGreen = "\u001b[92m"
// cYellow = "\u001b[93m"
// cBlue = "\u001b[94m"
// cMagenta = "\u001b[95m"
// cWhite = "\u001b[97m"
cReset = "\u001b[0m"
)
value := func(s string, width int) string {
pad := width - len(s)
str := ""
for i := 0; i < pad; i++ {
str += "."
}
if s == "Disabled" {
str += " " + s
} else {
str += fmt.Sprintf(" %s%s%s", cCyan, s, cBlack)
}
return str
}
center := func(s string, width int) string {
pad := strconv.Itoa((width - len(s)) / 2)
str := fmt.Sprintf("%"+pad+"s", " ")
str += s
str += fmt.Sprintf("%"+pad+"s", " ")
if len(str) < width {
str += " "
}
return str
}
centerValue := func(s string, width int) string {
pad := strconv.Itoa((width - len(s)) / 2)
str := fmt.Sprintf("%"+pad+"s", " ")
str += fmt.Sprintf("%s%s%s", cCyan, s, cBlack)
str += fmt.Sprintf("%"+pad+"s", " ")
if len(str)-10 < width {
str += " "
}
return str
}
pad := func(s string, width int) (str string) {
toAdd := width - len(s)
str += s
for i := 0; i < toAdd; i++ {
str += " "
}
return
}
host, port := parseAddr(addr)
if host == "" {
if app.config.Network == NetworkTCP6 {
host = "[::1]"
} else {
host = "0.0.0.0"
}
}
scheme := "http"
if tls {
scheme = "https"
}
isPrefork := "Disabled"
if app.config.Prefork {
isPrefork = "Enabled"
}
procs := strconv.Itoa(runtime.GOMAXPROCS(0))
if !app.config.Prefork {
procs = "1"
}
mainLogo := cBlack + " ┌───────────────────────────────────────────────────┐\n"
if app.config.AppName != "" {
mainLogo += " │ " + centerValue(app.config.AppName, 49) + " │\n"
}
mainLogo += " │ " + centerValue(" Fiber v"+Version, 49) + " │\n"
if host == "0.0.0.0" {
mainLogo +=
" │ " + center(fmt.Sprintf("%s://127.0.0.1:%s", scheme, port), 49) + " │\n" +
" │ " + center(fmt.Sprintf("(bound on host 0.0.0.0 and port %s)", port), 49) + " │\n"
} else {
mainLogo +=
" │ " + center(fmt.Sprintf("%s://%s:%s", scheme, host, port), 49) + " │\n"
}
mainLogo += fmt.Sprintf(
" │ │\n"+
" │ Handlers %s Processes %s │\n"+
" │ Prefork .%s PID ....%s │\n"+
" └───────────────────────────────────────────────────┘"+
cReset,
value(strconv.Itoa(int(app.handlersCount)), 14), value(procs, 12),
value(isPrefork, 14), value(strconv.Itoa(os.Getpid()), 14),
)
var childPidsLogo string
if app.config.Prefork {
var childPidsTemplate string
childPidsTemplate += "%s"
childPidsTemplate += " ┌───────────────────────────────────────────────────┐\n%s"
childPidsTemplate += " └───────────────────────────────────────────────────┘"
childPidsTemplate += "%s"
newLine := " │ %s%s%s │"
// Turn the `pids` variable (in the form ",a,b,c,d,e,f,etc") into a slice of PIDs
var pidSlice []string
for _, v := range strings.Split(pids, ",") {
if v != "" {
pidSlice = append(pidSlice, v)
}
}
var lines []string
thisLine := "Child PIDs ... "
var itemsOnThisLine []string
addLine := func() {
lines = append(lines,
fmt.Sprintf(
newLine,
cBlack,
thisLine+cCyan+pad(strings.Join(itemsOnThisLine, ", "), 49-len(thisLine)),
cBlack,
),
)
}
for _, pid := range pidSlice {
if len(thisLine+strings.Join(append(itemsOnThisLine, pid), ", ")) > 49 {
addLine()
thisLine = ""
itemsOnThisLine = []string{pid}
} else {
itemsOnThisLine = append(itemsOnThisLine, pid)
}
}
// Add left over items to their own line
if len(itemsOnThisLine) != 0 {
addLine()
}
// Form logo
childPidsLogo = fmt.Sprintf(childPidsTemplate,
cBlack,
strings.Join(lines, "\n")+"\n",
cReset,
)
}
// Combine both the child PID logo and the main Fiber logo
// Pad the shorter logo to the length of the longer one
splitMainLogo := strings.Split(mainLogo, "\n")
splitChildPidsLogo := strings.Split(childPidsLogo, "\n")
mainLen := len(splitMainLogo)
childLen := len(splitChildPidsLogo)
if mainLen > childLen {
diff := mainLen - childLen
for i := 0; i < diff; i++ {
splitChildPidsLogo = append(splitChildPidsLogo, "")
}
} else {
diff := childLen - mainLen
for i := 0; i < diff; i++ {
splitMainLogo = append(splitMainLogo, "")
}
}
// Combine the two logos, line by line
output := "\n"
for i := range splitMainLogo {
output += cBlack + splitMainLogo[i] + " " + splitChildPidsLogo[i] + "\n"
}
out := colorable.NewColorableStdout()
if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) {
out = colorable.NewNonColorable(os.Stdout)
}
_, _ = fmt.Fprintln(out, output)
}
// printRoutesMessage print all routes with method, path, name and handlers
// in a format of table, like this:
// method | path | name | handlers
// GET | / | routeName | github.com/gofiber/fiber/v2.emptyHandler
// HEAD | / | | github.com/gofiber/fiber/v2.emptyHandler
func (app *App) printRoutesMessage() {
// ignore child processes
if IsChild() {
return
}
const (
// cBlack = "\u001b[90m"
// cRed = "\u001b[91m"
cCyan = "\u001b[96m"
cGreen = "\u001b[92m"
cYellow = "\u001b[93m"
cBlue = "\u001b[94m"
// cMagenta = "\u001b[95m"
cWhite = "\u001b[97m"
// cReset = "\u001b[0m"
)
var routes []RouteMessage
for _, routeStack := range app.stack {
for _, route := range routeStack {
var newRoute = RouteMessage{}
newRoute.name = route.Name
newRoute.method = route.Method
newRoute.path = route.Path
for _, handler := range route.Handlers {
newRoute.handlers += runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name() + " "
}
routes = append(routes, newRoute)
}
}
out := colorable.NewColorableStdout()
if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) {
out = colorable.NewNonColorable(os.Stdout)
}
w := tabwriter.NewWriter(out, 1, 1, 1, ' ', 0)
// Sort routes by path
sort.Slice(routes, func(i, j int) bool {
return routes[i].path < routes[j].path
})
_, _ = fmt.Fprintf(w, "%smethod\t%s| %spath\t%s| %sname\t%s| %shandlers\n", cBlue, cWhite, cGreen, cWhite, cCyan, cWhite, cYellow)
_, _ = fmt.Fprintf(w, "%s------\t%s| %s----\t%s| %s----\t%s| %s--------\n", cBlue, cWhite, cGreen, cWhite, cCyan, cWhite, cYellow)
for _, route := range routes {
_, _ = fmt.Fprintf(w, "%s%s\t%s| %s%s\t%s| %s%s\t%s| %s%s\n", cBlue, route.method, cWhite, cGreen, route.path, cWhite, cCyan, route.name, cWhite, cYellow, route.handlers)
}
_ = w.Flush()
}

View File

@ -6,7 +6,6 @@ package fiber
import (
"bytes"
"crypto/tls"
"errors"
"fmt"
"io"
@ -26,7 +25,6 @@ import (
"github.com/gofiber/fiber/v2/utils"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttputil"
)
var testEmptyHandler = func(c *Ctx) error {
@ -1122,131 +1120,6 @@ func Test_App_Next_Method(t *testing.T) {
utils.AssertEqual(t, 404, resp.StatusCode, "Status code")
}
// go test -run Test_App_Listen
func Test_App_Listen(t *testing.T) {
app := New(Config{DisableStartupMessage: true})
utils.AssertEqual(t, false, app.Listen(":99999") == nil)
go func() {
time.Sleep(1000 * time.Millisecond)
utils.AssertEqual(t, nil, app.Shutdown())
}()
utils.AssertEqual(t, nil, app.Listen(":4003"))
}
// go test -run Test_App_Listen_Prefork
func Test_App_Listen_Prefork(t *testing.T) {
testPreforkMaster = true
app := New(Config{DisableStartupMessage: true, Prefork: true})
utils.AssertEqual(t, nil, app.Listen(":99999"))
}
// go test -run Test_App_ListenTLS
func Test_App_ListenTLS(t *testing.T) {
app := New()
// invalid port
utils.AssertEqual(t, false, app.ListenTLS(":99999", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key") == nil)
// missing perm/cert file
utils.AssertEqual(t, false, app.ListenTLS(":0", "", "./.github/testdata/ssl.key") == nil)
go func() {
time.Sleep(1000 * time.Millisecond)
utils.AssertEqual(t, nil, app.Shutdown())
}()
utils.AssertEqual(t, nil, app.ListenTLS(":0", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key"))
}
// go test -run Test_App_ListenTLS_Prefork
func Test_App_ListenTLS_Prefork(t *testing.T) {
testPreforkMaster = true
app := New(Config{DisableStartupMessage: true, Prefork: true})
// invalid key file content
utils.AssertEqual(t, false, app.ListenTLS(":0", "./.github/testdata/ssl.pem", "./.github/testdata/template.tmpl") == nil)
utils.AssertEqual(t, nil, app.ListenTLS(":99999", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key"))
}
// go test -run Test_App_ListenMutualTLS
func Test_App_ListenMutualTLS(t *testing.T) {
app := New()
// invalid port
utils.AssertEqual(t, false, app.ListenMutualTLS(":99999", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key", "./.github/testdata/ca-chain.cert.pem") == nil)
// missing perm/cert file
utils.AssertEqual(t, false, app.ListenMutualTLS(":0", "", "./.github/testdata/ssl.key", "") == nil)
go func() {
time.Sleep(1000 * time.Millisecond)
utils.AssertEqual(t, nil, app.Shutdown())
}()
utils.AssertEqual(t, nil, app.ListenMutualTLS(":0", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key", "./.github/testdata/ca-chain.cert.pem"))
}
// go test -run Test_App_ListenMutualTLS_Prefork
func Test_App_ListenMutualTLS_Prefork(t *testing.T) {
testPreforkMaster = true
app := New(Config{DisableStartupMessage: true, Prefork: true})
// invalid key file content
utils.AssertEqual(t, false, app.ListenMutualTLS(":0", "./.github/testdata/ssl.pem", "./.github/testdata/template.html", "") == nil)
utils.AssertEqual(t, nil, app.ListenMutualTLS(":99999", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key", "./.github/testdata/ca-chain.cert.pem"))
}
// go test -run Test_App_Listener
func Test_App_Listener(t *testing.T) {
app := New()
go func() {
time.Sleep(500 * time.Millisecond)
utils.AssertEqual(t, nil, app.Shutdown())
}()
ln := fasthttputil.NewInmemoryListener()
utils.AssertEqual(t, nil, app.Listener(ln))
}
// go test -run Test_App_Listener_Prefork
func Test_App_Listener_Prefork(t *testing.T) {
testPreforkMaster = true
app := New(Config{DisableStartupMessage: true, Prefork: true})
ln := fasthttputil.NewInmemoryListener()
utils.AssertEqual(t, nil, app.Listener(ln))
}
func Test_App_Listener_TLS_Listener(t *testing.T) {
// Create tls certificate
cer, err := tls.LoadX509KeyPair("./.github/testdata/ssl.pem", "./.github/testdata/ssl.key")
if err != nil {
utils.AssertEqual(t, nil, err)
}
config := &tls.Config{Certificates: []tls.Certificate{cer}}
ln, err := tls.Listen(NetworkTCP4, ":0", config)
utils.AssertEqual(t, nil, err)
app := New()
go func() {
time.Sleep(time.Millisecond * 500)
utils.AssertEqual(t, nil, app.Shutdown())
}()
utils.AssertEqual(t, nil, app.Listener(ln))
}
// go test -v -run=^$ -bench=Benchmark_AcquireCtx -benchmem -count=4
func Benchmark_AcquireCtx(b *testing.B) {
app := New()
@ -1710,40 +1583,3 @@ func Test_App_UseMountedErrorHandlerForBestPrefixMatch(t *testing.T) {
utils.AssertEqual(t, nil, err, "iotuil.ReadAll()")
utils.AssertEqual(t, "hi, i'm a custom sub sub fiber error", string(b), "Third fiber Response body")
}
func emptyHandler(c *Ctx) error {
return nil
}
func Test_App_print_Route(t *testing.T) {
app := New(Config{EnablePrintRoutes: true})
app.Get("/", emptyHandler).Name("routeName")
printRoutesMessage := captureOutput(func() {
app.printRoutesMessage()
})
fmt.Println(printRoutesMessage)
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "GET"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "/"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "emptyHandler"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "routeName"))
}
func Test_App_print_Route_with_group(t *testing.T) {
app := New(Config{EnablePrintRoutes: true})
app.Get("/", emptyHandler)
v1 := app.Group("v1")
v1.Get("/test", emptyHandler).Name("v1")
v1.Post("/test/fiber", emptyHandler)
v1.Put("/test/fiber/*", emptyHandler)
printRoutesMessage := captureOutput(func() {
app.printRoutesMessage()
})
fmt.Println(printRoutesMessage)
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "GET"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "/"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "emptyHandler"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "/v1/test"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "POST"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "/v1/test/fiber"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "PUT"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "/v1/test/fiber/*"))
}

99
color.go Normal file
View File

@ -0,0 +1,99 @@
// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
// 🤖 Github Repository: https://github.com/gofiber/fiber
// 📌 API Documentation: https://docs.gofiber.io
package fiber
// Colors is a struct to define custom colors for Fiber app and middlewares.
type Colors struct {
// Black color.
//
// Optional. Default: "\u001b[90m"
Black string
// Red color.
//
// Optional. Default: "\u001b[91m"
Red string
// Green color.
//
// Optional. Default: "\u001b[92m"
Green string
// Yellow color.
//
// Optional. Default: "\u001b[93m"
Yellow string
// Blue color.
//
// Optional. Default: "\u001b[94m"
Blue string
// Magenta color.
//
// Optional. Default: "\u001b[95m"
Magenta string
// Cyan color.
//
// Optional. Default: "\u001b[96m"
Cyan string
// White color.
//
// Optional. Default: "\u001b[97m"
White string
// Reset color.
//
// Optional. Default: "\u001b[0m"
Reset string
}
// Default color codes
var DefaultColors = Colors{
Black: "\u001b[90m",
Red: "\u001b[91m",
Green: "\u001b[92m",
Yellow: "\u001b[93m",
Blue: "\u001b[94m",
Magenta: "\u001b[95m",
Cyan: "\u001b[96m",
White: "\u001b[97m",
Reset: "\u001b[0m",
}
// defaultColors is a function to override default colors to config
func defaultColors(colors Colors) Colors {
if colors.Red == "" {
colors.Red = DefaultColors.Red
}
if colors.Green == "" {
colors.Green = DefaultColors.Green
}
if colors.Yellow == "" {
colors.Yellow = DefaultColors.Yellow
}
if colors.Blue == "" {
colors.Blue = DefaultColors.Blue
}
if colors.Magenta == "" {
colors.Magenta = DefaultColors.Magenta
}
if colors.Cyan == "" {
colors.Cyan = DefaultColors.Cyan
}
if colors.Reset == "" {
colors.Reset = DefaultColors.Reset
}
return colors
}

416
listen.go Normal file
View File

@ -0,0 +1,416 @@
// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
// 🤖 Github Repository: https://github.com/gofiber/fiber
// 📌 API Documentation: https://docs.gofiber.io
package fiber
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
"text/tabwriter"
"github.com/gofiber/fiber/v2/internal/colorable"
"github.com/gofiber/fiber/v2/internal/isatty"
)
// Listener can be used to pass a custom listener.
func (app *App) Listener(ln net.Listener) error {
// Prefork is supported for custom listeners
if app.config.Prefork {
addr, tlsConfig := lnMetadata(app.config.Network, ln)
return app.prefork(app.config.Network, addr, tlsConfig)
}
// prepare the server for the start
app.startupProcess()
// Print startup message
if !app.config.DisableStartupMessage {
app.startupMessage(ln.Addr().String(), getTlsConfig(ln) != nil, "")
}
// Print routes
if app.config.EnablePrintRoutes {
app.printRoutesMessage()
}
// Start listening
return app.server.Serve(ln)
}
// Listen serves HTTP requests from the given addr.
//
// app.Listen(":8080")
// app.Listen("127.0.0.1:8080")
func (app *App) Listen(addr string) error {
// Start prefork
if app.config.Prefork {
return app.prefork(app.config.Network, addr, nil)
}
// Setup listener
ln, err := net.Listen(app.config.Network, addr)
if err != nil {
return err
}
// prepare the server for the start
app.startupProcess()
// Print startup message
if !app.config.DisableStartupMessage {
app.startupMessage(ln.Addr().String(), false, "")
}
// Print routes
if app.config.EnablePrintRoutes {
app.printRoutesMessage()
}
// Start listening
return app.server.Serve(ln)
}
// ListenTLS serves HTTPS requests from the given addr.
// certFile and keyFile are the paths to TLS certificate and key file:
// app.ListenTLS(":8080", "./cert.pem", "./cert.key")
func (app *App) ListenTLS(addr, certFile, keyFile string) error {
// Check for valid cert/key path
if len(certFile) == 0 || len(keyFile) == 0 {
return errors.New("tls: provide a valid cert or key path")
}
// Prefork is supported
if app.config.Prefork {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return fmt.Errorf("tls: cannot load TLS key pair from certFile=%q and keyFile=%q: %s", certFile, keyFile, err)
}
config := &tls.Config{
MinVersion: tls.VersionTLS12,
Certificates: []tls.Certificate{
cert,
},
}
return app.prefork(app.config.Network, addr, config)
}
// Setup listener
ln, err := net.Listen(app.config.Network, addr)
if err != nil {
return err
}
// prepare the server for the start
app.startupProcess()
// Print startup message
if !app.config.DisableStartupMessage {
app.startupMessage(ln.Addr().String(), true, "")
}
// Print routes
if app.config.EnablePrintRoutes {
app.printRoutesMessage()
}
// Start listening
return app.server.ServeTLS(ln, certFile, keyFile)
}
// ListenMutualTLS serves HTTPS requests from the given addr.
// certFile, keyFile and clientCertFile are the paths to TLS certificate and key file:
// app.ListenMutualTLS(":8080", "./cert.pem", "./cert.key", "./client.pem")
func (app *App) ListenMutualTLS(addr, certFile, keyFile, clientCertFile string) error {
// Check for valid cert/key path
if len(certFile) == 0 || len(keyFile) == 0 {
return errors.New("tls: provide a valid cert or key path")
}
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return fmt.Errorf("tls: cannot load TLS key pair from certFile=%q and keyFile=%q: %s", certFile, keyFile, err)
}
clientCACert, err := ioutil.ReadFile(filepath.Clean(clientCertFile))
if err != nil {
return err
}
clientCertPool := x509.NewCertPool()
clientCertPool.AppendCertsFromPEM(clientCACert)
config := &tls.Config{
MinVersion: tls.VersionTLS12,
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: clientCertPool,
Certificates: []tls.Certificate{
cert,
},
}
// Prefork is supported
if app.config.Prefork {
return app.prefork(app.config.Network, addr, config)
}
// Setup listener
ln, err := tls.Listen(app.config.Network, addr, config)
if err != nil {
return err
}
// prepare the server for the start
app.startupProcess()
// Print startup message
if !app.config.DisableStartupMessage {
app.startupMessage(ln.Addr().String(), true, "")
}
// Print routes
if app.config.EnablePrintRoutes {
app.printRoutesMessage()
}
// Start listening
return app.server.Serve(ln)
}
// startupMessage prepares the startup message with the handler number, port, address and other information
func (app *App) startupMessage(addr string, tls bool, pids string) {
// ignore child processes
if IsChild() {
return
}
// Alias colors
colors := app.config.ColorScheme
value := func(s string, width int) string {
pad := width - len(s)
str := ""
for i := 0; i < pad; i++ {
str += "."
}
if s == "Disabled" {
str += " " + s
} else {
str += fmt.Sprintf(" %s%s%s", colors.Cyan, s, colors.Black)
}
return str
}
center := func(s string, width int) string {
pad := strconv.Itoa((width - len(s)) / 2)
str := fmt.Sprintf("%"+pad+"s", " ")
str += s
str += fmt.Sprintf("%"+pad+"s", " ")
if len(str) < width {
str += " "
}
return str
}
centerValue := func(s string, width int) string {
pad := strconv.Itoa((width - len(s)) / 2)
str := fmt.Sprintf("%"+pad+"s", " ")
str += fmt.Sprintf("%s%s%s", colors.Cyan, s, colors.Black)
str += fmt.Sprintf("%"+pad+"s", " ")
if len(str)-10 < width {
str += " "
}
return str
}
pad := func(s string, width int) (str string) {
toAdd := width - len(s)
str += s
for i := 0; i < toAdd; i++ {
str += " "
}
return
}
host, port := parseAddr(addr)
if host == "" {
if app.config.Network == NetworkTCP6 {
host = "[::1]"
} else {
host = "0.0.0.0"
}
}
scheme := "http"
if tls {
scheme = "https"
}
isPrefork := "Disabled"
if app.config.Prefork {
isPrefork = "Enabled"
}
procs := strconv.Itoa(runtime.GOMAXPROCS(0))
if !app.config.Prefork {
procs = "1"
}
mainLogo := colors.Black + " ┌───────────────────────────────────────────────────┐\n"
if app.config.AppName != "" {
mainLogo += " │ " + centerValue(app.config.AppName, 49) + " │\n"
}
mainLogo += " │ " + centerValue(" Fiber v"+Version, 49) + " │\n"
if host == "0.0.0.0" {
mainLogo +=
" │ " + center(fmt.Sprintf("%s://127.0.0.1:%s", scheme, port), 49) + " │\n" +
" │ " + center(fmt.Sprintf("(bound on host 0.0.0.0 and port %s)", port), 49) + " │\n"
} else {
mainLogo +=
" │ " + center(fmt.Sprintf("%s://%s:%s", scheme, host, port), 49) + " │\n"
}
mainLogo += fmt.Sprintf(
" │ │\n"+
" │ Handlers %s Processes %s │\n"+
" │ Prefork .%s PID ....%s │\n"+
" └───────────────────────────────────────────────────┘"+
colors.Reset,
value(strconv.Itoa(int(app.handlersCount)), 14), value(procs, 12),
value(isPrefork, 14), value(strconv.Itoa(os.Getpid()), 14),
)
var childPidsLogo string
if app.config.Prefork {
var childPidsTemplate string
childPidsTemplate += "%s"
childPidsTemplate += " ┌───────────────────────────────────────────────────┐\n%s"
childPidsTemplate += " └───────────────────────────────────────────────────┘"
childPidsTemplate += "%s"
newLine := " │ %s%s%s │"
// Turn the `pids` variable (in the form ",a,b,c,d,e,f,etc") into a slice of PIDs
var pidSlice []string
for _, v := range strings.Split(pids, ",") {
if v != "" {
pidSlice = append(pidSlice, v)
}
}
var lines []string
thisLine := "Child PIDs ... "
var itemsOnThisLine []string
addLine := func() {
lines = append(lines,
fmt.Sprintf(
newLine,
colors.Black,
thisLine+colors.Cyan+pad(strings.Join(itemsOnThisLine, ", "), 49-len(thisLine)),
colors.Black,
),
)
}
for _, pid := range pidSlice {
if len(thisLine+strings.Join(append(itemsOnThisLine, pid), ", ")) > 49 {
addLine()
thisLine = ""
itemsOnThisLine = []string{pid}
} else {
itemsOnThisLine = append(itemsOnThisLine, pid)
}
}
// Add left over items to their own line
if len(itemsOnThisLine) != 0 {
addLine()
}
// Form logo
childPidsLogo = fmt.Sprintf(childPidsTemplate,
colors.Black,
strings.Join(lines, "\n")+"\n",
colors.Reset,
)
}
// Combine both the child PID logo and the main Fiber logo
// Pad the shorter logo to the length of the longer one
splitMainLogo := strings.Split(mainLogo, "\n")
splitChildPidsLogo := strings.Split(childPidsLogo, "\n")
mainLen := len(splitMainLogo)
childLen := len(splitChildPidsLogo)
if mainLen > childLen {
diff := mainLen - childLen
for i := 0; i < diff; i++ {
splitChildPidsLogo = append(splitChildPidsLogo, "")
}
} else {
diff := childLen - mainLen
for i := 0; i < diff; i++ {
splitMainLogo = append(splitMainLogo, "")
}
}
// Combine the two logos, line by line
output := "\n"
for i := range splitMainLogo {
output += colors.Black + splitMainLogo[i] + " " + splitChildPidsLogo[i] + "\n"
}
out := colorable.NewColorableStdout()
if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) {
out = colorable.NewNonColorable(os.Stdout)
}
_, _ = fmt.Fprintln(out, output)
}
// printRoutesMessage print all routes with method, path, name and handlers
// in a format of table, like this:
// method | path | name | handlers
// GET | / | routeName | github.com/gofiber/fiber/v2.emptyHandler
// HEAD | / | | github.com/gofiber/fiber/v2.emptyHandler
func (app *App) printRoutesMessage() {
// ignore child processes
if IsChild() {
return
}
// Alias colors
colors := app.config.ColorScheme
var routes []RouteMessage
for _, routeStack := range app.stack {
for _, route := range routeStack {
var newRoute = RouteMessage{}
newRoute.name = route.Name
newRoute.method = route.Method
newRoute.path = route.Path
for _, handler := range route.Handlers {
newRoute.handlers += runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name() + " "
}
routes = append(routes, newRoute)
}
}
out := colorable.NewColorableStdout()
if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) {
out = colorable.NewNonColorable(os.Stdout)
}
w := tabwriter.NewWriter(out, 1, 1, 1, ' ', 0)
// Sort routes by path
sort.Slice(routes, func(i, j int) bool {
return routes[i].path < routes[j].path
})
_, _ = fmt.Fprintf(w, "%smethod\t%s| %spath\t%s| %sname\t%s| %shandlers\n", colors.Blue, colors.White, colors.Green, colors.White, colors.Cyan, colors.White, colors.Yellow)
_, _ = fmt.Fprintf(w, "%s------\t%s| %s----\t%s| %s----\t%s| %s--------\n", colors.Blue, colors.White, colors.Green, colors.White, colors.Cyan, colors.White, colors.Yellow)
for _, route := range routes {
_, _ = fmt.Fprintf(w, "%s%s\t%s| %s%s\t%s| %s%s\t%s| %s%s\n", colors.Blue, route.method, colors.White, colors.Green, route.path, colors.White, colors.Cyan, route.name, colors.White, colors.Yellow, route.handlers)
}
_ = w.Flush()
}

181
listen_test.go Normal file
View File

@ -0,0 +1,181 @@
// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
// 🤖 Github Repository: https://github.com/gofiber/fiber
// 📌 API Documentation: https://docs.gofiber.io
package fiber
import (
"crypto/tls"
"fmt"
"strings"
"testing"
"time"
"github.com/gofiber/fiber/v2/utils"
"github.com/valyala/fasthttp/fasthttputil"
)
// go test -run Test_App_Listen
func Test_App_Listen(t *testing.T) {
app := New(Config{DisableStartupMessage: true})
utils.AssertEqual(t, false, app.Listen(":99999") == nil)
go func() {
time.Sleep(1000 * time.Millisecond)
utils.AssertEqual(t, nil, app.Shutdown())
}()
utils.AssertEqual(t, nil, app.Listen(":4003"))
}
// go test -run Test_App_Listen_Prefork
func Test_App_Listen_Prefork(t *testing.T) {
testPreforkMaster = true
app := New(Config{DisableStartupMessage: true, Prefork: true})
utils.AssertEqual(t, nil, app.Listen(":99999"))
}
// go test -run Test_App_ListenTLS
func Test_App_ListenTLS(t *testing.T) {
app := New()
// invalid port
utils.AssertEqual(t, false, app.ListenTLS(":99999", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key") == nil)
// missing perm/cert file
utils.AssertEqual(t, false, app.ListenTLS(":0", "", "./.github/testdata/ssl.key") == nil)
go func() {
time.Sleep(1000 * time.Millisecond)
utils.AssertEqual(t, nil, app.Shutdown())
}()
utils.AssertEqual(t, nil, app.ListenTLS(":0", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key"))
}
// go test -run Test_App_ListenTLS_Prefork
func Test_App_ListenTLS_Prefork(t *testing.T) {
testPreforkMaster = true
app := New(Config{DisableStartupMessage: true, Prefork: true})
// invalid key file content
utils.AssertEqual(t, false, app.ListenTLS(":0", "./.github/testdata/ssl.pem", "./.github/testdata/template.tmpl") == nil)
utils.AssertEqual(t, nil, app.ListenTLS(":99999", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key"))
}
// go test -run Test_App_ListenMutualTLS
func Test_App_ListenMutualTLS(t *testing.T) {
app := New()
// invalid port
utils.AssertEqual(t, false, app.ListenMutualTLS(":99999", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key", "./.github/testdata/ca-chain.cert.pem") == nil)
// missing perm/cert file
utils.AssertEqual(t, false, app.ListenMutualTLS(":0", "", "./.github/testdata/ssl.key", "") == nil)
go func() {
time.Sleep(1000 * time.Millisecond)
utils.AssertEqual(t, nil, app.Shutdown())
}()
utils.AssertEqual(t, nil, app.ListenMutualTLS(":0", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key", "./.github/testdata/ca-chain.cert.pem"))
}
// go test -run Test_App_ListenMutualTLS_Prefork
func Test_App_ListenMutualTLS_Prefork(t *testing.T) {
testPreforkMaster = true
app := New(Config{DisableStartupMessage: true, Prefork: true})
// invalid key file content
utils.AssertEqual(t, false, app.ListenMutualTLS(":0", "./.github/testdata/ssl.pem", "./.github/testdata/template.html", "") == nil)
utils.AssertEqual(t, nil, app.ListenMutualTLS(":99999", "./.github/testdata/ssl.pem", "./.github/testdata/ssl.key", "./.github/testdata/ca-chain.cert.pem"))
}
// go test -run Test_App_Listener
func Test_App_Listener(t *testing.T) {
app := New()
go func() {
time.Sleep(500 * time.Millisecond)
utils.AssertEqual(t, nil, app.Shutdown())
}()
ln := fasthttputil.NewInmemoryListener()
utils.AssertEqual(t, nil, app.Listener(ln))
}
// go test -run Test_App_Listener_Prefork
func Test_App_Listener_Prefork(t *testing.T) {
testPreforkMaster = true
app := New(Config{DisableStartupMessage: true, Prefork: true})
ln := fasthttputil.NewInmemoryListener()
utils.AssertEqual(t, nil, app.Listener(ln))
}
func Test_App_Listener_TLS_Listener(t *testing.T) {
// Create tls certificate
cer, err := tls.LoadX509KeyPair("./.github/testdata/ssl.pem", "./.github/testdata/ssl.key")
if err != nil {
utils.AssertEqual(t, nil, err)
}
config := &tls.Config{Certificates: []tls.Certificate{cer}}
ln, err := tls.Listen(NetworkTCP4, ":0", config)
utils.AssertEqual(t, nil, err)
app := New()
go func() {
time.Sleep(time.Millisecond * 500)
utils.AssertEqual(t, nil, app.Shutdown())
}()
utils.AssertEqual(t, nil, app.Listener(ln))
}
func Test_App_print_Route(t *testing.T) {
app := New(Config{EnablePrintRoutes: true})
app.Get("/", emptyHandler).Name("routeName")
printRoutesMessage := captureOutput(func() {
app.printRoutesMessage()
})
fmt.Println(printRoutesMessage)
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "GET"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "/"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "emptyHandler"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "routeName"))
}
func Test_App_print_Route_with_group(t *testing.T) {
app := New(Config{EnablePrintRoutes: true})
app.Get("/", emptyHandler)
v1 := app.Group("v1")
v1.Get("/test", emptyHandler).Name("v1")
v1.Post("/test/fiber", emptyHandler)
v1.Put("/test/fiber/*", emptyHandler)
printRoutesMessage := captureOutput(func() {
app.printRoutesMessage()
})
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "GET"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "/"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "emptyHandler"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "/v1/test"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "POST"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "/v1/test/fiber"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "PUT"))
utils.AssertEqual(t, true, strings.Contains(printRoutesMessage, "/v1/test/fiber/*"))
}
func emptyHandler(c *Ctx) error {
return nil
}

View File

@ -61,19 +61,6 @@ const (
TagReset = "reset"
)
// Color values
const (
cBlack = "\u001b[90m"
cRed = "\u001b[91m"
cGreen = "\u001b[92m"
cYellow = "\u001b[93m"
cBlue = "\u001b[94m"
cMagenta = "\u001b[95m"
cCyan = "\u001b[96m"
cWhite = "\u001b[97m"
cReset = "\u001b[0m"
)
// New creates a new middleware handler
func New(config ...Config) fiber.Handler {
// Set default config
@ -133,6 +120,9 @@ func New(config ...Config) fiber.Handler {
return c.Next()
}
// Alias colors
colors := c.App().Config().ColorScheme
// Set error handler once
once.Do(func() {
// get longested possible path
@ -179,16 +169,16 @@ func New(config ...Config) fiber.Handler {
// Format error if exist
formatErr := ""
if chainErr != nil {
formatErr = cRed + " | " + chainErr.Error() + cReset
formatErr = colors.Red + " | " + chainErr.Error() + colors.Reset
}
// Format log to buffer
_, _ = buf.WriteString(fmt.Sprintf("%s |%s %3d %s| %7v | %15s |%s %-7s %s| %-"+errPaddingStr+"s %s\n",
timestamp.Load().(string),
statusColor(c.Response().StatusCode()), c.Response().StatusCode(), cReset,
statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset,
stop.Sub(start).Round(time.Millisecond),
c.IP(),
methodColor(c.Method()), c.Method(), cReset,
methodColor(c.Method(), colors), c.Method(), colors.Reset,
c.Path(),
formatErr,
))
@ -240,7 +230,7 @@ func New(config ...Config) fiber.Handler {
return buf.WriteString(c.Route().Path)
case TagStatus:
if cfg.enableColors {
return buf.WriteString(fmt.Sprintf("%s %3d %s", statusColor(c.Response().StatusCode()), c.Response().StatusCode(), cReset))
return buf.WriteString(fmt.Sprintf("%s %3d %s", statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset))
}
return appendInt(buf, c.Response().StatusCode())
case TagResBody:
@ -255,27 +245,27 @@ func New(config ...Config) fiber.Handler {
return buf.WriteString(c.Request().URI().QueryArgs().String())
case TagMethod:
if cfg.enableColors {
return buf.WriteString(fmt.Sprintf("%s %-7s %s", methodColor(c.Method()), c.Method(), cReset))
return buf.WriteString(fmt.Sprintf("%s %-7s %s", methodColor(c.Method(), colors), c.Method(), colors.Reset))
}
return buf.WriteString(c.Method())
case TagBlack:
return buf.WriteString(cBlack)
return buf.WriteString(colors.Black)
case TagRed:
return buf.WriteString(cRed)
return buf.WriteString(colors.Red)
case TagGreen:
return buf.WriteString(cGreen)
return buf.WriteString(colors.Green)
case TagYellow:
return buf.WriteString(cYellow)
return buf.WriteString(colors.Yellow)
case TagBlue:
return buf.WriteString(cBlue)
return buf.WriteString(colors.Blue)
case TagMagenta:
return buf.WriteString(cMagenta)
return buf.WriteString(colors.Magenta)
case TagCyan:
return buf.WriteString(cCyan)
return buf.WriteString(colors.Cyan)
case TagWhite:
return buf.WriteString(cWhite)
return buf.WriteString(colors.White)
case TagReset:
return buf.WriteString(cReset)
return buf.WriteString(colors.Reset)
case TagError:
if chainErr != nil {
return buf.WriteString(chainErr.Error())

View File

@ -144,11 +144,14 @@ func Test_Logger_All(t *testing.T) {
Output: buf,
}))
// Alias colors
colors := app.Config().ColorScheme
resp, err := app.Test(httptest.NewRequest("GET", "/?foo=bar", nil))
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode)
expected := fmt.Sprintf("%dHost=example.comhttp0.0.0.0example.com/?foo=bar/%s%s%s%s%s%s%s%s%sCannot GET /", os.Getpid(), cBlack, cRed, cGreen, cYellow, cBlue, cMagenta, cCyan, cWhite, cReset)
expected := fmt.Sprintf("%dHost=example.comhttp0.0.0.0example.com/?foo=bar/%s%s%s%s%s%s%s%s%sCannot GET /", os.Getpid(), colors.Black, colors.Red, colors.Green, colors.Yellow, colors.Blue, colors.Magenta, colors.Cyan, colors.White, colors.Reset)
utils.AssertEqual(t, expected, buf.String())
}

View File

@ -4,36 +4,36 @@ import (
"github.com/gofiber/fiber/v2"
)
func methodColor(method string) string {
func methodColor(method string, colors fiber.Colors) string {
switch method {
case fiber.MethodGet:
return cCyan
return colors.Cyan
case fiber.MethodPost:
return cGreen
return colors.Green
case fiber.MethodPut:
return cYellow
return colors.Yellow
case fiber.MethodDelete:
return cRed
return colors.Red
case fiber.MethodPatch:
return cWhite
return colors.White
case fiber.MethodHead:
return cMagenta
return colors.Magenta
case fiber.MethodOptions:
return cBlue
return colors.Blue
default:
return cReset
return colors.Reset
}
}
func statusColor(code int) string {
func statusColor(code int, colors fiber.Colors) string {
switch {
case code >= fiber.StatusOK && code < fiber.StatusMultipleChoices:
return cGreen
return colors.Green
case code >= fiber.StatusMultipleChoices && code < fiber.StatusBadRequest:
return cBlue
return colors.Blue
case code >= fiber.StatusBadRequest && code < fiber.StatusInternalServerError:
return cYellow
return colors.Yellow
default:
return cRed
return colors.Red
}
}