Connecting to Quest Lab 360 Using SOAP

How the plumbing works

Contents

The Quest 360 interface uses HTTP basic authentication over HTTPS which is quite easy to implement. The error messages the web service gave good hints that this would be the case. It’s a very common way to implement authentication with web services. If you get the return HTTP code of 401 it indicates the user name and password were wrong.

Then it was a matter of taking example SOAP envelope from the Quest diagnostics documentation and slotting in the HL7 message payload encoding it in base64.

That’s what this code does:

T = [==[<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ord="http://medplus.com/orders">
   <soapenv:Header/>
   <soapenv:Body>
      <ord:submitOrder soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
         <order xsi:type="java:Order" xs:type="type:Order" xmlns:java="java:com.medplus.serviceHub.orders.webservice" xmlns:xs="http://www.w3.org/2000/XMLSchema-instance">
            <hl7Order xsi:type="xsd:base64Binary">${globalCleanHL7}</hl7Order>
         </order>
      </ord:submitOrder>
   </soapenv:Body>
</soapenv:Envelope>]==]

function MakePayload(HL7)
   local P = xml.parse{data=T}
   P["soapenv:Envelope"]["soapenv:Body"]["ord:submitOrder"].order.hl7Order[2]
       = base64.enc(HL7)
   return P 
end

I used a simple approach of defining an XML template and then slotting in the base64 encoded message. From there it’s a matter of calling the web service:

function PlaceOrder(HL7, User, Password) 
   local E = MakePayload(HL7)
   trace(E)
   local D = net.http.post{
         url=Url, 
         auth={username=User, password=Password},
         headers={['Content-Type']='text/xml',
         SOAPAction=Url..'/submitOrder'}, 
      body=E:S(), live=true}
   return xml.parse{data=D}
end

It wasn’t all that hard to get that working, the since Translator gives us immediate feedback with the error messages; which made it simple to figure out the required Content-Type and SOAPAction headers.

The next step is to decipher what we got back, there are three of possibilities:

  • A SOAP fault response if you really screw things up like sending an empty string instead of an HL7 order message
  • Or a normal response, containing a valid HL7 message
  • A normal response, containing error information related to the HL7 message

When the Quest interface returns an error we need to identify whether it’s BEA logic stuff or the core system. It’s a rather leaky abstraction! So far the following code seems to be adequate to figure out what type of response we have and extract error messages in a useful manner:

-- Returns nil if we do not have a SOAP fault response
function IsFault(X)  
   return X["env:Envelope"]["env:Body"]["env:Fault"] 
end

-- Extracts the response message from a non fault response
function GetResponse(X)
   return X["env:Envelope"]["env:Body"]
    ["m:submitOrderResponse"].result.responseMsg[2]:S()
end

-- Extracts the error message from a fault response
function GetFault(X)
   return X["env:Envelope"]["env:Body"]["env:Fault"].
      detail["bea_fault:stacktrace"][2]:S()
end