diff --git a/cmd/alice/flags.go b/cmd/alice/flags.go index e7093e5..1ce29c5 100644 --- a/cmd/alice/flags.go +++ b/cmd/alice/flags.go @@ -37,6 +37,7 @@ func flagsInitialization() []cli.Flag { Category: "Syslog settings", Usage: "optional setting; more information in syslog RFC", Value: "", + Hidden: true, }, // fiber-server settings @@ -63,6 +64,7 @@ func flagsInitialization() []cli.Flag { if enabled, the application will need to be ran through a shell because prefork mode sets environment variables; EXPERIMENTAL! USE CAREFULLY!`, + Hidden: true, DisableDefaultText: true, }, &cli.DurationFlag{ @@ -103,28 +105,33 @@ func flagsInitialization() []cli.Flag { &cli.BoolFlag{ Name: "limiter-enable", Category: "Limiter settings", + Hidden: true, DisableDefaultText: true, }, &cli.BoolFlag{ Name: "limiter-use-bbolt", Category: "Limiter settings", Usage: "use bbolt key\value file database instead of memory database", + Hidden: true, DisableDefaultText: true, }, &cli.BoolFlag{ Name: "limiter-bbolt-reset", Category: "Limiter settings", Usage: "if bbolt used as storage, reset all limited IPs on startup", + Hidden: true, DisableDefaultText: true, }, &cli.IntFlag{ Name: "limiter-max-req", Category: "Limiter settings", + Hidden: true, Value: 200, }, &cli.DurationFlag{ Name: "limiter-records-duration", Category: "Limiter settings", + Hidden: true, Value: 5 * time.Minute, }, @@ -262,6 +269,7 @@ func flagsInitialization() []cli.Flag { Category: "GeoIP", Usage: `sha256 helps to check database contents of the mmdb database and avoid unnecessary requests to MaxMind CDN`, + DisableDefaultText: true, }, &cli.DurationFlag{ Name: "geoip-update-frequency", @@ -283,11 +291,77 @@ func flagsInitialization() []cli.Flag { DisableDefaultText: true, }, + // anilibria release randomizer + &cli.BoolFlag{ + Name: "randomizer-enable", + Category: "Release randomizer", + Usage: `alice has its own function for randomizing releases; it optimizes random_release (apiv1) + and public/random.php (www site), ensuring the high performance of these methods; + if disabled, all reuqests will be cached in shared cache pool with another methods`, + }, + &cli.StringFlag{ + Name: "randomizer-redis-host", + Category: "Release randomizer", + Value: "127.0.0.1:6279", + }, + &cli.StringFlag{ + Name: "randomizer-redis-password", + Category: "Release randomizer", + Value: "", + }, + &cli.IntFlag{ + Name: "randomizer-redis-database", + Category: "Release randomizer", + Value: 0, + }, + &cli.StringFlag{ + Name: "randomizer-releaseskey", + Category: "Release randomizer", + Usage: "the feature of the legacy", + Value: "apiInfo", + }, + &cli.DurationFlag{ + Name: "randomizer-update-frequency", + Category: "Release randomizer", + Value: 1 * time.Minute, + }, + &cli.DurationFlag{ + Name: "randomizer-update-frequency-onerror", + Category: "Release randomizer", + Value: 1 * time.Minute, + Hidden: true, + }, + &cli.IntFlag{ + Name: "redis-client-maxretries", + Category: "Release randomizer", + Hidden: true, + Value: 3, + }, + &cli.DurationFlag{ + Name: "redis-client-dialtimeout", + Category: "Release randomizer", + Hidden: true, + Value: 5 * time.Second, + }, + &cli.DurationFlag{ + Name: "redis-client-readtimeout", + Category: "Release randomizer", + Hidden: true, + Value: 3 * time.Second, + }, + &cli.DurationFlag{ + Name: "redis-client-writetimeout", + Category: "Release randomizer", + Hidden: true, + Value: 3 * time.Second, + }, + // custom settings &cli.BoolFlag{ Name: "anilibrix-cmpb-mode", Category: "Feature flags", Usage: "avoiding 'Cannot POST //public/api/index.php' errors with req rewrite", + Hidden: true, DisableDefaultText: true, }, } diff --git a/go.mod b/go.mod index aaad28b..a78ef9a 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/klauspost/compress v1.17.9 github.com/mailru/easyjson v0.7.7 github.com/oschwald/maxminddb-golang v1.13.1 + github.com/redis/go-redis/v9 v9.6.1 github.com/rs/zerolog v1.33.0 github.com/urfave/cli/v2 v2.27.3 github.com/valyala/bytebufferpool v1.0.0 @@ -18,7 +19,9 @@ require ( require ( github.com/andybalholm/brotli v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/gofiber/utils/v2 v2.0.0-beta.3 // indirect github.com/google/uuid v1.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect diff --git a/go.sum b/go.sum index 0a21d27..e8df57f 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,16 @@ github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3b github.com/allegro/bigcache/v3 v3.1.0/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= @@ -37,6 +43,8 @@ github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= +github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= diff --git a/internal/anilibria/anilibria.go b/internal/anilibria/anilibria.go new file mode 100644 index 0000000..7c0358d --- /dev/null +++ b/internal/anilibria/anilibria.go @@ -0,0 +1,16 @@ +package anilibria + +type ( + Releases map[string]*Release + Release struct { + Id uint + Code string + BlockedInfo *ReleaseBlockedInfo `json:"blockedInfo"` + } + ReleaseBlockedInfo struct { + Blocked bool + Reason string + IsBlockedInGeo []string `json:"is_blocked_in_geo"` + IsBlockedByCopyrights bool `json:"is_blocked_by_copyrights"` + } +) diff --git a/internal/anilibria/randomizer.go b/internal/anilibria/randomizer.go new file mode 100644 index 0000000..c047e99 --- /dev/null +++ b/internal/anilibria/randomizer.go @@ -0,0 +1,237 @@ +package anilibria + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math/rand" + "strconv" + "sync" + "time" + + "github.com/anilibria/alice/internal/utils" + futils "github.com/gofiber/fiber/v2/utils" + "github.com/redis/go-redis/v9" + "github.com/rs/zerolog" + "github.com/urfave/cli/v2" +) + +type Randomizer struct { + log *zerolog.Logger + done func() <-chan struct{} + abort context.CancelFunc + + rctx context.Context + rclient *redis.Client + + releasesKey string + relUpdFreq time.Duration + relUpdFreqErr time.Duration + + mu sync.RWMutex + releases []string +} + +func New(c context.Context) *Randomizer { + cli := c.Value(utils.CKCliCtx).(*cli.Context) + + r := &Randomizer{ + done: c.Done, + log: c.Value(utils.CKLogger).(*zerolog.Logger), + abort: c.Value(utils.CKAbortFunc).(context.CancelFunc), + + rctx: context.Background(), + rclient: redis.NewClient(&redis.Options{ + Addr: cli.String("randomizer-redis-host"), + Password: cli.String("randomizer-redis-password"), + DB: cli.Int("randomizer-redis-database"), + + ClientName: fmt.Sprintf("%s/%s", cli.App.Name, cli.App.Version), + + MaxRetries: cli.Int("redis-client-maxretries"), + DialTimeout: cli.Duration("redis-client-dialtimeout"), + ReadTimeout: cli.Duration("redis-client-readtimeout"), + WriteTimeout: cli.Duration("redis-client-writetimeout"), + }), + + relUpdFreq: cli.Duration("randomizer-update-frequency"), + relUpdFreqErr: cli.Duration("randomizer-update-frequency-onerror"), + + releases: make([]string, 0), + releasesKey: cli.String("randomizer-releaseskey"), + } + + return r +} + +func (m *Randomizer) Bootstrap() { + m.loop() + m.destroy() +} + +func (m *Randomizer) Randomize() string { + return m.randomRelease() +} + +// + +func (m *Randomizer) loop() { + m.log.Debug().Msg("initiate randomizer release update loop...") + defer m.log.Debug().Msg("randomizer release update loop has been closed") + + // initial parsing + releases, e := m.lookupReleases() + if e != nil { + m.log.Error().Msg(e.Error()) + m.abort() + return + } + m.rotateReleases(releases) + + update := time.NewTimer(m.relUpdFreq) + +LOOP: + for { + select { + case <-m.done(): + m.log.Info().Msg("internal abort() has been caught; initiate application closing...") + break LOOP + case <-update.C: + update.Stop() + + var releases []string + if releases, e = m.lookupReleases(); e != nil { + m.log.Error().Msg("could not updated releases for randomizer - " + e.Error()) + update.Reset(m.relUpdFreqErr) + continue + } + + m.rotateReleases(releases) + update.Reset(m.relUpdFreq) + } + } +} + +func (m *Randomizer) destroy() { + if e := m.rclient.Close(); e != nil { + m.log.Error().Msg("could not properly close http client - " + e.Error()) + } +} + +func (m *Randomizer) peekReleaseKeyChunks() (_ int, e error) { + var res string + if res, e = m.rclient.Get(m.rctx, m.releasesKey).Result(); e == redis.Nil { + e = errors.New("no such release key in redis; is it correct - " + m.releasesKey) + return + } else if e != nil { + return + } else if res == "" { + e = errors.New("redis client respond with an empty string; is release key is alive?") + return + } + + return strconv.Atoi(res) +} + +func (m *Randomizer) lookupReleases() (_ []string, e error) { + var chunks int + if chunks, e = m.peekReleaseKeyChunks(); e != nil { + return + } else if chunks == 0 { + e = errors.New("invalid chunks count was responded by redis client or converted by golang") + return + } + m.log.Trace().Msgf("release key says about %d chunks", chunks) + m.log.Info().Msgf("staring release parsing from redis with %d chunks", chunks) + + // avoid mass allocs + started := time.Now() + releases := make([]string, 0, len(m.releases)) + + var res string + var errs []string + var total, banned int + + for i := 0; i < chunks; i++ { + select { + case <-m.done(): + e = errors.New("chunk parsing has been interrupted by global abort()") + return + default: + m.log.Trace().Msgf("parsing chunk %d/%d...", i, chunks) + } + + if res, e = m.rclient.Get(m.rctx, m.releasesKey+strconv.Itoa(i)).Result(); e == redis.Nil { + e = fmt.Errorf("given chunk number %d is not exists", i) + m.log.Warn().Msg(e.Error()) + errs = append(errs, e.Error()) + continue + } else if e != nil { + m.log.Warn().Msg("an error occured while peeking a releases chunk - " + e.Error()) + errs = append(errs, e.Error()) + continue + } + + var releasesChunk Releases + if e = json.Unmarshal(futils.UnsafeBytes(res), &releasesChunk); e != nil { + m.log.Warn().Msg("an error occured while unmarshal release chunk - " + e.Error()) + errs = append(errs, e.Error()) + continue + } + + for _, release := range releasesChunk { + if release.BlockedInfo != nil && release.BlockedInfo.IsBlockedByCopyrights { + m.log.Debug().Msgf("release %d (%s) worldwide banned, skip it...", release.Id, release.Code) + banned++ + continue + } + + if zerolog.GlobalLevel() <= zerolog.DebugLevel { + m.log.Trace().Msgf("release %d with code %s found", release.Id, release.Code) + } + + total++ + releases = append(releases, release.Code) + } + + } + + if errslen := len(errs); errslen != 0 { + m.log.Error().Msgf("%d chunks were corrupted, data from them did not get into the cache", errslen) + m.log.Error().Msg("release redis extraction process errors:") + + for _, err := range errs { + m.log.Error().Msg(err) + } + } + + m.log.Info().Msgf("in %s from %d (of %d) chunks added %d releases and %d skipped because of WW ban", + time.Since(started).String(), chunks-len(errs), chunks, total, banned) + return releases, nil +} + +func (m *Randomizer) rotateReleases(releases []string) { + m.mu.Lock() + defer m.mu.Unlock() + + m.log.Debug().Msgf("update current %d releases with slice of %d releases", + len(m.releases), len(releases)) + m.releases = releases +} + +func (m *Randomizer) randomRelease() (_ string) { + if !m.mu.TryRLock() { + m.log.Warn().Msg("could not get randomized release, read lock is not available") + return + } + defer m.mu.RUnlock() + + if len(m.releases) == 0 { + m.log.Warn().Msg("randomizer is not ready yet") + return + } + + r := rand.Intn(len(m.releases)) // skipcq: GSC-G404 math/rand is enoght here + return m.releases[r] +} diff --git a/internal/proxy/handlers.go b/internal/proxy/handlers.go index b0db7e0..92f18fc 100644 --- a/internal/proxy/handlers.go +++ b/internal/proxy/handlers.go @@ -35,6 +35,21 @@ func (m *Proxy) HandleProxyToDst(c *fiber.Ctx) (e error) { return } +func (m *Proxy) HandleRandomRelease(c *fiber.Ctx) (e error) { + if m.randomizer == nil { + return fiber.NewError(fiber.StatusServiceUnavailable, "BUG! randomizer is not initialized") + } + + var release string + if release = m.randomizer.Randomize(); release == "" { + return fiber.NewError(fiber.StatusServiceUnavailable, + "an error occured in randomizer, maybe it's not ready yet") + } + + fmt.Fprintln(c, release) + return respondPlainWithStatus(c, fiber.StatusOK) +} + // internal api handlers func respondPlainWithStatus(c *fiber.Ctx, status int) error { diff --git a/internal/proxy/middlewares.go b/internal/proxy/middlewares.go index c2fc427..cf43466 100644 --- a/internal/proxy/middlewares.go +++ b/internal/proxy/middlewares.go @@ -3,10 +3,11 @@ package proxy import ( "bytes" + "github.com/anilibria/alice/internal/utils" "github.com/gofiber/fiber/v2" ) -func (*Proxy) MiddlewareValidation(c *fiber.Ctx) (e error) { +func (m *Proxy) MiddlewareValidation(c *fiber.Ctx) (e error) { v := AcquireValidator(c, c.Request().Header.ContentType()) defer ReleaseValidator(v) @@ -14,6 +15,17 @@ func (*Proxy) MiddlewareValidation(c *fiber.Ctx) (e error) { return fiber.NewError(fiber.StatusBadRequest, e.Error()) } + if v.IsQueryEqual([]byte("random_release")) { + if m.randomizer != nil { + if release := m.randomizer.Randomize(); release != "" { + if e = utils.RespondWithRandomRelease(release, c); e == nil { + return respondPlainWithStatus(c, fiber.StatusOK) + } + rlog(c).Error().Msg("could not respond on random release query - " + e.Error()) + } + } + } + // continue request processing e = c.Next() return diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 13709da..67e91b3 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/anilibria/alice/internal/anilibria" "github.com/anilibria/alice/internal/cache" "github.com/anilibria/alice/internal/geoip" "github.com/anilibria/alice/internal/utils" @@ -19,8 +20,9 @@ type Proxy struct { client *ProxyClient config *ProxyConfig - cache *cache.Cache - geoip geoip.GeoIPClient + cache *cache.Cache + geoip geoip.GeoIPClient + randomizer *anilibria.Randomizer } type ProxyConfig struct { @@ -31,6 +33,16 @@ type ProxyConfig struct { func NewProxy(c context.Context) *Proxy { cli := c.Value(utils.CKCliCtx).(*cli.Context) + var randomizer *anilibria.Randomizer + if c.Value(utils.CKRandomizer) != nil { + randomizer = c.Value(utils.CKRandomizer).(*anilibria.Randomizer) + } + + var gip geoip.GeoIPClient + if c.Value(utils.CKGeoIP) != nil { + gip = c.Value(utils.CKGeoIP).(geoip.GeoIPClient) + } + return &Proxy{ client: NewClient(cli), config: &ProxyConfig{ @@ -39,8 +51,10 @@ func NewProxy(c context.Context) *Proxy { apiSecret: []byte(cli.String("cache-api-secret")), }, + geoip: gip, + randomizer: randomizer, + cache: c.Value(utils.CKCache).(*cache.Cache), - geoip: c.Value(utils.CKGeoIP).(geoip.GeoIPClient), } } diff --git a/internal/proxy/validator.go b/internal/proxy/validator.go index 2f5114b..a9eff41 100644 --- a/internal/proxy/validator.go +++ b/internal/proxy/validator.go @@ -43,6 +43,7 @@ func AcquireValidator(c *fiber.Ctx, ctr []byte) (v *Validator) { } func ReleaseValidator(v *Validator) { + fasthttp.ReleaseArgs(v.requestArgs) v.Reset() validatorPool.Put(v) } @@ -60,7 +61,6 @@ func (m *Validator) ValidateRequest() (e error) { m.validateCustomHeaders() m.requestArgs = fasthttp.AcquireArgs() - defer fasthttp.ReleaseArgs(m.requestArgs) if e = m.extractRequestKey(); e != nil { return @@ -82,6 +82,10 @@ func (m *Validator) ValidateRequest() (e error) { return } +func (m *Validator) IsQueryEqual(equal []byte) bool { + return m.queryLookup(equal) +} + func (m *Validator) Reset() { m.Context().RemoveUserValue(utils.UVCacheKey) ReleaseKey(m.cacheKey) @@ -116,7 +120,6 @@ func (m *Validator) validateContentType() utils.RequestContentType { } func (m *Validator) validateCustomHeaders() { - for header, ch := range Stoch { val := m.Request().Header.PeekBytes(futils.UnsafeBytes(header)) if len(val) != 0 { @@ -239,7 +242,7 @@ func (m *Validator) isArgsWhitelisted() (_ bool) { declinedKeysPtr := declinedKeysPool.Get().(*[]string) declinedKeys := *declinedKeysPtr - m.requestArgs.VisitAll(func(key, value []byte) { + m.requestArgs.VisitAll(func(key, _ []byte) { if _, ok := postArgsWhitelist[futils.UnsafeString(key)]; !ok { declinedKeys = append(declinedKeys, futils.UnsafeString(key)) } @@ -275,3 +278,12 @@ func (m *Validator) isQueryWhitelisted() (ok bool) { return } + +func (m *Validator) queryLookup(equal []byte) (_ bool) { + var query []byte + if query = m.requestArgs.PeekBytes([]byte("query")); len(query) == 0 { + return + } + + return bytes.Equal(query, equal) +} diff --git a/internal/service/router.go b/internal/service/router.go index 9103cc2..7fd7893 100644 --- a/internal/service/router.go +++ b/internal/service/router.go @@ -172,6 +172,12 @@ func (m *Service) fiberRouterInitialization() { cacheapi.Post("/purge", m.proxy.HandleCachePurge) cacheapi.Post("/purgeall", m.proxy.HandleCachePurgeAll) + // + // ALICE randomizer method for legacy www + if m.randomizer != nil { + m.fb.Post("/public/random.php", m.proxy.HandleRandomRelease) + } + // // ALICE apiv1 requests proxying lifecycle: diff --git a/internal/service/service.go b/internal/service/service.go index 3c3f3e1..e9cefbd 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -12,6 +12,7 @@ import ( "syscall" "time" + "github.com/anilibria/alice/internal/anilibria" "github.com/anilibria/alice/internal/cache" "github.com/anilibria/alice/internal/geoip" "github.com/anilibria/alice/internal/proxy" @@ -37,9 +38,10 @@ type Service struct { fb *fiber.App fbstor fiber.Storage - proxy *proxy.Proxy - cache *cache.Cache - geoip geoip.GeoIPClient + proxy *proxy.Proxy + cache *cache.Cache + geoip geoip.GeoIPClient + randomizer *anilibria.Randomizer syslogWriter io.Writer @@ -181,6 +183,14 @@ func (m *Service) Bootstrap() (e error) { gCtx = context.WithValue(gCtx, utils.CKCache, m.cache) gofunc(&wg, m.cache.Bootstrap) + // randomizer module + if gCli.Bool("randomizer-enable") { + m.randomizer = anilibria.New(gCtx) + gCtx = context.WithValue(gCtx, utils.CKRandomizer, m.randomizer) + + gofunc(&wg, m.randomizer.Bootstrap) + } + // geoip module if gCli.Bool("geoip-enable") { if path := gCli.String("geoip-db-path"); path != "" { diff --git a/internal/utils/apiv1_response.go b/internal/utils/apiv1_response.go index 10c31f0..e320627 100644 --- a/internal/utils/apiv1_response.go +++ b/internal/utils/apiv1_response.go @@ -10,7 +10,7 @@ import ( type ( ApiResponse struct { Status bool - Data interface{} + Data *ApiResponseData Error *ApiError } ApiResponseWOData struct { @@ -18,6 +18,9 @@ type ( Data interface{} `json:"-"` Error *ApiError } + ApiResponseData struct { + Code string + } ApiError struct { Code int Message string @@ -46,6 +49,7 @@ func ReleaseApiResponseWOData(ar *ApiResponseWOData) { var apiResponsePool = sync.Pool{ New: func() interface{} { return &ApiResponse{ + Data: &ApiResponseData{}, Error: &ApiError{}, } }, @@ -57,16 +61,38 @@ func AcquireApiResponse() *ApiResponse { func ReleaseApiResponse(ar *ApiResponse) { ar.Status = false - ar.Error.Code, ar.Error.Message, ar.Error.Description = 0, "", "" + if ar.Error != nil { + ar.Error.Code, ar.Error.Message, ar.Error.Description = 0, "", "" + } + if ar.Data != nil { + ar.Data.Code = "" + } apiResponsePool.Put(ar) } func RespondWithApiError(status int, msg, desc string, w io.Writer) (e error) { apirsp := AcquireApiResponse() + defer ReleaseApiResponse(apirsp) + apirsp.Error.Code, apirsp.Error.Message, apirsp.Error.Description = status, msg, desc + apirsp.Data = nil + + var buf []byte + if buf, e = easyjson.Marshal(apirsp); e != nil { + return + } + + _, e = w.Write(buf) + return +} + +func RespondWithRandomRelease(code string, w io.Writer) (e error) { + apirsp := AcquireApiResponse() defer ReleaseApiResponse(apirsp) + apirsp.Status, apirsp.Data.Code, apirsp.Error = true, code, nil + var buf []byte if buf, e = easyjson.Marshal(apirsp); e != nil { return diff --git a/internal/utils/apiv1_response_easyjson.go b/internal/utils/apiv1_response_easyjson.go index 182f259..99f0793 100644 --- a/internal/utils/apiv1_response_easyjson.go +++ b/internal/utils/apiv1_response_easyjson.go @@ -102,7 +102,73 @@ func (v *ApiResponseWOData) UnmarshalJSON(data []byte) error { func (v *ApiResponseWOData) UnmarshalEasyJSON(l *jlexer.Lexer) { easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils(l, v) } -func easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils1(in *jlexer.Lexer, out *ApiResponse) { +func easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils1(in *jlexer.Lexer, out *ApiResponseData) { + isTopLevel := in.IsStart() + if in.IsNull() { + if isTopLevel { + in.Consumed() + } + in.Skip() + return + } + in.Delim('{') + for !in.IsDelim('}') { + key := in.UnsafeFieldName(false) + in.WantColon() + if in.IsNull() { + in.Skip() + in.WantComma() + continue + } + switch key { + case "code": + out.Code = string(in.String()) + default: + in.SkipRecursive() + } + in.WantComma() + } + in.Delim('}') + if isTopLevel { + in.Consumed() + } +} +func easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils1(out *jwriter.Writer, in ApiResponseData) { + out.RawByte('{') + first := true + _ = first + { + const prefix string = ",\"code\":" + out.RawString(prefix[1:]) + out.String(string(in.Code)) + } + out.RawByte('}') +} + +// MarshalJSON supports json.Marshaler interface +func (v ApiResponseData) MarshalJSON() ([]byte, error) { + w := jwriter.Writer{} + easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils1(&w, v) + return w.Buffer.BuildBytes(), w.Error +} + +// MarshalEasyJSON supports easyjson.Marshaler interface +func (v ApiResponseData) MarshalEasyJSON(w *jwriter.Writer) { + easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils1(w, v) +} + +// UnmarshalJSON supports json.Unmarshaler interface +func (v *ApiResponseData) UnmarshalJSON(data []byte) error { + r := jlexer.Lexer{Data: data} + easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils1(&r, v) + return r.Error() +} + +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface +func (v *ApiResponseData) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils1(l, v) +} +func easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils2(in *jlexer.Lexer, out *ApiResponse) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -124,12 +190,14 @@ func easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils1(in *jlexer.Lexer case "status": out.Status = bool(in.Bool()) case "data": - if m, ok := out.Data.(easyjson.Unmarshaler); ok { - m.UnmarshalEasyJSON(in) - } else if m, ok := out.Data.(json.Unmarshaler); ok { - _ = m.UnmarshalJSON(in.Raw()) + if in.IsNull() { + in.Skip() + out.Data = nil } else { - out.Data = in.Interface() + if out.Data == nil { + out.Data = new(ApiResponseData) + } + (*out.Data).UnmarshalEasyJSON(in) } case "error": if in.IsNull() { @@ -151,7 +219,7 @@ func easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils1(in *jlexer.Lexer in.Consumed() } } -func easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils1(out *jwriter.Writer, in ApiResponse) { +func easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils2(out *jwriter.Writer, in ApiResponse) { out.RawByte('{') first := true _ = first @@ -163,12 +231,10 @@ func easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils1(out *jwriter.Wri { const prefix string = ",\"data\":" out.RawString(prefix) - if m, ok := in.Data.(easyjson.Marshaler); ok { - m.MarshalEasyJSON(out) - } else if m, ok := in.Data.(json.Marshaler); ok { - out.Raw(m.MarshalJSON()) + if in.Data == nil { + out.RawString("null") } else { - out.Raw(json.Marshal(in.Data)) + (*in.Data).MarshalEasyJSON(out) } } { @@ -186,27 +252,27 @@ func easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils1(out *jwriter.Wri // MarshalJSON supports json.Marshaler interface func (v ApiResponse) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils1(&w, v) + easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils2(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ApiResponse) MarshalEasyJSON(w *jwriter.Writer) { - easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils1(w, v) + easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils2(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ApiResponse) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils1(&r, v) + easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils2(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ApiResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils1(l, v) + easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils2(l, v) } -func easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils2(in *jlexer.Lexer, out *ApiError) { +func easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils3(in *jlexer.Lexer, out *ApiError) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -241,7 +307,7 @@ func easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils2(in *jlexer.Lexer in.Consumed() } } -func easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils2(out *jwriter.Writer, in ApiError) { +func easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils3(out *jwriter.Writer, in ApiError) { out.RawByte('{') first := true _ = first @@ -266,23 +332,23 @@ func easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils2(out *jwriter.Wri // MarshalJSON supports json.Marshaler interface func (v ApiError) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} - easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils2(&w, v) + easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils3(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface func (v ApiError) MarshalEasyJSON(w *jwriter.Writer) { - easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils2(w, v) + easyjson1e840bfEncodeGithubComAnilibriaAliceInternalUtils3(w, v) } // UnmarshalJSON supports json.Unmarshaler interface func (v *ApiError) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils2(&r, v) + easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils3(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface func (v *ApiError) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils2(l, v) + easyjson1e840bfDecodeGithubComAnilibriaAliceInternalUtils3(l, v) } diff --git a/internal/utils/context.go b/internal/utils/context.go index 8df8d38..99b78c7 100644 --- a/internal/utils/context.go +++ b/internal/utils/context.go @@ -8,4 +8,5 @@ const ( CKAbortFunc CKCache CKGeoIP + CKRandomizer )