diff --git a/deploy/nginx/common/routes-core.conf b/deploy/nginx/common/routes-core.conf index 45c01db3..a907babb 100644 --- a/deploy/nginx/common/routes-core.conf +++ b/deploy/nginx/common/routes-core.conf @@ -31,7 +31,8 @@ # location is chosen, because the literal locations below do not match a ';' # and would hand it to Java. $request_uri is the path as sent: an encoded # ';' (%3B) is part of an id, as it is to Java, and a ';' in the query string -# is the query's. +# is the query's. That holds only because Java is sent the path as sent, too +# (the /ContentService/ location below). error_page 418 = @content_node_refuses; if ($request_uri ~ "^/ContentService/[^?]*;") { return 418; } location @content_node_refuses { @@ -511,7 +512,14 @@ location ~ ^/gsa(/|$) { return 301 /PathwayBrowser?analysisTab=quantitative; } location /ContentService/ { include /etc/nginx/common/upstream-proxy.conf; - proxy_pass http://content/ContentService/; + # No URI part: Tomcat is sent the request's path as it was sent, not + # nginx's decoded copy. With one, an encoded ';' reached Java as a raw one + # and Java dropped it as a matrix parameter -- so /data/query%3Bx/R-HSA-1, + # which no node location matches, was answered by Java as node's + # /data/query/R-HSA-1: a node route answered by Java, the fallback the + # port rules out. (No braces in comments here: routes.spec.ts reads a + # location's body up to its first closing one.) + proxy_pass http://content; # A diagram or SBML export takes minutes. Apache's default is 300s and the # site relies on it; nginx defaults to 60 and would cut them off. proxy_read_timeout 300s; @@ -520,7 +528,9 @@ location /ContentService/ { location /AnalysisService/ { include /etc/nginx/common/upstream-proxy.conf; - proxy_pass http://analysis/AnalysisService/; + # No URI part, as for /ContentService/: the path as sent, never nginx's + # decoded copy of it. + proxy_pass http://analysis; # An analysis over a large identifier list is not quick either. proxy_read_timeout 300s; proxy_send_timeout 300s; diff --git a/deploy/nginx/routes.spec.ts b/deploy/nginx/routes.spec.ts index 3efa5f6d..d7809bc0 100644 --- a/deploy/nginx/routes.spec.ts +++ b/deploy/nginx/routes.spec.ts @@ -288,6 +288,25 @@ describe('a matrix parameter', () => { ]); }); + it('reaches Java encoded, as sent, so Java cannot drop it as one', () => { + // A proxy_pass with a URI part sends nginx's decoded path: %3B arrives at + // Tomcat as ';', which it drops as a matrix parameter, and + // /data/query%3Bx/{id} -- no node location's -- is answered by Java as + // node's /data/query/{id}. Without one, Tomcat gets the path as sent; + // the AnalysisService's too. + const core = readFileSync(join(__dirname, 'common', 'routes-core.conf'), 'utf8'); + for (const [service, upstream] of [ + ['ContentService', 'content'], + ['AnalysisService', 'analysis'], + ]) { + const block = new RegExp(`^location /${service}/ \\{([^}]*)\\}`, 'm').exec(core)?.[1] ?? ''; + expect([service, block.match(/^\s*proxy_pass\s+[^;]*;/gm)]).toEqual([ + service, + [` proxy_pass http://${upstream};`], + ]); + } + }); + it('is not an encoded one, nor one in the query string', async () => { for (const url of [ '/ContentService/data/query/abridged/R-HSA-109581%3Bx',