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