2020-02-22 17:03:30 -05:00
// 🚀 Fiber is an Express inspired web framework written in Go with 💖
// 📌 API Documentation: https://fiber.wiki
// 📝 Github Repository: https://github.com/gofiber/fiber
2020-02-21 18:07:43 +01:00
package fiber
import (
"bufio"
"flag"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"time"
fasthttp "github.com/valyala/fasthttp"
)
// Version of Fiber
2020-02-22 16:23:40 -05:00
const Version = "1.7.1"
2020-02-21 18:07:43 +01:00
type (
// App denotes the Fiber application.
App struct {
server * fasthttp . Server
routes [ ] * Route
child bool
recover func ( * Ctx )
Settings * Settings
}
// Map defines a generic map of type `map[string]interface{}`.
Map map [ string ] interface { }
// Settings is a struct holding the server settings
Settings struct {
// fiber settings
Prefork bool ` default:"false" `
// Enable strict routing. When enabled, the router treats "/foo" and "/foo/" as different. Otherwise, the router treats "/foo" and "/foo/" as the same.
StrictRouting bool ` default:"false" `
// Enable case sensitivity. When enabled, "/Foo" and "/foo" are different routes. When disabled, "/Foo" and "/foo" are treated the same.
CaseSensitive bool ` default:"false" `
// Enables the "Server: value" HTTP header.
ServerHeader string ` default:"" `
// fasthttp settings
GETOnly bool ` default:"false" `
IdleTimeout time . Duration ` default:"0" `
Concurrency int ` default:"256 * 1024" `
ReadTimeout time . Duration ` default:"0" `
WriteTimeout time . Duration ` default:"0" `
TCPKeepalive bool ` default:"false" `
MaxConnsPerIP int ` default:"0" `
ReadBufferSize int ` default:"4096" `
WriteBufferSize int ` default:"4096" `
ConcurrencySleep time . Duration ` default:"0" `
DisableKeepAlive bool ` default:"false" `
ReduceMemoryUsage bool ` default:"false" `
MaxRequestsPerConn int ` default:"0" `
TCPKeepalivePeriod time . Duration ` default:"0" `
MaxRequestBodySize int ` default:"4 * 1024 * 1024" `
NoHeaderNormalizing bool ` default:"false" `
NoDefaultContentType bool ` default:"false" `
// template settings
ViewCache bool ` default:"false" `
ViewFolder string ` default:"" `
ViewEngine string ` default:"" `
ViewExtension string ` default:"" `
}
)
2020-02-21 18:56:49 +01:00
var prefork = flag . Bool ( "fiber-prefork" , false , "use prefork" )
var child = flag . Bool ( "fiber-child" , false , "is child process" )
2020-02-21 18:07:43 +01:00
// New ...
func New ( settings ... * Settings ) ( app * App ) {
2020-02-21 18:59:00 +01:00
flag . Parse ( )
2020-02-21 18:07:43 +01:00
app = & App {
2020-02-21 18:44:51 +01:00
child : * child ,
2020-02-21 18:07:43 +01:00
}
if len ( settings ) > 0 {
opt := settings [ 0 ]
if ! opt . Prefork {
2020-02-21 18:44:51 +01:00
opt . Prefork = * prefork
2020-02-21 18:07:43 +01:00
}
if opt . Concurrency == 0 {
opt . Concurrency = 256 * 1024
}
if opt . ReadBufferSize == 0 {
opt . ReadBufferSize = 4096
}
if opt . WriteBufferSize == 0 {
opt . WriteBufferSize = 4096
}
if opt . MaxRequestBodySize == 0 {
opt . MaxRequestBodySize = 4 * 1024 * 1024
}
app . Settings = opt
return
}
app . Settings = & Settings {
2020-02-21 18:44:51 +01:00
Prefork : * prefork ,
2020-02-21 18:07:43 +01:00
Concurrency : 256 * 1024 ,
ReadBufferSize : 4096 ,
WriteBufferSize : 4096 ,
MaxRequestBodySize : 4 * 1024 * 1024 ,
}
return
}
2020-02-21 22:36:26 -05:00
// Recover
func ( app * App ) Recover ( cb func ( * Ctx ) ) {
app . recover = cb
}
// Recover
func ( grp * Group ) Recover ( cb func ( * Ctx ) ) {
grp . app . recover = cb
}
2020-02-21 18:07:43 +01:00
// Static ...
func ( app * App ) Static ( args ... string ) * App {
app . registerStatic ( "/" , args ... )
return app
}
// WebSocket ...
func ( app * App ) WebSocket ( args ... interface { } ) * App {
app . register ( "GET" , "" , args ... )
return app
}
// Connect ...
func ( app * App ) Connect ( args ... interface { } ) * App {
app . register ( "CONNECT" , "" , args ... )
return app
}
// Put ...
func ( app * App ) Put ( args ... interface { } ) * App {
app . register ( "PUT" , "" , args ... )
return app
}
// Post ...
func ( app * App ) Post ( args ... interface { } ) * App {
app . register ( "POST" , "" , args ... )
return app
}
// Delete ...
func ( app * App ) Delete ( args ... interface { } ) * App {
app . register ( "DELETE" , "" , args ... )
return app
}
// Head ...
func ( app * App ) Head ( args ... interface { } ) * App {
app . register ( "HEAD" , "" , args ... )
return app
}
// Patch ...
func ( app * App ) Patch ( args ... interface { } ) * App {
app . register ( "PATCH" , "" , args ... )
return app
}
// Options ...
func ( app * App ) Options ( args ... interface { } ) * App {
app . register ( "OPTIONS" , "" , args ... )
return app
}
// Trace ...
func ( app * App ) Trace ( args ... interface { } ) * App {
app . register ( "TRACE" , "" , args ... )
return app
}
// Get ...
func ( app * App ) Get ( args ... interface { } ) * App {
app . register ( "GET" , "" , args ... )
return app
}
// All ...
func ( app * App ) All ( args ... interface { } ) * App {
app . register ( "ALL" , "" , args ... )
return app
}
// Use ...
func ( app * App ) Use ( args ... interface { } ) * App {
app . register ( "USE" , "" , args ... )
return app
}
// Listen : https://fiber.wiki/application#listen
func ( app * App ) Listen ( address interface { } , tls ... string ) error {
addr , ok := address . ( string )
if ! ok {
port , ok := address . ( int )
if ! ok {
return fmt . Errorf ( "Listen: Host must be an INT port or STRING address" )
}
addr = strconv . Itoa ( port )
}
if ! strings . Contains ( addr , ":" ) {
addr = ":" + addr
}
// Create fasthttp server
app . server = app . newServer ( )
// Print banner
// if app.Settings.Banner && !app.child {
// fmt.Printf("Fiber-%s is listening on %s\n", Version, addr)
// }
var ln net . Listener
var err error
// Prefork enabled
if app . Settings . Prefork && runtime . NumCPU ( ) > 1 {
if ln , err = app . prefork ( addr ) ; err != nil {
return err
}
} else {
if ln , err = net . Listen ( "tcp4" , addr ) ; err != nil {
return err
}
}
// enable TLS/HTTPS
if len ( tls ) > 1 {
return app . server . ServeTLS ( ln , tls [ 0 ] , tls [ 1 ] )
}
return app . server . Serve ( ln )
}
// Shutdown server gracefully
func ( app * App ) Shutdown ( ) error {
if app . server == nil {
return fmt . Errorf ( "Server is not running" )
}
return app . server . Shutdown ( )
}
// Test takes a http.Request and execute a fake connection to the application
// It returns a http.Response when the connection was successful
func ( app * App ) Test ( req * http . Request ) ( * http . Response , error ) {
// Get raw http request
reqRaw , err := httputil . DumpRequest ( req , true )
if err != nil {
return nil , err
}
// Setup a fiber server struct
app . server = app . newServer ( )
// Create fake connection
conn := & testConn { }
// Pass HTTP request to conn
_ , err = conn . r . Write ( reqRaw )
if err != nil {
return nil , err
}
// Serve conn to server
channel := make ( chan error )
go func ( ) {
channel <- app . server . ServeConn ( conn )
} ( )
// Wait for callback
select {
case err := <- channel :
if err != nil {
return nil , err
}
// Throw timeout error after 200ms
case <- time . After ( 1000 * time . Millisecond ) :
return nil , fmt . Errorf ( "timeout" )
}
// Get raw HTTP response
respRaw , err := ioutil . ReadAll ( & conn . w )
if err != nil {
return nil , err
}
// Create buffer
reader := strings . NewReader ( getString ( respRaw ) )
buffer := bufio . NewReader ( reader )
// Convert raw HTTP response to http.Response
resp , err := http . ReadResponse ( buffer , req )
if err != nil {
return nil , err
}
// Return *http.Response
return resp , nil
}
// https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/
func ( app * App ) prefork ( address string ) ( ln net . Listener , err error ) {
// Master proc
if ! app . child {
addr , err := net . ResolveTCPAddr ( "tcp" , address )
if err != nil {
return ln , err
}
tcplistener , err := net . ListenTCP ( "tcp" , addr )
if err != nil {
return ln , err
}
fl , err := tcplistener . File ( )
if err != nil {
return ln , err
}
childs := make ( [ ] * exec . Cmd , runtime . NumCPU ( ) / 2 )
// #nosec G204
for i := range childs {
2020-02-21 18:56:49 +01:00
childs [ i ] = exec . Command ( os . Args [ 0 ] , "-fiber-prefork" , "-fiber-child" )
2020-02-21 18:07:43 +01:00
childs [ i ] . Stdout = os . Stdout
childs [ i ] . Stderr = os . Stderr
childs [ i ] . ExtraFiles = [ ] * os . File { fl }
if err := childs [ i ] . Start ( ) ; err != nil {
return ln , err
}
}
for _ , child := range childs {
if err := child . Wait ( ) ; err != nil {
return ln , err
}
}
os . Exit ( 0 )
} else {
ln , err = net . FileListener ( os . NewFile ( 3 , "" ) )
}
return ln , err
}
func ( app * App ) newServer ( ) * fasthttp . Server {
return & fasthttp . Server {
Handler : app . handler ,
ErrorHandler : func ( ctx * fasthttp . RequestCtx , err error ) {
ctx . Response . SetStatusCode ( 400 )
ctx . Response . SetBodyString ( "Bad Request" )
} ,
Name : app . Settings . ServerHeader ,
Concurrency : app . Settings . Concurrency ,
SleepWhenConcurrencyLimitsExceeded : app . Settings . ConcurrencySleep ,
DisableKeepalive : app . Settings . DisableKeepAlive ,
ReadBufferSize : app . Settings . ReadBufferSize ,
WriteBufferSize : app . Settings . WriteBufferSize ,
ReadTimeout : app . Settings . ReadTimeout ,
WriteTimeout : app . Settings . WriteTimeout ,
IdleTimeout : app . Settings . IdleTimeout ,
MaxConnsPerIP : app . Settings . MaxConnsPerIP ,
MaxRequestsPerConn : app . Settings . MaxRequestsPerConn ,
TCPKeepalive : app . Settings . TCPKeepalive ,
TCPKeepalivePeriod : app . Settings . TCPKeepalivePeriod ,
MaxRequestBodySize : app . Settings . MaxRequestBodySize ,
ReduceMemoryUsage : app . Settings . ReduceMemoryUsage ,
GetOnly : app . Settings . GETOnly ,
DisableHeaderNamesNormalizing : app . Settings . NoHeaderNormalizing ,
NoDefaultServerHeader : app . Settings . ServerHeader == "" ,
NoDefaultContentType : app . Settings . NoDefaultContentType ,
}
}