github.com/gofiber/fiber/v2@v2.47.0/listen.go (about)

     1  // ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
     2  // 🤖 Github Repository: https://github.com/gofiber/fiber
     3  // 📌 API Documentation: https://docs.gofiber.io
     4  
     5  package fiber
     6  
     7  import (
     8  	"crypto/tls"
     9  	"crypto/x509"
    10  	"errors"
    11  	"fmt"
    12  	"log"
    13  	"net"
    14  	"os"
    15  	"path/filepath"
    16  	"reflect"
    17  	"runtime"
    18  	"sort"
    19  	"strconv"
    20  	"strings"
    21  	"text/tabwriter"
    22  
    23  	"github.com/mattn/go-colorable"
    24  	"github.com/mattn/go-isatty"
    25  	"github.com/mattn/go-runewidth"
    26  )
    27  
    28  // Listener can be used to pass a custom listener.
    29  func (app *App) Listener(ln net.Listener) error {
    30  	// prepare the server for the start
    31  	app.startupProcess()
    32  
    33  	// run hooks
    34  	app.runOnListenHooks()
    35  
    36  	// Print startup message
    37  	if !app.config.DisableStartupMessage {
    38  		app.startupMessage(ln.Addr().String(), getTLSConfig(ln) != nil, "")
    39  	}
    40  
    41  	// Print routes
    42  	if app.config.EnablePrintRoutes {
    43  		app.printRoutesMessage()
    44  	}
    45  
    46  	// Prefork is not supported for custom listeners
    47  	if app.config.Prefork {
    48  		log.Printf("[Warning] Prefork isn't supported for custom listeners.\n")
    49  	}
    50  
    51  	// Start listening
    52  	return app.server.Serve(ln)
    53  }
    54  
    55  // Listen serves HTTP requests from the given addr.
    56  //
    57  //	app.Listen(":8080")
    58  //	app.Listen("127.0.0.1:8080")
    59  func (app *App) Listen(addr string) error {
    60  	// Start prefork
    61  	if app.config.Prefork {
    62  		return app.prefork(app.config.Network, addr, nil)
    63  	}
    64  
    65  	// Setup listener
    66  	ln, err := net.Listen(app.config.Network, addr)
    67  	if err != nil {
    68  		return fmt.Errorf("failed to listen: %w", err)
    69  	}
    70  
    71  	// prepare the server for the start
    72  	app.startupProcess()
    73  
    74  	// run hooks
    75  	app.runOnListenHooks()
    76  
    77  	// Print startup message
    78  	if !app.config.DisableStartupMessage {
    79  		app.startupMessage(ln.Addr().String(), false, "")
    80  	}
    81  
    82  	// Print routes
    83  	if app.config.EnablePrintRoutes {
    84  		app.printRoutesMessage()
    85  	}
    86  
    87  	// Start listening
    88  	return app.server.Serve(ln)
    89  }
    90  
    91  // ListenTLS serves HTTPS requests from the given addr.
    92  // certFile and keyFile are the paths to TLS certificate and key file:
    93  //
    94  //	app.ListenTLS(":8080", "./cert.pem", "./cert.key")
    95  func (app *App) ListenTLS(addr, certFile, keyFile string) error {
    96  	// Check for valid cert/key path
    97  	if len(certFile) == 0 || len(keyFile) == 0 {
    98  		return errors.New("tls: provide a valid cert or key path")
    99  	}
   100  
   101  	// Set TLS config with handler
   102  	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
   103  	if err != nil {
   104  		return fmt.Errorf("tls: cannot load TLS key pair from certFile=%q and keyFile=%q: %w", certFile, keyFile, err)
   105  	}
   106  
   107  	return app.ListenTLSWithCertificate(addr, cert)
   108  }
   109  
   110  // ListenTLS serves HTTPS requests from the given addr.
   111  // cert is a tls.Certificate
   112  //
   113  //	app.ListenTLSWithCertificate(":8080", cert)
   114  func (app *App) ListenTLSWithCertificate(addr string, cert tls.Certificate) error {
   115  	tlsHandler := &TLSHandler{}
   116  	config := &tls.Config{
   117  		MinVersion: tls.VersionTLS12,
   118  		Certificates: []tls.Certificate{
   119  			cert,
   120  		},
   121  		GetCertificate: tlsHandler.GetClientInfo,
   122  	}
   123  
   124  	// Prefork is supported
   125  	if app.config.Prefork {
   126  		return app.prefork(app.config.Network, addr, config)
   127  	}
   128  
   129  	// Setup listener
   130  	ln, err := net.Listen(app.config.Network, addr)
   131  	ln = tls.NewListener(ln, config)
   132  	if err != nil {
   133  		return fmt.Errorf("failed to listen: %w", err)
   134  	}
   135  
   136  	// prepare the server for the start
   137  	app.startupProcess()
   138  
   139  	// run hooks
   140  	app.runOnListenHooks()
   141  
   142  	// Print startup message
   143  	if !app.config.DisableStartupMessage {
   144  		app.startupMessage(ln.Addr().String(), true, "")
   145  	}
   146  
   147  	// Print routes
   148  	if app.config.EnablePrintRoutes {
   149  		app.printRoutesMessage()
   150  	}
   151  
   152  	// Attach the tlsHandler to the config
   153  	app.SetTLSHandler(tlsHandler)
   154  
   155  	// Start listening
   156  	return app.server.Serve(ln)
   157  }
   158  
   159  // ListenMutualTLS serves HTTPS requests from the given addr.
   160  // certFile, keyFile and clientCertFile are the paths to TLS certificate and key file:
   161  //
   162  //	app.ListenMutualTLS(":8080", "./cert.pem", "./cert.key", "./client.pem")
   163  func (app *App) ListenMutualTLS(addr, certFile, keyFile, clientCertFile string) error {
   164  	// Check for valid cert/key path
   165  	if len(certFile) == 0 || len(keyFile) == 0 {
   166  		return errors.New("tls: provide a valid cert or key path")
   167  	}
   168  
   169  	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
   170  	if err != nil {
   171  		return fmt.Errorf("tls: cannot load TLS key pair from certFile=%q and keyFile=%q: %w", certFile, keyFile, err)
   172  	}
   173  
   174  	clientCACert, err := os.ReadFile(filepath.Clean(clientCertFile))
   175  	if err != nil {
   176  		return fmt.Errorf("failed to read file: %w", err)
   177  	}
   178  	clientCertPool := x509.NewCertPool()
   179  	clientCertPool.AppendCertsFromPEM(clientCACert)
   180  
   181  	return app.ListenMutualTLSWithCertificate(addr, cert, clientCertPool)
   182  }
   183  
   184  // ListenMutualTLSWithCertificate serves HTTPS requests from the given addr.
   185  // cert is a tls.Certificate and clientCertPool is a *x509.CertPool:
   186  //
   187  //	app.ListenMutualTLS(":8080", cert, clientCertPool)
   188  func (app *App) ListenMutualTLSWithCertificate(addr string, cert tls.Certificate, clientCertPool *x509.CertPool) error {
   189  	tlsHandler := &TLSHandler{}
   190  	config := &tls.Config{
   191  		MinVersion: tls.VersionTLS12,
   192  		ClientAuth: tls.RequireAndVerifyClientCert,
   193  		ClientCAs:  clientCertPool,
   194  		Certificates: []tls.Certificate{
   195  			cert,
   196  		},
   197  		GetCertificate: tlsHandler.GetClientInfo,
   198  	}
   199  
   200  	// Prefork is supported
   201  	if app.config.Prefork {
   202  		return app.prefork(app.config.Network, addr, config)
   203  	}
   204  
   205  	// Setup listener
   206  	ln, err := tls.Listen(app.config.Network, addr, config)
   207  	if err != nil {
   208  		return fmt.Errorf("failed to listen: %w", err)
   209  	}
   210  
   211  	// prepare the server for the start
   212  	app.startupProcess()
   213  
   214  	// run hooks
   215  	app.runOnListenHooks()
   216  
   217  	// Print startup message
   218  	if !app.config.DisableStartupMessage {
   219  		app.startupMessage(ln.Addr().String(), true, "")
   220  	}
   221  
   222  	// Print routes
   223  	if app.config.EnablePrintRoutes {
   224  		app.printRoutesMessage()
   225  	}
   226  
   227  	// Attach the tlsHandler to the config
   228  	app.SetTLSHandler(tlsHandler)
   229  
   230  	// Start listening
   231  	return app.server.Serve(ln)
   232  }
   233  
   234  // startupMessage prepares the startup message with the handler number, port, address and other information
   235  func (app *App) startupMessage(addr string, tls bool, pids string) { //nolint: revive // Accepting a bool param is fine here
   236  	// ignore child processes
   237  	if IsChild() {
   238  		return
   239  	}
   240  
   241  	// Alias colors
   242  	colors := app.config.ColorScheme
   243  
   244  	value := func(s string, width int) string {
   245  		pad := width - len(s)
   246  		str := ""
   247  		for i := 0; i < pad; i++ {
   248  			str += "."
   249  		}
   250  		if s == "Disabled" {
   251  			str += " " + s
   252  		} else {
   253  			str += fmt.Sprintf(" %s%s%s", colors.Cyan, s, colors.Black)
   254  		}
   255  		return str
   256  	}
   257  
   258  	center := func(s string, width int) string {
   259  		const padDiv = 2
   260  		pad := strconv.Itoa((width - len(s)) / padDiv)
   261  		str := fmt.Sprintf("%"+pad+"s", " ")
   262  		str += s
   263  		str += fmt.Sprintf("%"+pad+"s", " ")
   264  		if len(str) < width {
   265  			str += " "
   266  		}
   267  		return str
   268  	}
   269  
   270  	centerValue := func(s string, width int) string {
   271  		const padDiv = 2
   272  		pad := strconv.Itoa((width - runewidth.StringWidth(s)) / padDiv)
   273  		str := fmt.Sprintf("%"+pad+"s", " ")
   274  		str += fmt.Sprintf("%s%s%s", colors.Cyan, s, colors.Black)
   275  		str += fmt.Sprintf("%"+pad+"s", " ")
   276  		if runewidth.StringWidth(s)-10 < width && runewidth.StringWidth(s)%2 == 0 {
   277  			// add an ending space if the length of str is even and str is not too long
   278  			str += " "
   279  		}
   280  		return str
   281  	}
   282  
   283  	pad := func(s string, width int) string {
   284  		toAdd := width - len(s)
   285  		str := s
   286  		for i := 0; i < toAdd; i++ {
   287  			str += " "
   288  		}
   289  		return str
   290  	}
   291  
   292  	host, port := parseAddr(addr)
   293  	if host == "" {
   294  		if app.config.Network == NetworkTCP6 {
   295  			host = "[::1]"
   296  		} else {
   297  			host = "0.0.0.0"
   298  		}
   299  	}
   300  
   301  	scheme := schemeHTTP
   302  	if tls {
   303  		scheme = schemeHTTPS
   304  	}
   305  
   306  	isPrefork := "Disabled"
   307  	if app.config.Prefork {
   308  		isPrefork = "Enabled"
   309  	}
   310  
   311  	procs := strconv.Itoa(runtime.GOMAXPROCS(0))
   312  	if !app.config.Prefork {
   313  		procs = "1"
   314  	}
   315  
   316  	const lineLen = 49
   317  	mainLogo := colors.Black + " ┌───────────────────────────────────────────────────┐\n"
   318  	if app.config.AppName != "" {
   319  		mainLogo += " │ " + centerValue(app.config.AppName, lineLen) + " │\n"
   320  	}
   321  	mainLogo += " │ " + centerValue("Fiber v"+Version, lineLen) + " │\n"
   322  
   323  	if host == "0.0.0.0" {
   324  		mainLogo += " │ " + center(fmt.Sprintf("%s://127.0.0.1:%s", scheme, port), lineLen) + " │\n" +
   325  			" │ " + center(fmt.Sprintf("(bound on host 0.0.0.0 and port %s)", port), lineLen) + " │\n"
   326  	} else {
   327  		mainLogo += " │ " + center(fmt.Sprintf("%s://%s:%s", scheme, host, port), lineLen) + " │\n"
   328  	}
   329  
   330  	mainLogo += fmt.Sprintf(
   331  		" │                                                   │\n"+
   332  			" │ Handlers %s  Processes %s │\n"+
   333  			" │ Prefork .%s  PID ....%s │\n"+
   334  			" └───────────────────────────────────────────────────┘"+
   335  			colors.Reset,
   336  		value(strconv.Itoa(int(app.handlersCount)), 14), value(procs, 12),
   337  		value(isPrefork, 14), value(strconv.Itoa(os.Getpid()), 14),
   338  	)
   339  
   340  	var childPidsLogo string
   341  	if app.config.Prefork {
   342  		var childPidsTemplate string
   343  		childPidsTemplate += "%s"
   344  		childPidsTemplate += " ┌───────────────────────────────────────────────────┐\n%s"
   345  		childPidsTemplate += " └───────────────────────────────────────────────────┘"
   346  		childPidsTemplate += "%s"
   347  
   348  		newLine := " │ %s%s%s │"
   349  
   350  		// Turn the `pids` variable (in the form ",a,b,c,d,e,f,etc") into a slice of PIDs
   351  		var pidSlice []string
   352  		for _, v := range strings.Split(pids, ",") {
   353  			if v != "" {
   354  				pidSlice = append(pidSlice, v)
   355  			}
   356  		}
   357  
   358  		var lines []string
   359  		thisLine := "Child PIDs ... "
   360  		var itemsOnThisLine []string
   361  
   362  		const maxLineLen = 49
   363  
   364  		addLine := func() {
   365  			lines = append(lines,
   366  				fmt.Sprintf(
   367  					newLine,
   368  					colors.Black,
   369  					thisLine+colors.Cyan+pad(strings.Join(itemsOnThisLine, ", "), maxLineLen-len(thisLine)),
   370  					colors.Black,
   371  				),
   372  			)
   373  		}
   374  
   375  		for _, pid := range pidSlice {
   376  			if len(thisLine+strings.Join(append(itemsOnThisLine, pid), ", ")) > maxLineLen {
   377  				addLine()
   378  				thisLine = ""
   379  				itemsOnThisLine = []string{pid}
   380  			} else {
   381  				itemsOnThisLine = append(itemsOnThisLine, pid)
   382  			}
   383  		}
   384  
   385  		// Add left over items to their own line
   386  		if len(itemsOnThisLine) != 0 {
   387  			addLine()
   388  		}
   389  
   390  		// Form logo
   391  		childPidsLogo = fmt.Sprintf(childPidsTemplate,
   392  			colors.Black,
   393  			strings.Join(lines, "\n")+"\n",
   394  			colors.Reset,
   395  		)
   396  	}
   397  
   398  	// Combine both the child PID logo and the main Fiber logo
   399  
   400  	// Pad the shorter logo to the length of the longer one
   401  	splitMainLogo := strings.Split(mainLogo, "\n")
   402  	splitChildPidsLogo := strings.Split(childPidsLogo, "\n")
   403  
   404  	mainLen := len(splitMainLogo)
   405  	childLen := len(splitChildPidsLogo)
   406  
   407  	if mainLen > childLen {
   408  		diff := mainLen - childLen
   409  		for i := 0; i < diff; i++ {
   410  			splitChildPidsLogo = append(splitChildPidsLogo, "")
   411  		}
   412  	} else {
   413  		diff := childLen - mainLen
   414  		for i := 0; i < diff; i++ {
   415  			splitMainLogo = append(splitMainLogo, "")
   416  		}
   417  	}
   418  
   419  	// Combine the two logos, line by line
   420  	output := "\n"
   421  	for i := range splitMainLogo {
   422  		output += colors.Black + splitMainLogo[i] + " " + splitChildPidsLogo[i] + "\n"
   423  	}
   424  
   425  	out := colorable.NewColorableStdout()
   426  	if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) {
   427  		out = colorable.NewNonColorable(os.Stdout)
   428  	}
   429  
   430  	_, _ = fmt.Fprintln(out, output)
   431  }
   432  
   433  // printRoutesMessage print all routes with method, path, name and handlers
   434  // in a format of table, like this:
   435  // method | path | name      | handlers
   436  // GET    | /    | routeName | github.com/gofiber/fiber/v2.emptyHandler
   437  // HEAD   | /    |           | github.com/gofiber/fiber/v2.emptyHandler
   438  func (app *App) printRoutesMessage() {
   439  	// ignore child processes
   440  	if IsChild() {
   441  		return
   442  	}
   443  
   444  	// Alias colors
   445  	colors := app.config.ColorScheme
   446  
   447  	var routes []RouteMessage
   448  	for _, routeStack := range app.stack {
   449  		for _, route := range routeStack {
   450  			var newRoute RouteMessage
   451  			newRoute.name = route.Name
   452  			newRoute.method = route.Method
   453  			newRoute.path = route.Path
   454  			for _, handler := range route.Handlers {
   455  				newRoute.handlers += runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name() + " "
   456  			}
   457  			routes = append(routes, newRoute)
   458  		}
   459  	}
   460  
   461  	out := colorable.NewColorableStdout()
   462  	if os.Getenv("TERM") == "dumb" || os.Getenv("NO_COLOR") == "1" || (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd())) {
   463  		out = colorable.NewNonColorable(os.Stdout)
   464  	}
   465  
   466  	w := tabwriter.NewWriter(out, 1, 1, 1, ' ', 0)
   467  	// Sort routes by path
   468  	sort.Slice(routes, func(i, j int) bool {
   469  		return routes[i].path < routes[j].path
   470  	})
   471  
   472  	_, _ = fmt.Fprintf(w, "%smethod\t%s| %spath\t%s| %sname\t%s| %shandlers\t%s\n", colors.Blue, colors.White, colors.Green, colors.White, colors.Cyan, colors.White, colors.Yellow, colors.Reset)
   473  	_, _ = fmt.Fprintf(w, "%s------\t%s| %s----\t%s| %s----\t%s| %s--------\t%s\n", colors.Blue, colors.White, colors.Green, colors.White, colors.Cyan, colors.White, colors.Yellow, colors.Reset)
   474  	for _, route := range routes {
   475  		_, _ = fmt.Fprintf(w, "%s%s\t%s| %s%s\t%s| %s%s\t%s| %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, colors.Reset)
   476  	}
   477  
   478  	_ = w.Flush() //nolint:errcheck // It is fine to ignore the error here
   479  }