Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func main() {
app := web.Default()

// Add a simple handler that responds with "Hello, World!" to any request.
app.Use(func(ctx *core.Context) error {
app.Use(func(ctx core.Context) error {
ctx.Write([]byte("Hello, World!"))
return nil
})
Expand All @@ -42,6 +42,20 @@ The server will listen on the default port (8000) and respond with "Hello, World

For more detailed examples and usage, please refer to the [examples](https://github.com/go-amwk/examples) repository.

## Configuration

The `web` package can be configured using various options when creating a new application instance. Here are some of the available configuration options:

- `WithAddress(addr string)`: Sets the address for the server to listen on. Default is `:8000`.
- `WithEnableShutdownSignal(enable bool, signals ...os.Signal)`: Enables or disables shutdown signal handling. By default, it is enabled and listens for `os.Interrupt`, `syscall.SIGTERM`, and `syscall.SIGQUIT` signals.
- `WithIdleTimeout(timeout time.Duration)`: Sets the maximum amount of time to wait for the next request when keep-alives are enabled. Default is `60s`.
- `WithMaxHeaderBytes(size int)`: Sets the maximum size of request headers in bytes. Default is `1 MB` (1048576 bytes).
- `WithMaxResponseBodyBytes(size int64)`: Sets the maximum size of the response body in bytes. Default is `32 MB`. Set to `-1` for unlimited body size.
- `WithReadHeaderTimeout(timeout time.Duration)`: Sets the maximum duration for reading request headers. Default is `10s`.
- `WithReadTimeout(timeout time.Duration)`: Sets the maximum duration for reading the entire request, including the body. Default is `30s`.
- `WithShutdownTimeout(timeout time.Duration)`: Sets the maximum duration for gracefully shutting down the server. Default is `5s`.
- `WithWriteTimeout(timeout time.Duration)`: Sets the maximum duration before timing out writes of the response. Default is `30s`.

## Contributing

- Contributions are welcome. Please open issues for bugs or feature requests.
Expand Down
220 changes: 208 additions & 12 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,102 @@ import (
"net/http"
"os"
"os/signal"
"sync/atomic"
"syscall"
"time"

"github.com/go-amwk/core"
"github.com/go-amwk/engine"
)

const (
// AddressDefault defines the default address for the application server to listen on. It is set
// to ":8000", which means the server will listen on all available network interfaces on
// port 8000.
AddressDefault = ":8000"
// IdleTimeoutDefault defines the default maximum amount of time to wait for the next request
// when keep-alives are enabled. It is set to 60 seconds.
IdleTimeoutDefault time.Duration = 60 * time.Second
// MaxHeaderBytesDefault defines the default maximum size of request headers. It is set to
// 1 MB.
MaxHeaderBytesDefault int = 1 << 20
// MaxRequestBodyBytesDefault defines the default maximum body size for requests. It is set to
// 32 MB.
MaxRequestBodyBytesDefault int64 = 1 << 25
// MaxRequestBodyBytesUnlimited indicates a special value that represents no limit on the body
// size for requests.
MaxRequestBodyBytesUnlimited int64 = -1
// MaxResponseBodyBytesDefault defines the default maximum body size for responses. It is set
// to 32 MB.
MaxResponseBodyBytesDefault int64 = 1 << 25
// MaxResponseBodyBytesUnlimited defines a special value indicating that there is no limit on
// the body size for responses.
MaxResponseBodyBytesUnlimited int64 = -1
// ReadHeaderTimeoutDefault defines the default maximum duration for reading the headers of
// the request. It is set to 10 seconds.
ReadHeaderTimeoutDefault time.Duration = 10 * time.Second
// ReadTimeoutDefault defines the default maximum duration for reading the entire request,
// including the body. It is set to 30 seconds.
ReadTimeoutDefault time.Duration = 30 * time.Second
// ShutdownTimeoutDefault defines the default maximum duration for gracefully shutting down
// the application server. It is set to 5 seconds.
ShutdownTimeoutDefault time.Duration = 5 * time.Second
// WriteTimeoutDefault defines the default maximum duration before timing out writes of the
// response. It is set to 30 seconds.
WriteTimeoutDefault time.Duration = 30 * time.Second
)

var (
// shutdownListenSignalsDefault defines the default signals to listen for when shutting down the
// application. It includes os.Interrupt, syscall.SIGTERM, and syscall.SIGQUIT.
shutdownListenSignalsDefault = []os.Signal{os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}
)

// Application is an HTTP web application to serve HTTP requests.
type Application struct {
addr string
server *http.Server
handlers []core.HandlerFunc

maxRequestBodyBytes atomic.Int64
maxResponseBodyBytes atomic.Int64
enableShutdownSignal atomic.Bool
shutdownListenSignals []os.Signal
shutdownTimeout time.Duration
}

// defaultApp creates a new Application instance with default settings.
func defaultApp() *Application {
app := &Application{}
app.handlers = make([]core.HandlerFunc, 0)
app.addr = ":8000"
app.server = &http.Server{
Handler: app,
Handler: app,
IdleTimeout: IdleTimeoutDefault,
MaxHeaderBytes: MaxHeaderBytesDefault,
ReadHeaderTimeout: ReadHeaderTimeoutDefault,
ReadTimeout: ReadTimeoutDefault,
WriteTimeout: WriteTimeoutDefault,
}
app.
SetAddress(AddressDefault).
SetEnableShutdownSignal(true, shutdownListenSignalsDefault...).
SetMaxRequestBodyBytes(MaxRequestBodyBytesDefault).
SetMaxResponseBodyBytes(MaxResponseBodyBytesDefault).
SetShutdownTimeout(ShutdownTimeoutDefault)

return app
}

// Default returns a default application instance with default settings.
func Default() core.Application {
func Default() *Application {
app := defaultApp()

return app
}

// New returns a new application instance with default settings. It allows for further
// customization before starting the server.
func New(opts ...Option) core.Application {
func New(opts ...Option) *Application {
app := defaultApp()

for _, opt := range opts {
Expand All @@ -59,12 +119,20 @@ func (app *Application) Start() error {
errCh <- app.server.ListenAndServe()
}()

sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
defer signal.Stop(sig)
sig := app.listenShutdownSignals(app.shutdownListenSignals)
defer func() {
if sig != nil {
signal.Stop(sig)
}
}()
select {
case <-sig:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
shutdownTimeout := app.shutdownTimeout
if shutdownTimeout <= 0 {
shutdownTimeout = ShutdownTimeoutDefault
}

ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
defer cancel()
return app.Shutdown(ctx)
case err := <-errCh:
Expand Down Expand Up @@ -99,12 +167,140 @@ func (app *Application) Shutdown(ctx context.Context) error {

// ServeHTTP implements the http.Handler interface to handle incoming HTTP requests.
func (app *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) {
req := newRequest(r)
resp := newResponse(w)
req := newRequest(app, r)
resp := newResponse(app, w)
ctx := engine.NewContext(app, req, resp)
ctx.Use(app.handlers...)

ctx.Next()
// TODO: add error handling
_ = ctx.Next()

_ = resp.send()
Comment on lines +175 to +178
}

// Address returns the address the application is listening on.
func (app *Application) Address() string {
return app.addr
}

// SetAddress sets the address for the application to listen on. Please call this method before
// starting the application server.
func (app *Application) SetAddress(addr string) *Application {
app.addr = addr
return app
}

// SetEnableShutdownSignal enables or disables the handling of shutdown signals for the
// application. If enabled, the application will listen for shutdown signals (e.g., os.Interrupt,
// syscall.SIGTERM, syscall.SIGQUIT) and gracefully shut down when such a signal is received.
// You can also specify custom signals to listen for by providing them as additional arguments.
func (app *Application) SetEnableShutdownSignal(enable bool, signals ...os.Signal) *Application {
app.enableShutdownSignal.Store(enable)
if len(signals) > 0 {
app.shutdownListenSignals = signals
}
return app
}

// SetIdleTimeout sets the maximum amount of time to wait for the next request when keep-alives are
// enabled. It is recommended to call this method before starting the application server to avoid
// unexpected behavior.
func (app *Application) SetIdleTimeout(timeout time.Duration) *Application {
if app.server != nil {
app.server.IdleTimeout = timeout
}
return app
}

// SetMaxHeaderBytes sets the maximum size of request headers. It is recommended to call this
// method before starting the application server to avoid unexpected behavior.
func (app *Application) SetMaxHeaderBytes(size int) *Application {
if app.server != nil {
app.server.MaxHeaderBytes = size
}
return app
}

// MaxRequestBodyBytes returns the maximum body size for requests. If the size is set to
// MaxRequestBodyBytesUnlimited, there will be no limit on the body size.
func (app *Application) MaxRequestBodyBytes() int64 {
return app.maxRequestBodyBytes.Load()
}

// SetMaxRequestBodyBytes sets the maximum body size for requests. If the size is set to
// MaxRequestBodyBytesUnlimited, there will be no limit on the body size.
// It would be better to call this method before starting the application server to avoid
// unexpected behavior.
func (app *Application) SetMaxRequestBodyBytes(size int64) *Application {
app.maxRequestBodyBytes.Store(size)
return app
}

// MaxResponseBodyBytes returns the maximum body size for responses. If the size is set to
// MaxResponseBodyBytesUnlimited, there will be no limit on the body size.
func (app *Application) MaxResponseBodyBytes() int64 {
return app.maxResponseBodyBytes.Load()
}

// SetMaxResponseBodyBytes sets the maximum body size for responses. If the size is set to
// MaxResponseBodyBytesUnlimited, there will be no limit on the body size.
// It would be better to call this method before starting the application server to avoid
// unexpected behavior.
func (app *Application) SetMaxResponseBodyBytes(size int64) *Application {
app.maxResponseBodyBytes.Store(size)
return app
}

// SetReadHeaderTimeout sets the maximum duration for reading the headers of the request. It is
// recommended to call this method before starting the application server to avoid unexpected
// behavior.
func (app *Application) SetReadHeaderTimeout(timeout time.Duration) *Application {
if app.server != nil {
app.server.ReadHeaderTimeout = timeout
}
return app
}

// SetReadTimeout sets the maximum duration for reading the entire request, including the body. It
// is recommended to call this method before starting the application server to avoid unexpected
// behavior.
func (app *Application) SetReadTimeout(timeout time.Duration) *Application {
if app.server != nil {
app.server.ReadTimeout = timeout
}
return app
}

// SetShutdownTimeout sets the maximum duration for gracefully shutting down the application
// server. It is recommended to call this method before starting the application server to avoid
// unexpected behavior.
func (app *Application) SetShutdownTimeout(timeout time.Duration) *Application {
app.shutdownTimeout = timeout
return app
}

// SetWriteTimeout sets the maximum duration before timing out writes of the response. It is
// recommended to call this method before starting the application server to avoid unexpected
// behavior.
func (app *Application) SetWriteTimeout(timeout time.Duration) *Application {
if app.server != nil {
app.server.WriteTimeout = timeout
}
return app
}

// listenShutdownSignals listens for shutdown signals if enabled and returns a channel that
// receives the signals. If shutdown signal handling is disabled, it returns nil.
func (app *Application) listenShutdownSignals(signals []os.Signal) chan os.Signal {
if !app.enableShutdownSignal.Load() {
return nil
}

if len(signals) == 0 {
signals = shutdownListenSignalsDefault
}

resp.send()
sig := make(chan os.Signal, 1)
signal.Notify(sig, signals...)
return sig
}
Loading