Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
github.com/ahuigo/gocache-decorator
This gofnext provides the following functions extended(go>=1.21).
Cache decorators(concurrent safe): Similar to Python's functools.cache
and functools.lru_cache
.
In addition to memory caching, it also supports Redis caching and custom caching.
function | decorator |
---|---|
func f() res | gofnext.CacheFn0(f) |
func f(a) res | gofnext.CacheFn1(f) |
func f(a,b) res | gofnext.CacheFn2(f) |
func f() (res,err) | gofnext.CacheFn0Err(f) |
func f(a) (res,err) | gofnext.CacheFn1Err(f) |
func f(a,b) (res,err) | gofnext.CacheFn2Err(f) |
func f() (res,err) | gofnext.CacheFn0Err(f, &gofnext.Config{TTL: time.Hour}) // memory cache with ttl |
func f() (res) | gofnext.CacheFn0(f, &gofnext.Config{CacheMap: gofnext.NewCacheLru(9999)}) // Maxsize of cache is 9999 |
func f() (res) | gofnext.CacheFn0(f, &gofnext.Config{CacheMap: gofnext.NewCacheRedis("cacheKey")}) // Warning: redis's marshaling may result in data loss |
Refer to: examples
Refer to: decorator fib example
package main
import "fmt"
import "github.com/ahuigo/gofnext"
func main() {
var fib func(int) int
fib = func(x int) int {
fmt.Printf("call arg:%d\n", x)
if x <= 1 {
return x
} else {
return fib(x-1) + fib(x-2)
}
}
fib = gofnext.CacheFn1(fib)
fmt.Println(fib(5))
fmt.Println(fib(6))
}
Refer to: decorator example
package examples
import "github.com/ahuigo/gofnext"
func getUserAnonymouse() (UserInfo, error) {
fmt.Println("select * from db limit 1", time.Now())
time.Sleep(10 * time.Millisecond)
return UserInfo{Name: "Anonymous", Age: 9}, errors.New("db error")
}
var (
// Cacheable Function
getUserInfoFromDbWithCache = gofnext.CacheFn0Err(getUserAnonymouse)
)
func TestCacheFuncWithNoParam(t *testing.T) {
// Execute the function multi times in parallel.
times := 10
parallelCall(func() {
userinfo, err := getUserInfoFromDbWithCache()
fmt.Println(userinfo, err)
}, times)
}
Refer to: decorator example
func getUserNoError(age int) (UserInfo) {
time.Sleep(10 * time.Millisecond)
return UserInfo{Name: "Alex", Age: age}
}
var (
// Cacheable Function with 1 param and no error
getUserInfoFromDbNil= gofnext.CacheFn1(getUserNoError)
)
func TestCacheFuncNil(t *testing.T) {
// Execute the function multi times in parallel.
times := 10
parallelCall(func() {
userinfo := getUserInfoFromDbNil(20)
fmt.Println(userinfo)
}, times)
}
Refer to: decorator example
func TestCacheFuncWith2Param(t *testing.T) {
// Original function
executeCount := 0
getUserScore := func(c context.Context, id int) (int, error) {
executeCount++
fmt.Println("select score from db where id=", id, time.Now())
time.Sleep(10 * time.Millisecond)
return 98 + id, errors.New("db error")
}
// Cacheable Function
getUserScoreFromDbWithCache := gofnext.CacheFn2Err(getUserScore, &gofnext.Config{
TTL: time.Hour,
}) // getFunc can only accept 2 parameter
// Execute the function multi times in parallel.
ctx := context.Background()
parallelCall(func() {
score, _ := getUserScoreFromDbWithCache(ctx, 1)
if score != 99 {
t.Errorf("score should be 99, but get %d", score)
}
getUserScoreFromDbWithCache(ctx, 2)
getUserScoreFromDbWithCache(ctx, 3)
}, 10)
if executeCount != 3 {
t.Errorf("executeCount should be 3, but get %d", executeCount)
}
}
Refer to: decorator example
executeCount := 0
type Stu struct {
name string
age int
gender int
}
// Original function
fn := func(name string, age, gender int) int {
executeCount++
// select score from db where name=name and age=age and gender=gender
switch name {
case "Alex":
return 10
default:
return 30
}
}
// Convert to extra parameters to a single parameter(2 prameters is ok)
fnWrap := func(arg Stu) int {
return fn(arg.name, arg.age, arg.gender)
}
// Cacheable Function
fnCachedInner := gofnext.CacheFn1(fnWrap)
fnCached := func(name string, age, gender int) int {
return fnCachedInner(Stu{name, age, gender})
}
// Execute the function multi times in parallel.
parallelCall(func() {
score := fnCached("Alex", 20, 1)
if score != 10 {
t.Errorf("score should be 10, but get %d", score)
}
fnCached("Jhon", 21, 0)
fnCached("Alex", 20, 1)
}, 10)
// Test Count
if executeCount != 2 {
t.Errorf("executeCount should be 2, but get %d", executeCount)
}
Refer to: decorator lru example
executeCount := 0
maxCacheSize := 2
var getUserScore = func(more int) (int, error) {
executeCount++
return 98 + more, errors.New("db error")
}
// Cacheable Function
var getUserScoreFromDbWithLruCache = gofnext.CacheFn1Err(getUserScore, &gofnext.Config{
TTL: time.Hour,
CacheMap: gofnext.NewCacheLru(maxCacheSize),
})
Warning: Since redis needs JSON marshaling, this may result in data loss.
Refer to: decorator redis example
var (
// Cacheable Function
getUserScoreFromDbWithCache = gofnext.CacheFn1Err(getUserScore, &gofnext.Config{
TTL: time.Hour,
CacheMap: gofnext.NewCacheRedis("redis-cache-key"),
})
)
func TestRedisCacheFuncWithTTL(t *testing.T) {
// Execute the function multi times in parallel.
for i := 0; i < 10; i++ {
score, _ := getUserScoreFromDbWithCache(1)
if score != 99 {
t.Errorf("score should be 99, but get %d", score)
}
}
}
To avoid keys being too long, you can limit the length of Redis key:
cacheMap := gofnext.NewCacheRedis("redis-cache-key").SetMaxHashKeyLen(256);
Set redis config:
// method 1: by default: localhost:6379
cache := gofnext.NewCacheRedis("redis-cache-key")
// method 2: set redis addr
cache.SetRedisAddr("192.168.1.1:6379")
// method 3: set redis options
cache.SetRedisOpts(&redis.Options{
Addr: "localhost:6379",
})
// method 4: set redis universal options
cache.SetRedisUniversalOpts(&redis.UniversalOptions{
Addrs: []string{"localhost:6379"},
})
Refer to: https://github.com/ahuigo/gofnext/blob/main/cache-map-mem.go
gofnext.Config
)gofnext.Config item list:
Key | Description |
---|---|
TTL | Cache Time to Live |
CacheMap | Custom own cache |
NeedCacheIfErr | Enable cache even if there is an error |
HashKeyPointerAddr | Use Pointer Addr as key instead of its value when hashing key |
HashKeyFunc | Custom hash key function |
e.g.
gofnext.CacheFn1Err(getUserScore, &gofnext.Config{
TTL: time.Hour,
})
By default, gofnext won't cache error when there is an error.
To use the cache even when there is an error, simply add NeedCacheIfErr: true
.
Refer to: https://github.com/ahuigo/gofnext/blob/main/examples/decorator-err_test.go
gofnext.CacheFn1Err(getUserScore, &gofnext.Config{
NeedCacheIfErr: true,
})
Decorator will hash function's all parameters into hashkey. By default, if parameter is pointer, decorator will hash its real value instead of pointer address.
If you wanna hash pointer address, you should turn on HashKeyPointerAddr
:
getUserScoreFromDbWithCache := gofnext.CacheFn1Err(getUserScore, &gofnext.Config{
HashKeyPointerAddr: true,
})
In this case, you need to ensure that duplicate keys are not generated. Refer to: example
// hash key function
hashKeyFunc := func(keys ...any) []byte{
user := keys[0].(*UserInfo)
flag := keys[1].(bool)
return []byte(fmt.Sprintf("user:%d,flag:%t", user.id, flag))
}
// Cacheable Function
getUserScoreFromDbWithCache := gofnext.CacheFn2Err(getUserScore, &gofnext.Config{
HashKeyFunc: hashKeyFunc,
})
FAQs
Unknown package
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.