79e7ed894b39f65202ef820ae81af514f62e43dd max Wed May 20 13:38:35 2026 -0700 hubApi/blat: errCatch gfServer calls, fix comment, maxSeqCount from hg.conf, Content-Type spacing - Wrap gfServer block (mustOpen→gfDisconnect) in errCatch so a down or unreachable BLAT server returns a JSON 500 instead of a bare Apache error. - Fix comment: hgBlat's CGI interface and behavior remain unchanged (it was refactored internally in the previous commit). - Replace hardcoded maxSeqCount=25 with cfgOptionDefault("hgBlat.maxSequenceCount") to share the same hg.conf knob as hgBlat itself. - Remove spurious space in "Content-Type:text/plain" to match rest of hubApi. refs #36315 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> diff --git src/hg/hubApi/blat.c src/hg/hubApi/blat.c index 81ee7f2b58c..63b2c391eb5 100644 --- src/hg/hubApi/blat.c +++ src/hg/hubApi/blat.c @@ -1,19 +1,18 @@ /* blat - /blat endpoint: run a BLAT against an assembly's gfServer and * return PSL hits as JSON (or PSL text). This is the API-callable twin - * of hgBlat?output=json; hgBlat itself remains unchanged for backwards - * compatibility. + * of hgBlat?output=json; hgBlat's CGI interface and behavior remain unchanged. * * NOTE: Much of the alignment logic here (server lookup, sequence filtering, * gfAlign* calls, temp-file round-trip) is derived from hgBlat.c. If you * fix a bug or change behaviour there, check whether this file needs the * same fix. See also the reciprocal note in hgBlat.c. */ #include "dataApi.h" #include "blatServers.h" #include "fa.h" #include "dnautil.h" #include "dnaseq.h" #include "psl.h" #include "trashDir.h" #include "genoFind.h" #include "trackHub.h" @@ -347,45 +346,54 @@ struct blatType bt; parseTypeArg(type, seqList, &bt); filterSequences(seqList, &bt); struct blatServerParams *st = findBlatServer(genome, bt.isTx); if (st == NULL) apiErrAbort(err400, err400Msg, "no %s BLAT server configured for genome='%s'", bt.isTx ? "translated" : "DNA", genome); /* Run alignments into a temp pslx, then read it back to drive output. * Mirrors hgBlat's strategy so we benefit from the same gfOutputPsl path. */ struct tempName pslTn; trashDirFile(&pslTn, "apiBlat", "apiBlat", ".pslx"); -FILE *f = mustOpen(pslTn.forCgi, "w"); + +int maxSeqCount = 25; +char *optionMaxSeqCount = cfgOptionDefault("hgBlat.maxSequenceCount", NULL); +if (isNotEmpty(optionMaxSeqCount)) + maxSeqCount = atoi(optionMaxSeqCount); + +FILE *f = NULL; +struct errCatch *ec = errCatchNew(); +if (errCatchStart(ec)) + { + f = mustOpen(pslTn.forCgi, "w"); struct gfOutput *gvo = gfOutputPsl(0, bt.qIsProt, FALSE, f, FALSE, TRUE); pslxWriteHead(f, bt.qType, bt.tType); struct gfConnection *conn = gfConnect(st->host, st->port, trackHubDatabaseToGenome(st->db), st->genomeDataDir); struct hash *tFileCache = gfFileCacheNew(); int minMatch = 0; /* let gfServer decide; matches hgBlat allResults path */ struct dnaSeq *seq; int singleMax = bt.isTx ? 10000 : 75000; int totalMax = singleMax * 2.5; int total = 0; int seqCount = 0; -int maxSeqCount = 25; for (seq = seqList; seq != NULL; seq = seq->next) { if (++seqCount > maxSeqCount) break; if (seq->size <= 0 || seq->size > singleMax) continue; total += seq->size; if (total > totalMax) break; if (bt.isTx) { if (bt.isTxTx) { gfAlignTransTrans(conn, st->nibDir, seq, FALSE, 5, tFileCache, gvo, !bt.txTxBoth); @@ -396,32 +404,43 @@ FALSE); } } else gfAlignTrans(conn, st->nibDir, seq, 5, tFileCache, gvo); } else { gfAlignStrand(conn, st->nibDir, seq, FALSE, minMatch, tFileCache, gvo); reverseComplement(seq->dna, seq->size); gfAlignStrand(conn, st->nibDir, seq, TRUE, minMatch, tFileCache, gvo); } gfOutputQuery(gvo, f); } carefulClose(&f); + f = NULL; gfFileCacheFree(&tFileCache); gfDisconnect(&conn); + } +errCatchEnd(ec); +if (ec->gotError) + { + if (f != NULL) + carefulClose(&f); + remove(pslTn.forCgi); + apiErrAbort(err500, err500Msg, "BLAT server error: %s", ec->message->string); + } +errCatchFree(&ec); struct lineFile *lf = pslFileOpen(pslTn.forCgi); struct psl *pslList = NULL, *psl; while ((psl = pslNext(lf)) != NULL) slAddHead(&pslList, psl); lineFileClose(&lf); slReverse(&pslList); if (sameWordOk(format, "text") || sameWordOk(format, "psl")) writePslOutput(pslList, &bt); else if (sameWordOk(format, "hgblat")) writeLegacyJsonOutput(pslList, st->db); else writeJsonOutput(pslList, st->db, hubUrl, type, &bt); }