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 }