From 043192bf35462c6be6ebf0ecc67af6f9902b1fcd Mon Sep 17 00:00:00 2001 From: BJ Fulton Date: Thu, 10 Sep 2026 15:33:53 -0600 Subject: [PATCH 1/3] Fix malformed HTTP responses from /capabilities and /availability Both VOSI endpoints emitted their XML document with no HTTP status line and no headers at all -- the first bytes on the wire were `/TAP and returned a 500 whenever that directory did not already exist -- so on a fresh deployment these endpoints failed outright, and on an established one they reached the emitter and produced the header-less response above. Verified by driving the CGI directly for both endpoints: the response now begins with a CRLF-terminated 200 status line, and works whether or not /TAP exists. Co-Authored-By: Claude Opus 5 --- TAP/tap.py | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/TAP/tap.py b/TAP/tap.py index cc20103..64d830d 100755 --- a/TAP/tap.py +++ b/TAP/tap.py @@ -542,6 +542,21 @@ def __init__(self, **kwargs): self.statdict['resulturl'] = '' + # + #{ availability and capabilities are static VOSI documents: they need + # no workspace, so answer them before workspace resolution. Going + # through it treated the empty jobid as a workspace name and failed + # with a 500 whenever /TAP did not already exist. + # + if (self.tapcontext == 'availability'): + self.__printVosiAvailability__ () + + if (self.tapcontext == 'capabilities'): + self.__printVosiCapability__ () + # + #} end VOSI static endpoints + # + # # sync or async without input workspace id: make workspace, # otherwise retrieve workspace from getstatus id @@ -632,16 +647,6 @@ def __init__(self, **kwargs): logging.debug(f'workspace = {self.workspace:s}') logging.debug(f'userWorkdir = {self.userWorkdir:s}') - # - #{ if tapcontext is one of vosiEnpoint, take care of take care of VOSI - # output and return - # - if (self.tapcontext == 'availability'): - self.__printVosiAvailability__ () - - if (self.tapcontext == 'capabilities'): - self.__printVosiCapability__ () - # # vositable: make up vositbl filepath # @@ -2820,6 +2825,17 @@ def __printVosiAvailability__ (self, **kwargs): # { printVosiAvailability # + # + # nph- CGI: emit the full HTTP response ourselves. The status + # line and each header must end in CRLF (print supplies the LF), + # and a bare CRLF line closes the header block -- nginx and + # Cloudflare reject the response otherwise. + # + + print ("HTTP/1.1 200 OK\r") + print ("Content-type: text/xml\r") + print ("\r") + print ('') print ('') print ('') print ('') print (' Date: Thu, 10 Sep 2026 17:05:43 -0600 Subject: [PATCH 2/3] Spell out the CRLF terminator with end='\r\n' Review preference from @jpl-jengelke: make the CRLF requirement explicit at each print rather than relying on a trailing \r plus print's implicit \n. Byte output is unchanged -- both forms emit `...\r\n` -- but the intent is legible without having to remember what print appends, which matters on a file where a bare LF is what broke the endpoint. Co-Authored-By: Claude Opus 5 --- TAP/tap.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/TAP/tap.py b/TAP/tap.py index 64d830d..a39265b 100755 --- a/TAP/tap.py +++ b/TAP/tap.py @@ -2827,14 +2827,16 @@ def __printVosiAvailability__ (self, **kwargs): # # nph- CGI: emit the full HTTP response ourselves. The status - # line and each header must end in CRLF (print supplies the LF), - # and a bare CRLF line closes the header block -- nginx and - # Cloudflare reject the response otherwise. + # line and each header must end in CRLF, and a bare CRLF line + # closes the header block -- nginx and Cloudflare reject the + # response otherwise. The terminator is spelled out via end= + # rather than a trailing \r leaning on print's implicit \n, + # so the CRLF requirement is visible at a glance. # - print ("HTTP/1.1 200 OK\r") - print ("Content-type: text/xml\r") - print ("\r") + print ('HTTP/1.1 200 OK', end='\r\n') + print ('Content-type: text/xml', end='\r\n') + print ('', end='\r\n') print ('') print ('') @@ -2861,14 +2863,16 @@ def __printVosiCapability__ (self, **kwargs): # # nph- CGI: emit the full HTTP response ourselves. The status - # line and each header must end in CRLF (print supplies the LF), - # and a bare CRLF line closes the header block -- nginx and - # Cloudflare reject the response otherwise. + # line and each header must end in CRLF, and a bare CRLF line + # closes the header block -- nginx and Cloudflare reject the + # response otherwise. The terminator is spelled out via end= + # rather than a trailing \r leaning on print's implicit \n, + # so the CRLF requirement is visible at a glance. # - print ("HTTP/1.1 200 OK\r") - print ("Content-type: text/xml\r") - print ("\r") + print ('HTTP/1.1 200 OK', end='\r\n') + print ('Content-type: text/xml', end='\r\n') + print ('', end='\r\n') print ('') print ('') From fb52105676e8be90b4b83c47fbd758ed3a08315f Mon Sep 17 00:00:00 2001 From: ExoArchive Administrator Date: Thu, 10 Sep 2026 20:36:36 -0700 Subject: [PATCH 3/3] additional http formatting --- TAP/tap.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/TAP/tap.py b/TAP/tap.py index a39265b..0f73ba4 100755 --- a/TAP/tap.py +++ b/TAP/tap.py @@ -1905,7 +1905,7 @@ def __printStatus__(self, key, retval, outtype, **kwargs): if(outtype == 'xml'): - print("Content-type: text/xml\r") + print("Content-type: application/xml\r") print("\r") print('') @@ -2005,7 +2005,7 @@ def __getStatus__(self, workdir, workspace, key, param, **kwargs): if(len(key) == 0): print("HTTP/1.1 200 OK\r") - print("Content-type: text/xml\r") + print("Content-type: application/xml\r") print("\r") print(data) sys.exit() @@ -2261,7 +2261,7 @@ def __getStatus__(self, workdir, workspace, key, param, **kwargs): if(format == 'json'): print("Content-type: application/json\r") elif(format == 'votable'): - print("Content-type: text/xml\r") + print("Content-type: application/xml\r") else: print("Content-type: text/plain\r") print("\r") @@ -2310,7 +2310,7 @@ def __printError__(self, fmt, errmsg, errcode): print(httphdr) - print("Content-type: text/xml\r") + print("Content-type: application/xml\r") print("\r") print('') @@ -2328,7 +2328,7 @@ def __printError__(self, fmt, errmsg, errcode): """ if(fmt == 'votable'): - print("Content-type: text/xml\r") + print("Content-type: application/xml\r") print("\r") print('') @@ -2432,7 +2432,7 @@ def __printSyncResult__(self, resultpath, format, **kwargs): if(format == 'json'): print("Content-type: application/json\r") elif(format == 'votable'): - print("Content-type: text/xml\r") + print("Content-type: application/xml\r") else: print("Content-type: text/plain\r") print("\r") @@ -2835,7 +2835,8 @@ def __printVosiAvailability__ (self, **kwargs): # print ('HTTP/1.1 200 OK', end='\r\n') - print ('Content-type: text/xml', end='\r\n') + print ('Content-type: application/xml', end='\r\n') + print ('Connection: close', end='\r\n') print ('', end='\r\n') print ('') @@ -2847,6 +2848,7 @@ def __printVosiAvailability__ (self, **kwargs): print (' true') print (' TAP service available.') print ('') + print (end='\r\n') sys.exit() @@ -2871,8 +2873,9 @@ def __printVosiCapability__ (self, **kwargs): # print ('HTTP/1.1 200 OK', end='\r\n') - print ('Content-type: text/xml', end='\r\n') - print ('', end='\r\n') + print ('Content-type: application/xml', end='\r\n') + print ('Connection: close', end='\r\n') + print (end='\r\n') print ('') print ('') @@ -2976,6 +2979,7 @@ def __printVosiCapability__ (self, **kwargs): print (' ') print ('') print ('') + print (end='\r\n') sys.exit()