bridge/api/api.go

196 lines
3.5 KiB
Go
Raw Normal View History

2023-07-02 18:00:59 +02:00
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
lifetime time.Duration
tasks map[uint32]*Task
idCounter uint32
api *sati.Api
stats RegistryStats
}
func NewTaskRegistry(api *sati.Api, lifetime time.Duration) *TaskRegistry {
return &TaskRegistry{
mu: &sync.RWMutex{},
tasks: map[uint32]*Task{},
api: api,
lifetime: lifetime,
}
}
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(t.lifetime)
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 {
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
}