package api import ( "net/http" "sync" "time" "git.sati.ac/sati.ac/bridge/config" "git.sati.ac/sati.ac/sati-go" "github.com/sirupsen/logrus" ) type TaskState uint const ( StateProcessing TaskState = iota StateSuccess StateError ) type Task struct { State TaskState Entity *sati.TaskEntity Result any CreateTime int64 EndTime int64 } type RegistryStats struct { Total uint32 Success uint32 Error uint32 Processing uint32 } type TaskRegistry struct { mu *sync.RWMutex config *config.Config tasks map[uint32]*Task idCounter uint32 api *sati.Api stats RegistryStats } func NewTaskRegistry(api *sati.Api, config *config.Config) *TaskRegistry { return &TaskRegistry{ mu: &sync.RWMutex{}, tasks: map[uint32]*Task{}, api: api, config: config, } } func (t *TaskRegistry) CreateTask(task sati.AnyTask) uint32 { t.mu.Lock() t.idCounter++ id := t.idCounter entry := &Task{ State: StateProcessing, CreateTime: time.Now().Unix(), } t.stats.Total++ t.stats.Processing++ t.tasks[id] = entry t.mu.Unlock() go func() { result := task.Result() entity, err := t.api.Solve(task, result) t.mu.Lock() t.stats.Processing-- if err != nil { t.stats.Error++ entry.State = StateError } else { t.stats.Success++ entry.State = StateSuccess entry.Entity = entity entry.Result = result } entry.EndTime = time.Now().Unix() t.mu.Unlock() time.Sleep(time.Millisecond * time.Duration(t.config.TaskLifetime)) t.mu.Lock() delete(t.tasks, id) t.mu.Unlock() }() return id } func (t *TaskRegistry) Stats() RegistryStats { t.mu.RLock() defer t.mu.RUnlock() return t.stats } func (t *TaskRegistry) Get(id uint32) *Task { if t.config.TaskGetDelay != 0 { time.Sleep(time.Millisecond * time.Duration(t.config.TaskGetDelay)) } t.mu.RLock() defer t.mu.RUnlock() task := t.tasks[id] if task == nil { return nil } cloned := *task return &cloned } type ApiContext struct { Config *config.Config Api *sati.Api Registry *TaskRegistry Logger *logrus.Logger Server *ApiServer } type ApiHandler interface { Name() string Domains() []string ServeHTTP(w http.ResponseWriter, r *http.Request) } type ApiServer struct { handlers map[string]ApiHandler domains map[string]ApiHandler ctx *ApiContext } func (a *ApiServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { a.ctx.Logger.WithFields(logrus.Fields{ "method": r.Method, "host": r.Host, "path": r.URL.Path, }).Debug("request") handler, ok := a.domains[r.Host] if !ok { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(400) w.Write([]byte("domain not found")) return } handler.ServeHTTP(w, r) } func (a *ApiServer) GetDomains() []string { domains := make([]string, 0, len(a.domains)) for domain := range a.domains { domains = append(domains, domain) } return domains } func NewApiServer(ctx *ApiContext) *ApiServer { handlers := []ApiHandler{ newStatsApi(ctx), newAntigateV2Api(ctx), newRuCaptchaApi(ctx), } server := &ApiServer{ handlers: map[string]ApiHandler{}, domains: map[string]ApiHandler{}, ctx: ctx, } for _, handler := range handlers { server.handlers[handler.Name()] = handler for _, domain := range handler.Domains() { server.domains[domain] = handler } } for domain, handlerName := range ctx.Config.ExtraDomains { handler, ok := server.handlers[handlerName] if !ok { ctx.Logger.WithFields(logrus.Fields{ "domain": domain, "handler": handlerName, }).Warn("extraDomains: handler not found, ignoring it") continue } server.domains[domain] = handler } ctx.Server = server return server }