Skip to content

Commit

Permalink
Add cache
Browse files Browse the repository at this point in the history
  • Loading branch information
Feralthedogg committed Jan 17, 2025
1 parent 87b3a24 commit 3a58ae7
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 19 deletions.
23 changes: 12 additions & 11 deletions internal/middleware/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ type TokenBucket struct {
mutex sync.Mutex
}

var tokenBuckets = struct {
sync.Mutex
buckets map[string]*TokenBucket
}{buckets: make(map[string]*TokenBucket)}

func NewTokenBucket(capacity int, refillRate float64) *TokenBucket {
return &TokenBucket{
capacity: capacity,
Expand Down Expand Up @@ -52,16 +47,22 @@ func (tb *TokenBucket) AllowRequest() bool {
return false
}

var tokenBuckets sync.Map

func getOrCreateTokenBucket(ip string, capacity int, refillRate float64) *TokenBucket {
if v, ok := tokenBuckets.Load(ip); ok {
return v.(*TokenBucket)
}
newBucket := NewTokenBucket(capacity, refillRate)
actual, _ := tokenBuckets.LoadOrStore(ip, newBucket)
return actual.(*TokenBucket)
}

func TokenBucketMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
ip := c.ClientIP()

tokenBuckets.Lock()
if _, exists := tokenBuckets.buckets[ip]; !exists {
tokenBuckets.buckets[ip] = NewTokenBucket(100, 100.0/60.0) // 1분당 100개 (초당 약 1.67개 리필)
}
tb := tokenBuckets.buckets[ip]
tokenBuckets.Unlock()
tb := getOrCreateTokenBucket(ip, 100, 100.0/60.0) // 1분당 100개 (초당 약 1.67개 리필)

if !tb.AllowRequest() {
c.JSON(http.StatusTooManyRequests, gin.H{"error": "Too many requests"})
Expand Down
34 changes: 26 additions & 8 deletions internal/router/routes.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// package router
// internal/router/routes.go
package router

import (
Expand All @@ -24,13 +24,15 @@ func SetupRouter(system *actor.ActorSystem, korcenPID *actor.PID) *gin.Engine {
{
APIGroup.POST("/korcen", func(c *gin.Context) {
var header check.Header
isXML := false

switch c.ContentType() {
case "text/xml", "application/xml":
if err := c.ShouldBindXML(&header); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid XML request"})
c.XML(http.StatusBadRequest, gin.H{"error": "Invalid XML request"})
return
}
isXML = true
default:
if err := c.ShouldBindJSON(&header); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON request"})
Expand All @@ -39,33 +41,49 @@ func SetupRouter(system *actor.ActorSystem, korcenPID *actor.PID) *gin.Engine {
}

if strings.TrimSpace(header.Input) == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request: empty input"})
if isXML {
c.XML(http.StatusBadRequest, gin.H{"error": "Invalid request: empty input"})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request: empty input"})
}
return
}

msg := &check.KorcenRequest{Header: &header}

future := system.Root.RequestFuture(korcenPID, msg, 5*time.Second)

result, err := future.Result()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
if isXML {
c.XML(http.StatusInternalServerError, gin.H{"error": err.Error()})
} else {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
return
}

korcenResp, ok := result.(*check.KorcenResponse)
if !ok {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid actor response"})
if isXML {
c.XML(http.StatusInternalServerError, gin.H{"error": "Invalid actor response"})
} else {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid actor response"})
}
return
}

if korcenResp.Err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": korcenResp.Err.Error()})
if isXML {
c.XML(http.StatusBadRequest, gin.H{"error": korcenResp.Err.Error()})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": korcenResp.Err.Error()})
}
return
}

response := korcenResp.Respond

if c.GetHeader("Accept") == "application/xml" {
if isXML {
c.XML(http.StatusOK, response)
} else {
c.JSON(http.StatusOK, response)
Expand Down
105 changes: 105 additions & 0 deletions pkg/check/korcen.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
package check

import (
"container/list"
"encoding/xml"
"errors"
"sync"

"github.com/asynkron/protoactor-go/actor"
"github.com/fluffy-melli/korcen-go"
Expand All @@ -25,8 +27,107 @@ type Respond struct {
NewString string `json:"output" xml:"output"`
}

type KorcenResult = korcen.CheckInfo

type Node struct {
key string
value *KorcenResult
elem *list.Element
}

var nodePool = sync.Pool{
New: func() interface{} {
return &Node{}
},
}

func newNode(key string, value *KorcenResult) *Node {
n := nodePool.Get().(*Node)
n.key = key
n.value = value
n.elem = &list.Element{Value: n}
return n
}

func freeNode(n *Node) {
n.key = ""
n.value = nil
n.elem = nil
nodePool.Put(n)
}

type LRUCache struct {
mutex sync.Mutex
capacity int
ll *list.List
items map[string]*list.Element
}

func NewLRUCache(capacity int) *LRUCache {
return &LRUCache{
capacity: capacity,
ll: list.New(),
items: make(map[string]*list.Element),
}
}

func (c *LRUCache) Get(key string) (*KorcenResult, bool) {
c.mutex.Lock()
defer c.mutex.Unlock()

if elem, ok := c.items[key]; ok {
c.ll.MoveToFront(elem)
node := elem.Value.(*Node)
return node.value, true
}
return nil, false
}

func (c *LRUCache) Set(key string, value *KorcenResult) {
c.mutex.Lock()
defer c.mutex.Unlock()

if elem, ok := c.items[key]; ok {
c.ll.MoveToFront(elem)
node := elem.Value.(*Node)
node.value = value
return
}

n := newNode(key, value)
elem := c.ll.PushFront(n)
n.elem = elem
c.items[key] = elem

if c.ll.Len() > c.capacity {
old := c.ll.Back()
if old != nil {
c.removeElement(old)
}
}
}

func (c *LRUCache) removeElement(elem *list.Element) {
c.ll.Remove(elem)
node := elem.Value.(*Node)
delete(c.items, node.key)
freeNode(node)
}

var globalLRU = NewLRUCache(1000)

func Korcen(header *Header) *Respond {
if info, ok := globalLRU.Get(header.Input); ok {
return buildRespond(header, info)
}

info := korcen.Check(header.Input)
globalLRU.Set(header.Input, info)

return buildRespond(header, info)
}

func buildRespond(header *Header, info *KorcenResult) *Respond {
if !info.Detect {
return &Respond{
Detect: false,
Expand All @@ -48,6 +149,10 @@ func Korcen(header *Header) *Respond {
}

func formatMessage(text string, start, end int, prefix, suffix string) string {
if start < 0 || end > len(text) || start > end {
return text
}

switch {
case prefix != "" && suffix != "":
return text[:start] + prefix + text[start:end] + suffix + text[end:]
Expand Down

0 comments on commit 3a58ae7

Please sign in to comment.