Skip to content

Commit

Permalink
feat: rename strategy package to clientip
Browse files Browse the repository at this point in the history
  • Loading branch information
tigerwill90 committed Nov 21, 2024
1 parent 240951c commit a0e273f
Show file tree
Hide file tree
Showing 7 changed files with 479 additions and 109 deletions.
59 changes: 34 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ of the tree, where only the modified path and its ancestors are cloned, ensuring
Multiple patches can be applied in a single transaction, with intermediate nodes cached during the process to prevent
redundant cloning.

#### Other key points
### Other key points

- Routing requests is lock-free (reading thread never block, even while writes are ongoing)
- The router always see a consistent version of the tree while routing request
Expand Down Expand Up @@ -394,7 +394,17 @@ for method, route := range it.Prefix(it.Methods(), "tmp.exemple.com/") {
txn.Commit()
````

#### Managed read-write transaction
#### Managed read-only transaction
````go
_ = f.View(func(txn *fox.Txn) error {
if txn.Has(http.MethodGet, "/foo") {
if txn.Has(http.MethodGet, "/bar") {
// do something
}
}
return nil
})
````

## Working with http.Handler
Fox itself implements the `http.Handler` interface which make easy to chain any compatible middleware before the router. Moreover, the router
Expand Down Expand Up @@ -529,40 +539,39 @@ The sub-package `github.com/tigerwill90/fox/strategy` provides a set of best pra

````go
f := fox.New(
fox.DefaultOptions(),
fox.WithClientIPStrategy(
// We are behind one or many trusted proxies that have all private-space IP addresses.
strategy.NewRightmostNonPrivate(strategy.XForwardedForKey),
),
fox.DefaultOptions(),
fox.WithClientIPStrategy(
// We are behind one or many trusted proxies that have all private-space IP addresses.
clientip.NewRightmostNonPrivate(clientip.XForwardedForKey),
),
)

f.MustHandle(http.MethodGet, "/foo/bar", func(c fox.Context) {
ipAddr, err := c.ClientIP()
if err != nil {
// If the current strategy is not able to derive the client IP, an error
// will be returned rather than falling back on an untrustworthy IP. It
// should be treated as an application issue or a misconfiguration.
panic(err)
}
fmt.Println(ipAddr.String())
ipAddr, err := c.ClientIP()
if err != nil {
// If the current strategy is not able to derive the client IP, an error
// will be returned rather than falling back on an untrustworthy IP. It
// should be treated as an application issue or a misconfiguration.
panic(err)
}
fmt.Println(ipAddr.String())
})
````

It is also possible to create a chain with multiple strategies that attempt to derive the client IP, stopping when the first one succeeds.

````go
f = fox.New(
fox.DefaultOptions(),
fox.WithClientIPStrategy(
// A common use for this is if a server is both directly connected to the
// internet and expecting a header to check.
strategy.NewChain(
strategy.NewLeftmostNonPrivate(strategy.ForwardedKey),
strategy.NewRemoteAddr(),
),
),
fox.DefaultOptions(),
fox.WithClientIPStrategy(
// A common use for this is if a server is both directly connected to the
// internet and expecting a header to check.
clientip.NewChain(
clientip.NewLeftmostNonPrivate(clientip.ForwardedKey),
clientip.NewRemoteAddr(),
),
),
)

````

Note that there is no "sane" default strategy, so calling `Context.ClientIP` without a strategy configured will return an `ErrNoClientIPStrategy`.
Expand Down
2 changes: 1 addition & 1 deletion strategy/strategy.go → clientip/clientip.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Mount of this source code is governed by a BSD Zero Clause License that can be found
// at https://github.com/realclientip/realclientip-go/blob/main/LICENSE.

package strategy
package clientip

import (
"errors"
Expand Down
2 changes: 1 addition & 1 deletion strategy/strategy_test.go → clientip/clientip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Mount of this source code is governed by a BSD Zero Clause License that can be found
// at https://github.com/realclientip/realclientip-go/blob/main/LICENSE.

package strategy
package clientip

import (
"errors"
Expand Down
2 changes: 1 addition & 1 deletion strategy/options.go → clientip/options.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package strategy
package clientip

import "net"

Expand Down
105 changes: 74 additions & 31 deletions fox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,35 +494,6 @@ func BenchmarkStaticAll(b *testing.B) {
benchRoutes(b, r, staticRoutes)
}

// BenchmarkInsertStatic-16 3975 318519 ns/op 335701 B/op 4506 allocs/op
func BenchmarkInsertStatic(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()

for range b.N {
f := New()
for _, route := range staticRoutes {
f.Handle(route.method, route.path, emptyHandler)
}
}
}

func BenchmarkInsertStaticTx(b *testing.B) {

b.ResetTimer()
b.ReportAllocs()

for range b.N {
f := New()
txn := f.Txn(true)
for _, route := range staticRoutes {
txn.Handle(route.method, route.path, emptyHandler)
}
txn.Commit()
}

}

func BenchmarkGithubParamsAll(b *testing.B) {
r := New()
for _, route := range githubAPI {
Expand Down Expand Up @@ -5924,21 +5895,93 @@ func ExampleRouter_Lookup() {
}

// This example demonstrates how to do a reverse lookup on the tree.
func ExampleTree_Reverse() {
func ExampleRouter_Reverse() {
f := New()
f.MustHandle(http.MethodGet, "exemple.com/hello/{name}", emptyHandler)
route, _ := f.Reverse(http.MethodGet, "exemple.com", "/hello/fox")
fmt.Println(route.Pattern()) // /hello/{name}
}

// This example demonstrates how to check if a given route is registered in the tree.
func ExampleTree_Has() {
func ExampleRouter_Has() {
f := New()
f.MustHandle(http.MethodGet, "/hello/{name}", emptyHandler)
exist := f.Has(http.MethodGet, "/hello/{name}")
fmt.Println(exist) // true
}

// This example demonstrate how to create a managed read-write transaction.
func ExampleRouter_Updates() {
f := New()

// Updates executes a function within the context of a read-write managed transaction. If no error is returned
// from the function then the transaction is committed. If an error is returned then the entire transaction is
// aborted.
if err := f.Updates(func(txn *Txn) error {
if _, err := txn.Handle(http.MethodGet, "exemple.com/hello/{name}", func(c Context) {
_ = c.String(http.StatusOK, "hello %s", c.Param("name"))
}); err != nil {
return err
}

// Iter returns a collection of range iterators for traversing registered routes.
it := txn.Iter()
// When Iter() is called on a write transaction, it creates a point-in-time snapshot of the transaction state.
// It means that writing on the current transaction while iterating is allowed, but the mutation will not be
// observed in the result returned by Prefix (or any other iterator).
for method, route := range it.Prefix(it.Methods(), "tmp.exemple.com/") {
if err := f.Delete(method, route.Pattern()); err != nil {
return err
}
}
return nil
}); err != nil {
log.Printf("transaction aborted: %s", err)
}
}

// This example demonstrate how to create an unmanaged read-write transaction.
func ExampleRouter_Txn() {
f := New()

// Txn create an unmanaged read-write or read-only transaction.
txn := f.Txn(true)
defer txn.Abort()

if _, err := txn.Handle(http.MethodGet, "exemple.com/hello/{name}", func(c Context) {
_ = c.String(http.StatusOK, "hello %s", c.Param("name"))
}); err != nil {
log.Printf("error inserting route: %s", err)
return
}

// Iter returns a collection of range iterators for traversing registered routes.
it := txn.Iter()
// When Iter() is called on a write transaction, it creates a point-in-time snapshot of the transaction state.
// It means that writing on the current transaction while iterating is allowed, but the mutation will not be
// observed in the result returned by Prefix (or any other iterator).
for method, route := range it.Prefix(it.Methods(), "tmp.exemple.com/") {
if err := f.Delete(method, route.Pattern()); err != nil {
log.Printf("error deleting route: %s", err)
return
}
}
// Finalize the transaction
txn.Commit()
}

func ExampleRouter_View() {
f := New()

// View executes a function within the context of a read-only managed transaction.
_ = f.View(func(txn *Txn) error {
if txn.Has(http.MethodGet, "/foo") && txn.Has(http.MethodGet, "/bar") {
// Do something
}
return nil
})
}

func onlyError[T any](_ T, err error) error {
return err
}
Loading

0 comments on commit a0e273f

Please sign in to comment.