api: /v1/batch — submit, status summary, one result, cancel
The HTTP surface over s.Batch. New internal/api/batch.go: batchSubmit (POST)
decodes {"targets":[{url,mode,question,pages,grep,jq,context,recover}]} flat as
/v1/fetch does, 400 "invalid batch" on bad json / empty list / empty url / bad
mode, then PreCheck per target -> 422 "blocked: target <idx>: <reason>" with no
job, then Submit mapping ErrTooManyTargets->422, ErrCallerBusy->429 Retry-After
30, ErrBusy->429 Retry-After 60, ErrNoTargets->400, and 202
{success,job_id,status,targets,reused}. batchStatus (GET), batchResult
(GET {id}/{idx}) and batchCancel (DELETE) share ownership: unknown or not
c.Name -> 404, never 403; status is a summary (no inline results unless
?results=1), idx validated 0..targets_total-1 else 404, cancel of a terminal
job -> 409 else 200 {success,status:cancelled}. api.go scoped to rule 2:
batchMaxBody (256 KiB), the four routes in New, and ServeHTTP applying the
per-path cap once and collapsing every batch path to a fixed metrics label so a
job id never becomes a label. Each handler logs one batch line, never a URL,
hostname or idem_key.
Co-Authored-By: ornith-1.5-35b-a3b <ornith-1.5-35b-a3b@llama.cpp.invalid>
This commit is contained in:
+32
-1
@@ -37,6 +37,10 @@ const (
|
||||
writeTimeout = 240 * time.Second // P5a: a PDF may take 120 s to download + 20 s queued + 60 s to extract
|
||||
requestDeadline = 210 * time.Second // P5a: the PDF path's worst case (see writeTimeout); other paths keep their own shorter timeouts
|
||||
|
||||
// batchMaxBody is the largest body POST /v1/batch accepts: 32 targets, each with a
|
||||
// large question, exceed the 64 KiB cap of the other endpoints.
|
||||
batchMaxBody = 256 << 10
|
||||
|
||||
// DefaultPDFMaxUpload is the largest body POST /v1/extract accepts: an uploaded
|
||||
// PDF, addressed by its hash and extracted once per content.
|
||||
DefaultPDFMaxUpload = 100 << 20
|
||||
@@ -108,6 +112,10 @@ func New(version string, g *guard.Guard, f *fetch.Fetcher, keys *auth.Store, log
|
||||
s.Mux.HandleFunc("POST /v1/extract", s.v1extract)
|
||||
s.Mux.HandleFunc("POST /v1/search", s.v1search)
|
||||
s.Mux.HandleFunc("POST /v2/search", s.v2search)
|
||||
s.Mux.HandleFunc("POST /v1/batch", s.batchSubmit)
|
||||
s.Mux.HandleFunc("GET /v1/batch/{id}", s.batchStatus)
|
||||
s.Mux.HandleFunc("GET /v1/batch/{id}/{idx}", s.batchResult)
|
||||
s.Mux.HandleFunc("DELETE /v1/batch/{id}", s.batchCancel)
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -125,8 +133,11 @@ func (s *Server) HTTPServer(addr string) *http.Server {
|
||||
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
var limit int64 = maxReqBody
|
||||
if r.URL.Path == "/v1/extract" {
|
||||
switch r.URL.Path {
|
||||
case "/v1/extract":
|
||||
limit = int64(s.pdfMaxUpload())
|
||||
case "/v1/batch":
|
||||
limit = batchMaxBody
|
||||
}
|
||||
r.Body = http.MaxBytesReader(w, r.Body, limit)
|
||||
switch r.URL.Path {
|
||||
@@ -144,9 +155,29 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.Prom.Observe(r.URL.Path, time.Since(start), code)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(r.URL.Path, "/v1/batch") {
|
||||
cw := &codeWriter{ResponseWriter: w}
|
||||
start := time.Now()
|
||||
s.Mux.ServeHTTP(cw, r)
|
||||
code := cw.code
|
||||
if code == 0 {
|
||||
code = http.StatusOK
|
||||
}
|
||||
s.Prom.Observe(batchObservePath(r.URL.Path), time.Since(start), code)
|
||||
return
|
||||
}
|
||||
s.Mux.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// batchObservePath collapses a batch path to a fixed label so a job id never becomes a
|
||||
// metric label value: the submit path stays, every {id}/{idx} variant becomes {id}.
|
||||
func batchObservePath(path string) string {
|
||||
if path == "/v1/batch" {
|
||||
return "/v1/batch"
|
||||
}
|
||||
return "/v1/batch/{id}"
|
||||
}
|
||||
|
||||
type codeWriter struct {
|
||||
http.ResponseWriter
|
||||
code int
|
||||
|
||||
Reference in New Issue
Block a user