Skip to content

Commit

Permalink
Fix, optimization and doc improvement
Browse files Browse the repository at this point in the history
  • Loading branch information
tigerwill90 committed Apr 9, 2023
1 parent fd71fd2 commit 5c83d40
Show file tree
Hide file tree
Showing 9 changed files with 337 additions and 190 deletions.
295 changes: 160 additions & 135 deletions README.md

Large diffs are not rendered by default.

Binary file removed assets/tree-apply-patch.png
Binary file not shown.
16 changes: 11 additions & 5 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ func (c *context) Path() string {

// String sends a formatted string with the specified status code.
func (c *context) String(code int, format string, values ...any) (err error) {
c.w.Header().Set(HeaderContentType, MIMETextPlainCharsetUTF8)
if c.w.Header().Get(HeaderContentType) == "" {
c.w.Header().Set(HeaderContentType, MIMETextPlainCharsetUTF8)
}
c.w.WriteHeader(code)
_, err = fmt.Fprintf(c.w, format, values...)
return
Expand Down Expand Up @@ -243,27 +245,31 @@ func (c *context) getQueries() url.Values {

// WrapF is an adapter for wrapping http.HandlerFunc and returns a HandlerFunc function.
func WrapF(f http.HandlerFunc) HandlerFunc {
return func(c Context) {
return func(c Context) error {
f.ServeHTTP(c.Writer(), c.Request())
return nil
}
}

// WrapH is an adapter for wrapping http.Handler and returns a HandlerFunc function.
func WrapH(h http.Handler) HandlerFunc {
return func(c Context) {
return func(c Context) error {
h.ServeHTTP(c.Writer(), c.Request())
return nil
}
}

// WrapM is an adapter for wrapping http.Handler middleware and returns a
// MiddlewareFunc function.
func WrapM(m func(handler http.Handler) http.Handler) MiddlewareFunc {
return func(next HandlerFunc) HandlerFunc {
return func(c Context) {
return func(c Context) error {
var err error
adapter := m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next(c)
err = next(c)
}))
adapter.ServeHTTP(c.Writer(), c.Request())
return err
}
}
}
31 changes: 31 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package fox
import (
"errors"
"fmt"
"net/http"
"strings"
)

Expand Down Expand Up @@ -58,3 +59,33 @@ func (e *RouteConflictError) updateError() string {
func (e *RouteConflictError) Unwrap() error {
return e.err
}

// HTTPError represents an HTTP error with a status code (HTTPErrorCode)
// and an optional error message. If no error message is provided,
// the default error message for the status code will be used.
type HTTPError struct {
Code int
Err error
}

// Error returns the error message associated with the HTTPError,
// or the default error message for the status code if none is provided.
func (e HTTPError) Error() string {
if e.Err == nil {
return http.StatusText(e.Code)
}
return e.Err.Error()
}

// NewHTTPError creates a new HTTPError with the given status code
// and an optional error message.
func NewHTTPError(code int, err ...error) HTTPError {
var e error
if len(err) > 0 {
e = err[0]
}
return HTTPError{
Code: code,
Err: e,
}
}
46 changes: 38 additions & 8 deletions fox.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,22 @@ var commonVerbs = [verb]string{http.MethodGet, http.MethodPost, http.MethodPut,
// response, panic with the value http.ErrAbortHandler.
//
// HandlerFunc functions should be thread-safe, as they will be called concurrently.
type HandlerFunc func(c Context)
type HandlerFunc func(c Context) error

// MiddlewareFunc is a function type for implementing HandlerFunc middleware.
// The returned HandlerFunc usually wraps the input HandlerFunc, allowing you to perform operations
// before and/or after the wrapped HandlerFunc is executed. MiddlewareFunc functions should
// be thread-safe, as they will be called concurrently.
type MiddlewareFunc func(next HandlerFunc) HandlerFunc

type ErrorHandlerFunc func(c Context, err error)

// Router is a lightweight high performance HTTP request router that support mutation on its routing tree
// while handling request concurrently.
type Router struct {
noRoute HandlerFunc
noMethod HandlerFunc
errRoute ErrorHandlerFunc
tree atomic.Pointer[Tree]
mws []MiddlewareFunc
handleMethodNotAllowed bool
Expand All @@ -55,6 +58,8 @@ func New(opts ...Option) *Router {

r.noRoute = NotFoundHandler()
r.noMethod = MethodNotAllowedHandler()
r.errRoute = RouteErrorHandler()

for _, opt := range opts {
opt.apply(r)
}
Expand Down Expand Up @@ -217,14 +222,31 @@ Next:
// with a “404 page not found” reply.
func NotFoundHandler() HandlerFunc {
http.NotFoundHandler()
return func(c Context) { http.Error(c.Writer(), "404 page not found", http.StatusNotFound) }
return func(c Context) error {
http.Error(c.Writer(), "404 page not found", http.StatusNotFound)
return nil
}
}

// MethodNotAllowedHandler returns a simple HandlerFunc that replies to each request
// with a “405 Method Not Allowed” reply.
func MethodNotAllowedHandler() HandlerFunc {
return func(c Context) {
return func(c Context) error {
http.Error(c.Writer(), http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return nil
}
}

// RouteErrorHandler returns an ErrorHandlerFunc that handle HandlerFunc error.
func RouteErrorHandler() ErrorHandlerFunc {
return func(c Context, err error) {
if !c.Writer().Written() {
if e, ok := err.(HTTPError); ok {
http.Error(c.Writer(), e.Error(), e.Code)
return
}
http.Error(c.Writer(), http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}
}

Expand All @@ -235,8 +257,7 @@ func (fox *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
tsr bool
)

tree := fox.Tree()

tree := fox.tree.Load()
c := tree.ctx.Get().(*context)
c.reset(fox, w, r)

Expand All @@ -249,7 +270,10 @@ func (fox *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
n, tsr = tree.lookup(nds[index], r.URL.Path, c.params, c.skipNds, false)
if n != nil {
c.path = n.path
n.handler(c)
err := n.handler(c)
if err != nil {
fox.errRoute(c, err)
}
// Put back the context, if not extended more than max params or max depth, allowing
// the slice to naturally grow within the constraint.
if cap(*c.params) <= int(tree.maxParams.Load()) && cap(*c.skipNds) <= int(tree.maxDepth.Load()) {
Expand Down Expand Up @@ -311,13 +335,19 @@ NoMethodFallback:
allowed := sb.String()
if allowed != "" {
w.Header().Set("Allow", allowed)
fox.noMethod(c)
err := fox.noMethod(c)
if err != nil {
fox.errRoute(c, err)
}
c.Close()
return
}
}

fox.noRoute(c)
err := fox.noRoute(c)
if err != nil {
fox.errRoute(c, err)
}
c.Close()
}

Expand Down
Loading

0 comments on commit 5c83d40

Please sign in to comment.