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/<type> (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 <noreply@anthropic.com>

diff --git src/hg/hgBlat/hgBlat.c src/hg/hgBlat/hgBlat.c
index 2a02d921a3d..2c58ecc9795 100644
--- src/hg/hgBlat/hgBlat.c
+++ src/hg/hgBlat/hgBlat.c
@@ -1,16 +1,21 @@
-/* hgBlat - CGI-script to manage fast human genome sequence searching. */
+/* hgBlat - CGI-script to manage fast human genome sequence searching.
+ *
+ * NOTE: The hubApi /blat endpoint (src/hg/hubApi/blat.c) derives its
+ * alignment logic (server lookup, sequence filtering, gfAlign* calls,
+ * temp-file round-trip) from this file.  If you fix a bug or change
+ * behaviour here, check whether hubApi/blat.c needs the same fix. */
 
 /* Copyright (C) 2014 The Regents of the University of California 
  * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
 #include <pthread.h>
 #include "common.h"
 #include "errAbort.h"
 #include "hCommon.h"
 #include "jksql.h"
 #include "portable.h"
 #include "linefile.h"
 #include "dnautil.h"
 #include "fa.h"
 #include "psl.h"
 #include "genoFind.h"
 #include "cheapcgi.h"
@@ -310,140 +315,68 @@
     char *host;		/* Name of machine hosting server. */
     char *port;		/* Port that hosts server. */
     char *nibDir;	/* Directory of sequence files. */
     int tileSize;       /* gfServer -tileSize */
     int stepSize;       /* gfServer -stepSize */
     int minMatch;       /* gfServer -minMatch */
     boolean isDynamic;  /* is a dynamic server */
     char* genomeDataDir; /* genome name for dynamic gfServer  */
     };
 
 char *typeList[] = {"BLAT's guess", "DNA", "protein", "translated RNA", "translated DNA"};
 char *outputList[] = {"hyperlink", "psl", "psl no header", "JSON"};
 
 int minMatchShown = 0;
 
-static struct serverTable *databaseServerTable(char *db, boolean isTrans)
-/* Load blat table for a database */
-{
-struct sqlConnection *conn = hConnectCentral();
-char query[512];
-struct sqlResult *sr;
-char **row;
-char dbActualName[32];
-
-/* If necessary convert database description to name. */
-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;
-    }
-
-struct serverTable *st;
-AllocVar(st);
-
-/* Do a little join to get data to fit into the serverTable and grab
- * dbDb.nibPath too.  Check for newer dynamic flag and allow with or without
- * it.
- * For debugging, one set the variable blatServersTbl to some db.table to
- * pick up settings from somewhere other than dbDb.blatServers.
- */
-char *blatServersTbl = cfgOptionDefault("blatServersTbl", "blatServers");
-boolean haveDynamic = sqlColumnExists(conn, blatServersTbl, "dynamic");
-sqlSafef(query, sizeof(query), "select dbDb.name, dbDb.description, blatServers.isTrans,"
-         "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);
-sr = sqlGetResult(conn, query);
-if ((row = sqlNextRow(sr)) == NULL)
+struct serverTable *findServer(char *db, boolean isTrans)
+/* Return server for given database.  Db can either be database name or
+ * description.  Server lookup delegated to findBlatServer() in hg/lib/blatServers.c. */
 {
+struct blatServerParams *p = findBlatServer(db, isTrans);
+if (p == NULL)
     errAbort("Can't find a server for %s database %s.  Click "
 	     "<A HREF=\"/cgi-bin/hgBlat?%s&command=start&db=%s\">here</A> "
 	     "to reset to default database.",
 	     (isTrans ? "translated" : "DNA"), db,
 	     cartSidUrlString(cart), hDefaultDb());
-    }
-st->db = cloneString(row[0]);
-st->genome = cloneString(row[1]);
-st->isTrans = atoi(row[2]);
-st->host = cloneString(row[3]);
-st->port = cloneString(row[4]);
-st->nibDir = hReplaceGbdbSeqDir(row[5], st->db);
-if (atoi(row[6]))
-    {
-    st->isDynamic = TRUE;
-    st->genomeDataDir = cloneString(st->db);  // directories by database name for database genomes
-    ++nonHubDynamicBlatServerCount;
-    }
-
-sqlFreeResult(&sr);
-hDisconnectCentral(&conn);
-return st;
-}
-
-static struct serverTable *trackHubServerTable(char *db, boolean isTrans)
-/* Load blat table for a hub */
-{
-char *host, *port;
-char *genomeDataDir;
-
-if (!trackHubGetBlatParams(db, isTrans, &host, &port, &genomeDataDir))
-    return NULL;
-
 struct serverTable *st;
 AllocVar(st);
-
-st->db = cloneString(db);
-st->genome = cloneString(hGenome(db));
-st->isTrans = isTrans;
-st->host = host; 
-st->port = port;
-struct trackHubGenome *genome = trackHubGetGenome(db);
-st->nibDir = cloneString(genome->twoBitPath);
-char *ptr = strrchr(st->nibDir, '/');
-// we only want the directory name
-if (ptr != NULL)
-    *ptr = 0;
-if (genomeDataDir != NULL)
-    {
-    st->isDynamic = TRUE;
-    st->genomeDataDir = cloneString(genomeDataDir);
-    ++hubDynamicBlatServerCount;
-    }
-return st;
-}
-
-struct serverTable *findServer(char *db, boolean isTrans)
-/* Return server for given database.  Db can either be
- * database name or description. */
+st->db          = p->db;
+st->genome      = p->genome;
+st->isTrans     = p->isTrans;
+st->host        = p->host;
+st->port        = p->port;
+st->nibDir      = p->nibDir;
+st->isDynamic   = p->isDynamic;
+st->genomeDataDir = p->genomeDataDir;
+if (p->isDynamic)
     {
     if (trackHubDatabase(db))
-    return trackHubServerTable(db, isTrans);
+        ++hubDynamicBlatServerCount;
     else
-    return databaseServerTable(db, isTrans);
+        ++nonHubDynamicBlatServerCount;
+    }
+return st;
 }
 
 void findClosestServer(char **pDb, char **pOrg)
 /* If db doesn't have a blat server, look for the closest db (or org) that has one,
  * as hgPcr does. */
 {
 char *db = *pDb, *org = *pOrg;
 
-if (trackHubDatabase(db) && (trackHubServerTable(db, FALSE) != NULL))
+if (trackHubDatabase(db) && (findBlatServer(db, FALSE) != NULL))
     {
     *pDb = db;
     *pOrg = hGenome(db);
     return;
     }
 
 struct sqlConnection *conn = hConnectCentral();
 char query[256];
 sqlSafef(query, sizeof(query), "select db from blatServers where db = '%s'", db);
 if (!sqlExists(conn, query))
     {
     sqlSafef(query, sizeof(query), "select blatServers.db from blatServers,dbDb "
 	  "where blatServers.db = dbDb.name and dbDb.genome = '%s'", org);
     char *db = sqlQuickString(conn, query);
     if (db == NULL)