2197f6d5208aff4c48ccbe42e61a116d988ac392 max Tue May 19 08:23:54 2026 -0700 hubApi: add /blat endpoint with apiKey gating, format=hgblat, and known-agent bypass New src/hg/hubApi/blat.c implements /blat/ (dna, protein, transRna, transDna, guess) backed by the same gfServer logic as hgBlat. Key details: - Requires an apiKey for rate-limiting; botException() and botExceptionUserAgent() exempt IPs/user-agents in hg.conf (same policy as captcha bypass elsewhere in the browser stack). - Invalid apiKey returns a clean JSON 403 rather than an HTML 500 (pre-validated in hubApi.c main() before hgBotDelayTimeFrac runs). - Extra bot-delay fraction (default 0.3, 10x hubApi default) is configurable via hubApi.blatDelayFraction in hg.conf. - format=text/psl -> PSL text; format=hgblat -> byte-for-byte hgBlat?output=json shape; jsonOutputArrays=1 -> hubApi envelope with arrays (parallel to getData behaviour); default -> objects. - botExceptionUserAgent() carved out of cart.c's static isUserAgentException() into botDelay.c so non-cart callers can use it. - Cross-reference comments added in hgBlat.c and blat.c noting the shared logic so fixes get applied to both. refs #36315 Co-Authored-By: Claude Sonnet 4.6 diff --git src/hg/lib/blatServers.c src/hg/lib/blatServers.c index d1c23eee321..067b9ec475b 100644 --- src/hg/lib/blatServers.c +++ src/hg/lib/blatServers.c @@ -1,23 +1,26 @@ /* blatServers.c was originally generated by the autoSql program, which also * generated blatServers.h and blatServers.sql. This module links the database and * the RAM representation of objects. */ #include "common.h" #include "linefile.h" #include "dystring.h" #include "jksql.h" +#include "hdb.h" +#include "hgConfig.h" +#include "trackHub.h" #include "blatServers.h" char *blatServersCommaSepFieldNames = "db,host,port,isTrans,canPcr,dynamic"; void blatServersStaticLoad(char **row, struct blatServers *ret) /* Load a row from blatServers table into ret. The contents of ret will * be replaced at the next call to this function. */ { ret->db = row[0]; ret->host = row[1]; ret->port = sqlSigned(row[2]); ret->isTrans = sqlSigned(row[3]); @@ -132,15 +135,97 @@ fprintf(f, "%s", el->host); if (sep == ',') fputc('"',f); fputc(sep,f); fprintf(f, "%d", el->port); fputc(sep,f); fprintf(f, "%d", el->isTrans); fputc(sep,f); fprintf(f, "%d", el->canPcr); fputc(sep,f); fprintf(f, "%d", el->dynamic); fputc(lastSep,f); } /* -------------------------------- End autoSql Generated Code -------------------------------- */ +struct blatServerParams *findBlatServer(char *db, boolean isTrans) +/* Return gfServer connection parameters for the given assembly, or NULL if + * none is configured. Handles both hub and non-hub assemblies. */ +{ +struct blatServerParams *st; +AllocVar(st); + +if (trackHubDatabase(db)) + { + char *host = NULL, *port = NULL, *genomeDataDir = NULL; + if (!trackHubGetBlatParams(db, isTrans, &host, &port, &genomeDataDir)) + { + freez(&st); + return NULL; + } + st->db = cloneString(db); + st->genome = cloneString(hGenome(db)); + st->host = host; + st->port = port; + st->isTrans = isTrans; + struct trackHubGenome *hg = trackHubGetGenome(db); + st->nibDir = cloneString(hg->twoBitPath); + char *slash = strrchr(st->nibDir, '/'); + if (slash != NULL) + *slash = 0; + if (genomeDataDir != NULL) + { + st->isDynamic = TRUE; + st->genomeDataDir = cloneString(genomeDataDir); + } + return st; + } + +struct sqlConnection *conn = hConnectCentral(); +char query[512]; +char dbActualName[64]; + +/* Accept either a db name or a description (hgBlat allows both). */ +sqlSafef(query, sizeof(query), "select name from dbDb where name = '%s'", db); +if (!sqlExists(conn, query)) + { + sqlSafef(query, sizeof(query), + "select name from dbDb where description = '%s'", db); + if (sqlQuickQuery(conn, query, dbActualName, sizeof(dbActualName)) != NULL) + db = dbActualName; + } + +char *blatServersTbl = cfgOptionDefault("blatServersTbl", "blatServers"); +boolean haveDynamic = sqlColumnExists(conn, blatServersTbl, "dynamic"); +sqlSafef(query, sizeof(query), + "select dbDb.name, dbDb.description, blatServers.host, blatServers.port, " + "dbDb.nibPath, %s " + "from dbDb, %s blatServers " + "where blatServers.isTrans = %d and dbDb.name = '%s' " + "and dbDb.name = blatServers.db", + (haveDynamic ? "blatServers.dynamic" : "0"), + blatServersTbl, isTrans, db); +struct sqlResult *sr = sqlGetResult(conn, query); +char **row = sqlNextRow(sr); +if (row == NULL) + { + sqlFreeResult(&sr); + hDisconnectCentral(&conn); + freez(&st); + return NULL; + } +st->db = cloneString(row[0]); +st->genome = cloneString(row[1]); +st->host = cloneString(row[2]); +st->port = cloneString(row[3]); +st->nibDir = hReplaceGbdbSeqDir(row[4], st->db); +st->isTrans = isTrans; +if (atoi(row[5])) + { + st->isDynamic = TRUE; + st->genomeDataDir = cloneString(st->db); + } +sqlFreeResult(&sr); +hDisconnectCentral(&conn); +return st; +} +