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 }