ã¯ããã« Webã¢ããªã±ãŒã·ã§ã³ã«ãããã¬ãŒããªãããããµãŒããããã¬ãŒã«ãŒããªãã©ã€ã®åœ¹å² ãªãã©ã€ ãµãŒããããã¬ãŒã«ãŒ ã¬ãŒããªããã ã¬ãŒããªãããããµãŒããããã¬ãŒã«ãŒããªãã©ã€ã®å®è£
ãµã³ãã«ã¢ããªã±ãŒã·ã§ã³ã®å®è£
ãªãã©ã€ããµãŒããããã¬ãŒã«ãŒãã¬ãŒããªãããã远å ãŸãšã 幎ã«1åºŠã®æè¡ã€ãã³ããRAKUS Tech Conferenceããéå¬ããŸãïŒïŒ ã¯ããã« ããã«ã¡ã¯ïŒãšã³ãžãã¢ïŒå¹Žç®ã®TKDSã§ãã ä»åã¯ãã¬ãŒããªãããã»ãµãŒããããã¬ãŒã«ãŒã»ãªãã©ã€ã«ã€ããŠèª¿ã¹ãå
容ã玹ä»ããã©ã€ãã©ãªã䜿ã£ãŠGoã§å®è£
ããŠã¿ãŸãã Webã¢ããªã±ãŒã·ã§ã³ã«ãããã¬ãŒããªãããããµãŒããããã¬ãŒã«ãŒããªãã©ã€ã®åœ¹å² ãªãã©ã€ ãªã¯ ãšã¹ ãã倱æããå Žåã«å詊è¡ããŸãã ãªãã©ã€ã¯ãäžæçãªé害ã«å¯ŸããŠå¹æãçºæ®ããŸãã ãããã¯ãŒã¯ã®ç¬æããµãŒãã¹ã®äžæçãªéè² è·ãªã©ãããçŽãã°è§£æ±ºã§ããããªåé¡ã«ãã倱æãã·ã¹ãã å
ã®ãªãã©ã€ã§ã«ããŒããããšã§ããŠãŒã¶ãŒããã¯ç¹ã«åé¡ãªãèŠããç¶æ
ãç¶æãããŸãŸåŠç倱æã® ãªã«ã㪠ãŒãã§ããŸãã ãµãŒããããã¬ãŒã«ãŒ ãµãŒãã¹ãç£èŠããèšå®ããæ¡ä»¶ãã¿ãããããšããªãŒãã³ïŒãªã¯ ãšã¹ ããåãä»ããªãïŒãç¶æ
ã«ãªãããã°ãããããšãããŒããªãŒãã³ïŒäžéšã ãåãå
¥ãïŒãç¶æ
ã«ãªããã·ã¹ãã ãå埩ãããã©ããäžéšã®ãªã¯ ãšã¹ ããéããŠç¢ºèªããŸãã å埩ããããšã確èªã§ããã°ãã¯ããŒãºïŒéåžžç¶æ
ïŒãã«ãã ããªãããªãŒãã³ãã«ãªããŸãã ãµãŒããããã¬ãŒã«ãŒã¯ã埩æ§ã«æéã®ãããé害ã«å¯ŸããŠå¹æãçºæ®ããŸãã ãã®ãŸãŸãªãã©ã€ãç¶ããŠãå埩ããèŠèŸŒã¿ãäœãããããã¯å埩ãŸã§æéããããå ŽåãäžæŠã¢ã¯ã»ã¹ã鮿ããããšã§é害ãèµ·ãããµãŒãã¹ãžã®ç¡é§ãªã¢ã¯ã»ã¹ãç¡é§ãªãªãœãŒã¹ã®æ¶è²»ãé¿ããããšãã§ãããµãŒãã¹ãå埩ãããŸã§ã®æéã皌ããŸãã ã¬ãŒããªããã èšå®ãã以äžã®ãªã¯ ãšã¹ ããæ¥ããšãã«ãäžæçã«ã¢ã¯ã»ã¹ãå¶éããŸãã ã¬ãŒããªãããã䜿ããšãµãŒãã¹ã«éè² è·ããããããšãé²ãããšãã§ããŸãã ãŸãã DoSæ»æ ãªã©ããã¢ããªã±ãŒã·ã§ã³ãå®ãããšãã§ããŸãã ãããã®æ©èœãæ¡çšããããšã§ã·ã¹ãã ã®èé害æ§ãå®å®æ§ãããã©ãŒãã³ã¹ãåäžãããããŸãã ã¬ãŒããªãããããµãŒããããã¬ãŒã«ãŒããªãã©ã€ã®å®è£
ãã®ç¯ã§ã¯ãGoã§å®éã«ã¬ãŒããªãããããµãŒããããã¬ãŒã«ãŒããªãã©ã€ã®æ©èœãå®è£
ããŠãããŸãã ãµã³ãã«ã¢ããªã±ãŒã·ã§ã³ã®å®è£
ä»åã¯ã©ã€ãã©ãªã掻çšããŠå®è£
ããŠãããŸãã èªåã§ïŒããå®è£
ããªãçç±ã¯ããã€ããããŸãã å€ãã®äººãå©çšããŠããã©ã€ãã©ãªã¯ãã°ãçºèŠããããããèªåã§å®è£
ããããä¿¡é Œæ§ãé«ã æ¡çšæç¹ã§ã®ãã¹ãã ã©ã¯ ãã£ã¹ãé©çšã§ãã è€éãªåäœã®é©åãªåŠçãèªèº«ã§èããå¿
èŠããªã ãã®ãããªèгç¹ããã©ã€ãã©ãªã䜿ããŸãã 以äžããµã³ãã«ã¢ããªã±ãŒã·ã§ã³ã§ãã ãªã¯ ãšã¹ ããã©ã¡ãŒã¿ã«æå®ããURLã«ã¢ã¯ã»ã¹ããååšããå Žåã¯ã«ãŠã³ããïŒãã©ã¹ãããªãå Žåã¯ãšã©ãŒãè¿ããŸãã package main import ( "context" "errors" "fmt" "net/http" "os" "os/signal" "syscall" "time" "github.com/go-redis/redis/v8" "golang.org/x/exp/slog" ) var ( rdb *redis.Client ) func init() { rdb = redis.NewClient(&redis.Options{ Addr: "redis:6379" , // RedisãµãŒããŒã®ã¢ãã¬ã¹ Password: "" , // ãã¹ã¯ãŒããªã DB: 0 , // ããã©ã«ãã®DB }) } func hostExists(url string ) bool { resp, err := http.Get(url) if err != nil { return false } defer resp.Body.Close() return resp.StatusCode == http.StatusOK } func handler(w http.ResponseWriter, r *http.Request) { host := r.URL.Query().Get( "host" ) if host == "" { http.Error(w, "Host parameter is missing" , http.StatusBadRequest) return } if hostExists(host) { count, err := rdb.Incr(context.Background(), "counter" ).Result() if err != nil { http.Error(w, "Could not increment counter" , http.StatusInternalServerError) return } fmt.Fprintf(w, "Counter: %d \n " , count) } else { http.Error(w, "Host not found" , http.StatusNotFound) } } func main() { addr := ":8080" handler := http.HandlerFunc(handler) server := &http.Server{Addr: addr, Handler: handler} idleConnsClosed := make ( chan struct {}) go func () { c := make ( chan os.Signal, 1 ) signal.Notify(c, os.Interrupt, syscall.SIGTERM) // SIGINT, SIGTERM ãæ€ç¥ãã <-c ctx, cancel := context.WithTimeout(context.Background(), 10 *time.Second) defer cancel() slog.Info( "Server is shutting down..." ) if err := server.Shutdown(ctx); err != nil { if errors.Is(err, context.DeadlineExceeded) { slog.Warn( "HTTP server Shutdown: timeout" ) } else { slog.Error( "HTTP server Shutdown: " , err) } close (idleConnsClosed) return } slog.Info( "Server is shut down" ) close (idleConnsClosed) }() slog.Info( "Server is running on " , addr) if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { slog.Error( "HTTP server ListenAndServe: " , err) } <-idleConnsClosed } docker compose up --build ã§èµ·åããŸãã æåãããªã¯ ãšã¹ ããšå€±æãããªã¯ ãšã¹ ããéã£ãŠåäœç¢ºèªãããŠã¿ãŸãããã æåïŒ curl "http://localhost:8080?host=http://example.com" 倱æïŒ curl "http://localhost:8080?host=http://.com" äžã®ç»åã®ããã«åäœç¢ºèªãã§ããŸãã ãªãã©ã€ããµãŒããããã¬ãŒã«ãŒãã¬ãŒããªãããã远å ãµã³ãã«ã«ãªãã©ã€ããµãŒããããã¬ãŒã«ãŒãã¬ãŒããªãããã远å ããŠãããŸãã ã³ãŒãã¯ä»¥äžã®éãã§ãã 倿Žãå
¥ã£ããinitãšhostExistsãhandlerã ãèšèŒããŸãã 詳现㯠Github ãã¿ãŠãã ããã func init() { // RedisãµãŒããŒã®ã¢ãã¬ã¹ãèšå® rdb = redis.NewClient(&redis.Options{ Addr: "redis:6379" , // RedisãµãŒããŒã®ã¢ãã¬ã¹ Password: "" , // ãã¹ã¯ãŒããªã DB: 0 , // ããã©ã«ãã®DB }) cb = gobreaker.NewCircuitBreaker(gobreaker.Settings{ Name: "Redis" , Timeout: 30 * time.Second, ReadyToTrip: func (counts gobreaker.Counts) bool { return counts.ConsecutiveFailures >= 3 // 3åé£ç¶å€±æã§ããªãã }, OnStateChange: func (name string , from gobreaker.State, to gobreaker.State) { log.Println( "Circuit breaker state changed" , "name" , name, "from" , from.String(), "to" , to.String()) // ãµãŒããããã¬ãŒã«ãŒã®ç¶æ
ãå€ãããã³ã«ãã°åºå }, }) rateLimiter = ratelimit.New( 1 , ratelimit.Per( 10 *time.Second)) // 1ãªã¯ãšã¹ã/ç§ client = retryablehttp.NewClient() client.RetryMax = 3 // æå€§3åãªãã©ã€ } func hostExists(url string ) bool { req, err := retryablehttp.NewRequest( "GET" , url, nil ) if err != nil { return false } resp, err := client.Do(req) if err != nil { return false } defer resp.Body.Close() return resp.StatusCode == http.StatusOK } func handler(w http.ResponseWriter, r *http.Request) { // ã¬ãŒããªãããã®é©çš rateLimiter.Take() host := r.URL.Query().Get( "host" ) if host == "" { http.Error(w, "Host parameter is missing" , http.StatusBadRequest) return } if hostExists(host) { // ãµãŒããããã¬ãŒã«ãŒã®é©çš body, err := cb.Execute( func () ( interface {}, error ) { count, err := rdb.Incr(context.Background(), "counter" ).Result() if err != nil { return nil , err } return fmt.Sprintf( "Counter: %d \n " , count), nil }) if err != nil { http.Error(w, "Could not increment counter" , http.StatusInternalServerError) return } fmt.Fprintf(w, body.( string )) } else { http.Error(w, "Host not found" , http.StatusNotFound) } } ã§ã¯åæ§ã«ãèµ·åããŠåäœç¢ºèªããŠãããŸãã docker compose down -v ããŠãããã«ããŠãããŸãããã docker compose build --no-cache ã§ãã«ããã docker compose up ã§èµ·åããŸãã ãªãã©ã€ãèµ·ãããªã¯ ãšã¹ ã ãªãã©ã€ã¯ãªã¯ ãšã¹ ãã倱æããå Žåã«èµ·ãããŸãã curl "http://localhost:8080?host=http://.com" ãå®è¡ãããšå
çšãšéããretryã®ãã°ããµãŒããŒåŽã«åºåãããŠããããšãããããŸãã ããã§ãªãã©ã€ãæ©èœããŠããããšã確èªã§ããŸããã ã¬ãŒããªããããèµ·ãããªã¯ ãšã¹ ã ã¬ãŒããªãããã®éšåã以äžã®ããã«å€æŽããŸãããã ãã®ã©ã€ãã©ãªã§ã¯ãã¬ãŒããªããããè¶
ãããšæå®ããæéåŠçãåŸ
æ©ããããã«ãªã£ãŠããŸãã 10ç§éã«ïŒåã®ãªã¯ ãšã¹ ãã«å¶éããŠãããŸãã ã¬ãŒããªããããããããšããªã¯ ãšã¹ ãã«10ç§ããã£ãŠããããšãããããŸãã ããã§ãã¬ãŒããªããããæ©èœããŠããããšã確èªã§ããŸããã ãµãŒããããã¬ãŒã«ãŒãèµ·åãããªã¯ ãšã¹ ã ä»åã®ãµãŒããããã¬ãŒã«ãŒã¯ïŒåé£ç¶å€±æã§ã30ç§é ã¿ã€ã ã¢ãŠã ããããã«ããŠããŸãã ãŸããèµ·åããŠããRedisãæ¢ããŸãã ããã§Redisãžã®ã¢ã¯ã»ã¹ã倱æããããã«ãªããŸããã ãµãŒããããã¬ãŒã«ãŒã®å€åããã°ã«åºåãããŠããŸãã äžå倱æããããšã«ãµãŒããããã¬ãŒã«ãŒã®å€åãèµ·ããŠããŸãã ããã§ãµãŒããããã¬ãŒã«ãŒã®åäœç¢ºèªãã§ããŸããïŒ ãŸãšã ä»åã¯ã¬ãŒããªãããã»ãµãŒããããã¬ãŒã«ãŒã»ãªãã©ã€ã«ã€ããŠèª¿ã¹ãå
容ã玹ä»ããã©ã€ãã©ãªã䜿ã£ãŠGoã§å®è£
ããŸããã ã©ã€ãã©ãªã䜿ãã°æ¯èŒçç°¡åã«å®è£
ã§ããã®ã§ããã²å®è£
ããŠã¿ãŠäžããã ãã€ã¯ããµãŒãã¹ã«ã¯å¿
é ã®æ©èœã ãšæãã®ã§ãèšæ¶ã«ãšã©ããŠçœ®ããããšæããŸãã ãããŸã§èªãã§ããã ãããããšãããããŸããïŒ å¹Žã«1åºŠã®æè¡ã€ãã³ããRAKUS Tech Conferenceããéå¬ããŸãïŒïŒ ä»å¹Žã ã©ã¯ ã¹éçºæ¬éšäž»å¬ã®æè¡ã«ã³ãã¡ã¬ã³ã¹ããRAKUS Tech Conference 2024ããéå¬ããŸãïŒ ãRAKUS Tech Conferenceãã¯ã SaaS éçºã«ãããåãçµã¿ãç¥èŠã玹ä»ããã ã©ã¯ ã¹éçºæ¬éšäž»å¬ã®æè¡ã«ã³ãã¡ã¬ã³ã¹ã§ãã ã©ã¯ ã¹éçºæ¬éšã®ããã·ã§ã³ã«èŸŒããæ³ãããšã³ãžãã¢/ãã¶ã€ããŒãçã®å£°ã§ãå±ãããŸãã çããŸã®ãåå ããåŸ
ã¡ããŠãããŸãïŒ techcon.rakus.co.jp