Skip to content

Commit

Permalink
feat: returns registered route on handler & update
Browse files Browse the repository at this point in the history
  • Loading branch information
tigerwill90 committed Oct 14, 2024
1 parent dcc925a commit 1d5f706
Show file tree
Hide file tree
Showing 10 changed files with 267 additions and 223 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ routing structure based on user input, configuration changes, or other runtime e
The current api is not yet stabilize. Breaking changes may occur before `v1.0.0` and will be noted on the release note.

## Features
**Runtime updates:** Register, update and remove route handler safely at any time without impact on performance. Fox never block while serving
**Runtime updates:** Register, update and delete route handler safely at any time without impact on performance. Fox never block while serving
request!

**Wildcard pattern:** Route can be registered using wildcard parameters. The matched path segment can then be easily retrieved by
Expand Down Expand Up @@ -206,7 +206,7 @@ As such threads that route requests should never encounter latency due to ongoin

### Managing routes a runtime
#### Routing mutation
In this example, the handler for `routes/{action}` allow to dynamically register, update and remove handler for the
In this example, the handler for `routes/{action}` allow to dynamically register, update and delete handler for the
given route and method. Thanks to Fox's design, those actions are perfectly safe and may be executed concurrently.

````go
Expand Down Expand Up @@ -249,7 +249,7 @@ func Action(c fox.Context) {
_ = c.String(http.StatusOK, text)
})
case "delete":
err = c.Fox().Remove(method, path)
err = c.Fox().Delete(method, path)
default:
http.Error(c.Writer(), fmt.Sprintf("action %q is not allowed", action), http.StatusBadRequest)
return
Expand Down
48 changes: 22 additions & 26 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ type Context interface {
ClientIP() (*net.IPAddr, error)
// Path returns the registered path or an empty string if the handler is called in a scope other than [RouteHandler].
Path() string
// Route returns the registered route or nil if the handler is called in a scope other than [RouteHandler].
// Route returns the registered [Route] or nil if the handler is called in a scope other than [RouteHandler].
Route() *Route
// Params returns a range iterator over the matched wildcard parameters for the current route.
Params() iter.Seq[Param]
Expand Down Expand Up @@ -101,7 +101,7 @@ type Context interface {
Rehydrate(route *Route) bool
}

// cTx holds request-related information and allows interaction with the ResponseWriter.
// cTx holds request-related information and allows interaction with the [ResponseWriter].
type cTx struct {
w ResponseWriter
req *http.Request
Expand All @@ -120,7 +120,7 @@ type cTx struct {
tsr bool
}

// Reset resets the Context to its initial state, attaching the provided ResponseWriter and http.Request.
// Reset resets the [Context] to its initial state, attaching the provided [ResponseWriter] and [http.Request].
func (c *cTx) Reset(w ResponseWriter, r *http.Request) {
c.req = r
c.w = w
Expand All @@ -131,8 +131,8 @@ func (c *cTx) Reset(w ResponseWriter, r *http.Request) {
*c.params = (*c.params)[:0]
}

// Rehydrate updates the current Context to serve the provided Route, bypassing the need for a full tree lookup.
// It succeeds only if the Request's URL path strictly matches the given Route. If successful, the internal state
// Rehydrate updates the current [Context] to serve the provided [Route], bypassing the need for a full tree lookup.
// It succeeds only if the [http.Request]'s URL path strictly matches the given [Route]. If successful, the internal state
// of the context is updated, allowing the context to serve the route directly, regardless of whether the route
// still exists in the routing tree. This provides a key advantage in concurrent scenarios where routes may be
// modified by other threads, as Rehydrate guarantees success if the path matches, without requiring serial execution
Expand Down Expand Up @@ -167,9 +167,9 @@ func (c *cTx) Rehydrate(route *Route) bool {
return true
}

// reset resets the Context to its initial state, attaching the provided http.ResponseWriter and http.Request.
// Caution: always pass the original http.ResponseWriter to this method, not the ResponseWriter itself, to
// avoid wrapping the ResponseWriter within itself. Use wisely!
// reset resets the [Context] to its initial state, attaching the provided [http.ResponseWriter] and [http.Request].
// Caution: always pass the original [http.ResponseWriter] to this method, not the [ResponseWriter] itself, to
// avoid wrapping the [ResponseWriter] within itself. Use wisely!
func (c *cTx) reset(w http.ResponseWriter, r *http.Request) {
c.rec.reset(w)
c.req = r
Expand All @@ -188,27 +188,27 @@ func (c *cTx) resetNil() {
*c.params = (*c.params)[:0]
}

// Request returns the *http.Request.
// Request returns the [http.Request].
func (c *cTx) Request() *http.Request {
return c.req
}

// SetRequest sets the *http.Request.
// SetRequest sets the [http.Request].
func (c *cTx) SetRequest(r *http.Request) {
c.req = r
}

// Writer returns the ResponseWriter.
// Writer returns the [ResponseWriter].
func (c *cTx) Writer() ResponseWriter {
return c.w
}

// SetWriter sets the ResponseWriter.
// SetWriter sets the [ResponseWriter].
func (c *cTx) SetWriter(w ResponseWriter) {
c.w = w
}

// RemoteIP parses the IP from Request.RemoteAddr, normalizes it, and returns a *net.IPAddr.
// RemoteIP parses the IP from [http.Request.RemoteAddr], normalizes it, and returns a [net.IPAddr].
// It never returns nil, even if parsing the IP fails.
func (c *cTx) RemoteIP() *net.IPAddr {
ipStr, _, _ := net.SplitHostPort(c.req.RemoteAddr)
Expand All @@ -226,9 +226,9 @@ func (c *cTx) RemoteIP() *net.IPAddr {
return ipAddr
}

// ClientIP returns the "real" client IP address based on the configured ClientIPStrategy.
// The strategy is set using the WithClientIPStrategy option. If no strategy is configured,
// the method returns error ErrNoClientIPStrategy.
// ClientIP returns the "real" client IP address based on the configured [ClientIPStrategy].
// The strategy is set using the [WithClientIPStrategy] option. If no strategy is configured,
// the method returns error [ErrNoClientIPStrategy].
//
// The strategy used must be chosen and tuned for your network configuration. This should result
// in the strategy never returning an error -- i.e., never failing to find a candidate for the "real" IP.
Expand Down Expand Up @@ -264,7 +264,6 @@ func (c *cTx) Params() iter.Seq[Param] {
}

// Param retrieve a matching wildcard segment by name.
// It's a helper for c.Params.Get(name).
func (c *cTx) Param(name string) string {
for p := range c.Params() {
if p.Key == name {
Expand All @@ -274,15 +273,12 @@ func (c *cTx) Param(name string) string {
return ""
}

// QueryParams parses RawQuery and returns the corresponding values.
// It's a helper for c.Request.URL.Query(). Note that the parsed
// result is cached.
// QueryParams parses the [http.Request] raw query and returns the corresponding values.
func (c *cTx) QueryParams() url.Values {
return c.getQueries()
}

// QueryParam returns the first value associated with the given key.
// It's a helper for c.QueryParams().Get(name).
func (c *cTx) QueryParam(name string) string {
return c.getQueries().Get(name)
}
Expand All @@ -297,15 +293,15 @@ func (c *cTx) Header(key string) string {
return c.req.Header.Get(key)
}

// Path returns the registered path or an empty string if the handler is called in a scope other than RouteHandler.
// Path returns the registered path or an empty string if the handler is called in a scope other than [RouteHandler].
func (c *cTx) Path() string {
if c.route == nil {
return ""
}
return c.route.path
}

// Route returns the registered route or nil if the handler is called in a scope other than RouteHandler.
// Route returns the registered [Route] or nil if the handler is called in a scope other than [RouteHandler].
func (c *cTx) Route() *Route {
return c.route
}
Expand All @@ -328,7 +324,7 @@ func (c *cTx) Blob(code int, contentType string, buf []byte) (err error) {
return
}

// Stream sends data from an io.Reader with the specified status code and content type.
// Stream sends data from an [io.Reader] with the specified status code and content type.
func (c *cTx) Stream(code int, contentType string, r io.Reader) (err error) {
c.w.Header().Set(HeaderContentType, contentType)
c.w.WriteHeader(code)
Expand All @@ -345,12 +341,12 @@ func (c *cTx) Redirect(code int, url string) error {
return nil
}

// Tree is a local copy of the Tree in use to serve the request.
// Tree is a local copy of the [Tree] in use to serve the request.
func (c *cTx) Tree() *Tree {
return c.tree
}

// Fox returns the Router instance.
// Fox returns the [Router] instance.
func (c *cTx) Fox() *Router {
return c.fox
}
Expand Down
14 changes: 8 additions & 6 deletions context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,9 +392,9 @@ func TestContext_Fox(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/foo", nil)

f := New()
require.NoError(t, f.Handle(http.MethodGet, "/foo", func(c Context) {
require.NoError(t, onlyError(f.Handle(http.MethodGet, "/foo", func(c Context) {
assert.NotNil(t, c.Fox())
}))
})))

f.ServeHTTP(w, req)
}
Expand All @@ -405,9 +405,9 @@ func TestContext_Tree(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/foo", nil)

f := New()
require.NoError(t, f.Handle(http.MethodGet, "/foo", func(c Context) {
require.NoError(t, onlyError(f.Handle(http.MethodGet, "/foo", func(c Context) {
assert.NotNil(t, c.Tree())
}))
})))

f.ServeHTTP(w, req)
}
Expand All @@ -433,9 +433,11 @@ func TestContext_Scope(t *testing.T) {
assert.Equal(t, OptionsHandler, c.Scope())
}),
)
require.NoError(t, f.Handle(http.MethodGet, "/foo", func(c Context) {

_, err := f.Handle(http.MethodGet, "/foo", func(c Context) {
assert.Equal(t, RouteHandler, c.Scope())
}))
})
require.NoError(t, err)

cases := []struct {
name string
Expand Down
Loading

0 comments on commit 1d5f706

Please sign in to comment.