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
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
# Changelog

## [0.8.0] - Mar 29, 2026

### Added

- Graceful shutdown support with `Run()`, `RunListener()`, `Shutdown()`, and `RunGraceful()` methods
- `sync.Pool` for Context reuse to reduce GC pressure and improve performance
- `MaxRequestBodySize` configuration option for limiting request body size
- Middleware caching in route groups for better performance
- Signal handling (SIGINT/SIGTERM) for graceful shutdown
- Comprehensive integration tests with dynamic port allocation

### Changed

- **BREAKING**: Debug mode default changed from `true` to `false`
- Logger middleware now uses `ctx.App.Logger.Info()` instead of `fmt.Printf`
- X-Forwarded-For header parsing now correctly handles comma-separated IP addresses
- Replaced `interface{}` with `any` throughout codebase
- Improved global logger implementation

### Fixed

- Critical shutdown issue where server wouldn't stop properly
- Config merge nil pointer panic
- Request body size limit enforcement
- Context pool reuse bug where data wasn't being reset properly

### Removed

- Deprecated and unused code
- Hardcoded test ports in favor of dynamic allocation

### Performance

- Test coverage improved from 81.2% to 90.9%

## [0.7.3] - Jun 28, 2024

### Added
Expand Down
35 changes: 26 additions & 9 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ type Context struct {
skipFlush bool
}

// reset resets the Context to its zero state for reuse.
func (c *Context) reset() {
c.App = nil
c.Req = nil
c.Res = nil
c.req = nil
c.res = nil
c.data = nil
c.handlers = nil
c.index = -1
c.Method = ""
c.Path = ""
c.skipFlush = false
}

// NewContext creates a new context object with the given HTTP response writer and req.
func NewContext(writer http.ResponseWriter, req *http.Request) (*Context, error) {
request, err := newRequest(req)
Expand Down Expand Up @@ -98,7 +113,7 @@ func (c *Context) StringBody() string {
var validate = validator.New()

// JSONBody parses the origin request body as JSON and stores the result in v.
func (c *Context) JSONBody(v interface{}, valid ...bool) error {
func (c *Context) JSONBody(v any, valid ...bool) error {
decode := json.Unmarshal
if c.App != nil && c.App.Config.JSONDecoder != nil {
decode = c.App.Config.JSONDecoder
Expand Down Expand Up @@ -183,6 +198,7 @@ func (c *Context) ParamFloat64(key string) (float64, error) {
}

// ParamString returns the value of a URL parameter as a string for a given key.
// Deprecated: Use Param instead.
func (c *Context) ParamString(key string) string {
return c.Param(key)
}
Expand All @@ -198,6 +214,7 @@ func (c *Context) Query(key string) string {
}

// QueryString returns the value of a given query parameter as a string.
// Deprecated: Use Query instead.
func (c *Context) QueryString(key string) string {
return c.req.query(key)
}
Expand Down Expand Up @@ -416,7 +433,7 @@ func (c *Context) SetBody(body []byte) {
}

// JSON writes a JSON response with the given status code and object.
func (c *Context) JSON(code int, obj interface{}) {
func (c *Context) JSON(code int, obj any) {
encode := json.Marshal
if c.App != nil && c.App.Config.JSONEncoder != nil {
encode = c.App.Config.JSONEncoder
Expand All @@ -439,7 +456,7 @@ func (c *Context) Text(code int, text string) {
}

// HTML writes an HTML response with the given status code, template name, and data.
func (c *Context) HTML(code int, name string, data interface{}) {
func (c *Context) HTML(code int, name string, data any) {
c.SetHeader(HeaderContentType, MIMETextHTML)
c.SetStatus(code)

Expand All @@ -451,7 +468,7 @@ func (c *Context) HTML(code int, name string, data interface{}) {
}

// XML writes an XML response with the given status code and object.
func (c *Context) XML(code int, obj interface{}) {
func (c *Context) XML(code int, obj any) {
encodeData, err := xml.Marshal(obj)
if err != nil {
panic(err)
Expand All @@ -468,12 +485,12 @@ func (c *Context) File(filepath string) error {
}

// GetData returns the value of a custom data field for the context.
func (c *Context) GetData(key string) interface{} {
func (c *Context) GetData(key string) any {
return c.data.get(key)
}

// SetData sets the value of a custom data field for the context.
func (c *Context) SetData(key string, value interface{}) {
func (c *Context) SetData(key string, value any) {
c.data.set(key, value)
}

Expand Down Expand Up @@ -503,8 +520,8 @@ func (c *Context) RemoteAddr() string {
}

// Success writes a successful response with the given data.
func (c *Context) Success(data interface{}) {
c.JSON(http.StatusOK, map[string]interface{}{
func (c *Context) Success(data any) {
c.JSON(http.StatusOK, map[string]any{
"code": 0,
"message": "ok",
"data": data,
Expand All @@ -513,7 +530,7 @@ func (c *Context) Success(data interface{}) {

// Fail writes a failed response with the given code and message.
func (c *Context) Fail(code int, message string) {
c.JSON(http.StatusOK, map[string]interface{}{
c.JSON(http.StatusOK, map[string]any{
"code": code,
"message": message,
})
Expand Down
20 changes: 18 additions & 2 deletions group.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ type Group struct {
parent *Group
prefix string
middlewares []HandlerFunc
// cachedMiddlewares caches the merged middleware chain to avoid repeated allocations
cachedMiddlewares []HandlerFunc
}

// newGroup creates a new Group with the given prefix and Application.
Expand All @@ -29,11 +31,23 @@ func (g *Group) getFullPrefix() string {
}

// getMiddlewares returns the middleware functions of the Group and its ancestors.
// Uses cached result when available to avoid repeated allocations.
func (g *Group) getMiddlewares() []HandlerFunc {
if g.cachedMiddlewares != nil {
return g.cachedMiddlewares
}

if g.parent == nil {
return g.middlewares
g.cachedMiddlewares = g.middlewares
return g.cachedMiddlewares
}
return append(g.parent.getMiddlewares(), g.middlewares...)

parentMiddlewares := g.parent.getMiddlewares()
merged := make([]HandlerFunc, len(parentMiddlewares)+len(g.middlewares))
copy(merged, parentMiddlewares)
copy(merged[len(parentMiddlewares):], g.middlewares)
g.cachedMiddlewares = merged
return g.cachedMiddlewares
}

// Group creates a new Group with the given prefix and adds it as a child of the current Group.
Expand All @@ -55,6 +69,8 @@ func (g *Group) AddRoute(method string, pattern string, handlers []HandlerFunc)
// Use adds the given middleware functions to the Group's middleware stack.
func (g *Group) Use(middlewares ...HandlerFunc) {
g.middlewares = append(g.middlewares, middlewares...)
// Invalidate cache when middleware changes
g.cachedMiddlewares = nil
}

// Get adds a new GET route to the Application with the given pattern and handlers.
Expand Down
Loading
Loading