Keep-Alive and Custom HTTP Headers

DateVersionAuthorDescription
10/11/20160.1Tabassum JafriInitial Draft
10/19/20160.2Tabassum JafriUpdated after team discussion 
02/24/20170.3Minh-Hai NguyenUpdated with custom httpheader

Overview

In the United States Social Security Administration (SSA) workflow, a proxy server sits in between the initiating and responding gateways. All NwHIN Outbound requests goes through this proxy server. The proxy server has a 2 minute timeout setting that's is strictly enforced. One of the responding gateway is taking longer than 2 minutes which causes the proxy server to close the connection and this in turns forces CONNECT to throw a SocketTimeout   exception. 

Adding Keep-Alive and Custom Headers

CONNECT provides two methods for adding Keep-Alive and/or custom headers:

  1. In gateway.properties
  2. Within the assertion block of the entity request

Custom header hierarchy

When Keep-Alive has been enabled/disabled in both the assertion block and in gateway.properties, the setting in the assertion block will override the setting in gateway.properties. Likewise, if custom headers with the same names are set in both places, the value set in the assertion block will override the value set in gateway.properties

Gateway.properties configuration

Enabling or disabling Keep-Alive is done by simply setting the property to "true" or "false"

connectionKeepAlive=false - connection:Keep-Alive will not be added to the header
connectionKeepAlive=true - connection:Keep-Alive will be added to the header

Setting a custom header requires adding a new property to gateway.properties in the following format:

customHttpHeaders.<CUSTOM_HEADER_NAME>=<CUSTOM_HEADER_VALUE>

Assertion block configuration

Enabling or disabling Keep-Alive and adding custom headers is done by adding the following XML entries into the assertion block of the CONNECT entity request:

<urn:assertion>
	.............

	<urn3:keepAlive>true or false</urn3:keepAlive>

	<urn3:CONNECTCustomHttpHeaders>
		<urn3:headerName><CUSTOM_HEADER_NAME></urn3:headerName>
		<urn3:headerValue><CUSTOM_HEADER_VALUE></urn3:headerValue>
	</urn3:CONNECTCustomHttpHeaders>

	<urn3:CONNECTCustomHttpHeaders>
		<urn3:headerName><CUSTOM_HEADER_NAME></urn3:headerName>
		<urn3:headerValue><CUSTOM_HEADER_VALUE></urn3:headerValue>
	</urn3:CONNECTCustomHttpHeaders>

</urn:assertion>


Keep-Alive adapter persistence

If you want connection: Keep-Alive to be included in internal CONNECT adapter calls, set the following to true in gateway.properties:

readHttpHeaders=true

Understanding HTTP Keep-Alive Header

Keep-Alive is an HTTP header that maintains a persistent connection between client and server, preventing a connection from breaking intermittently. Also known as HTTP keep-alive, it can be defined as a method to allow the same TCP connection for HTTP communication instead of opening a new connection for each new request.


TCP keep alive

HTTP Keep-alive is different from TCP keep alive. TCP keep alive keeps TCP connection opened by sending small packets. Additionally, when the packet is sent this serves as a check so the sender is notified as soon as connection drops (note that this is NOT the case otherwise - until we try to communicate through TCP connection we have no idea if it is ok or not).

HTTP Keep-Alive Turned On

Pros:

  1. The cost of creating a SSL/HTTPS connection is very high, reusing the same connection will enhance performance.

Cons:

  1. Request at initiating gateway will fail, if connections were not being released back to the connection pool either due to slow connections or responses.
  2. Responsibility of closing the connection is on the Client.
  3. Enabling Keep-Alive on the server, will keep the TCP/IP connection open until the client closes it, or it expires on the server. It will be important to set up a correct time-out value, just in case there are too many open connections not being closed by the client.
  4. Keep-Alive actually increase server load. Each open connection consumes memory as well as a file descriptor (in Linux environment) and in extreme cases (some Apache configs) it may have a 1:1 mapping from connections to processes.

Business Requirement

  1. Keep-Alive and custom http header should be configurable on a message by message basis. 

Design Approach

Keeping in mind the business requirements, we plan to introduce a new custom header element for keep-alive and range of custom http headers in SOAP header. The Keep-Alive will be an optional element of String type and CustomHttpHeader will be a list of CONNECTCustomHttpHeadersType type which contains headerName and headerValue. The element in message will look like this:

<urn3:keepAlive>True</urn3:keepAlive>

<urn3:CONNECTCustomHttpHeaders>
<urn3:headerName>Custom2</urn3:headerName>
<urn3:headerValue>correct2</urn3:headerValue>
</urn3:CONNECTCustomHttpHeaders>

When present in a request, CONNECT will create a persistent connection with the following parameters:

Connection: Keep-Alive, custom2=[correct2]

Propose solution will require schema change to entity requests and code updates that will ensure to read this element and create an http connection based on the value in the incoming request. Below are the list of components that may affected:

  • Common-Type
  • CONNECT WebServices
  • CONNECTCoreLib

Implementation:

Common-Type: 

Since the entity services uses asssertion block which comes from nhincommon schema, we will add a new element such as below:

nhincCommon.xsd
<xsd:complexType name="AssertionType">
        <xsd:sequence>
            <xsd:element name="keepAlive" type="xsd:string" minOccurs="0" />
			<xsd:element name="CONNECTCustomHttpHeaders" type="tns:CONNECTCustomHttpHeadersType" minOccurs="0" maxOccurs="unbounded" />
            <xsd:element name="address" type="tns:AddressType"/>
            <xsd:element name="dateOfBirth" type="xsd:string"/>
            <xsd:element name="explanationNonClaimantSignature" type="xsd:string"/>
            <xsd:element name="haveSecondWitnessSignature" type="xsd:boolean"/>
            <xsd:element name="haveSignature" type="xsd:boolean"/>
            <xsd:element name="haveWitnessSignature" type="xsd:boolean"/>
            <xsd:element name="homeCommunity" type="tns:HomeCommunityType"/>
            <xsd:element name="nationalProviderId" type="xsd:string"/>
            <xsd:element name="personName" type="tns:PersonNameType"/>
            <xsd:element name="phoneNumber" type="tns:PhoneType"/>
            <xsd:element name="secondWitnessAddress" type="tns:AddressType"/>
            <xsd:element name="secondWitnessName" type="tns:PersonNameType"/>
            <xsd:element name="secondWitnessPhone" type="tns:PhoneType"/>
            <xsd:element name="SSN" type="xsd:string"/>
            <xsd:element name="uniquePatientId" type="xsd:string" maxOccurs="unbounded"/>
            <xsd:element name="witnessAddress" type="tns:AddressType"/>
            <xsd:element name="witnessName" type="tns:PersonNameType"/>
            <xsd:element name="witnessPhone" type="tns:PhoneType"/>
            <xsd:element name="userInfo" type="tns:UserType"/>
            <xsd:element name="authorized" type="xsd:boolean"/>
            <xsd:element name="purposeOfDisclosureCoded" type="tns:CeType" minOccurs="0"/>
            <xsd:element name="samlAuthnStatement" type="tns:SamlAuthnStatementType" minOccurs="0"/>
            <xsd:element name="samlAuthzDecisionStatement" type="tns:SamlAuthzDecisionStatementType" minOccurs="0"/>
            <xsd:element name="samlSignature" type="tns:SamlSignatureType" minOccurs="0"/>
            <xsd:element name="samlIssuer" type="tns:SamlIssuerType" minOccurs="0"/>
            <xsd:element name="messageId" type="xsd:string" minOccurs="0"/>
            <xsd:element name="relatesToList" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
            <xsd:element name="implementsSpecVersion" type="xsd:string" minOccurs="0" />
            <xsd:element name="transactionTimeout" type="xsd:int" minOccurs="0" default="-1" />
        </xsd:sequence>
    </xsd:complexType>
    <xsd:complexType name="CONNECTCustomHttpHeadersType">
		<xsd:sequence>
			<xsd:element name="headerName" type="xsd:string"/>
			<xsd:element name="headerValue" type="xsd:string"/>
		</xsd:sequence>
	</xsd:complexType>
    <xsd:element name="CONNECTCustomHttpHeaders" type="tns:CONNECTCustomHttpHeadersType" />

CONNECT Core Lib:

We also introduce custom entries inside gateway.properties which contains configuration to turn on or off keep-alive and custom httpheader feature.  Below is a sample entry:

gateway.properties
# Allows for inbound custom HTTP headers to be read from inbound message and passed to the responder's adapters, default property value is false.
readHttpHeaders=false

# Sets outbound http connection value to "keep-alive", default property value is false ((For True, set to True or T).
connectionKeepAlive=false

! Custom HTTP headers can be created by adding properties prepended with "customHttpHeaders."
! Followed by the header name "=" the header value.
! For example: customHttpHeaders.testHeader2=value2

Below are sample what payload message looks like

Sample request:

entity message from soapUI
<soap:Envelope
xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:urn="urn:hl7-org:v3"
xmlns:urn1="urn:gov:hhs:fha:nhinc:common:nhinccommon"
xmlns:add="http://schemas.xmlsoap.org/ws/2004/08/addressing">
<soap:Header/>
<soap:Body testSuite="Entity_g0" testCase="Patient Discovery">
...
      <urn:RespondingGateway_PRPA_IN201305UV02Request>        
            ……
         </urn:PRPA_IN201305UV02>
		...
         <urn:assertion>           
			<urn1:nationalProviderId>1234567890</urn1:nationalProviderId>          
			<urn1:keepAlive>true</urn1:keepAlive>
			<urn1:CONNECTCustomHttpHeaders>
			  	<urn1:headerName>Custom2</urn1:headerName>
			  	<urn1:headerValue>correct2</urn1:headerValue>
			</urn1:CONNECTCustomHttpHeaders>
            <urn1:address>
....

Soap message will look like when going out to NHIN gateway

HTTP message over the wire
Address:https://localhost:8181/Gateway/PatientDiscovery/1_0/NhinService/NhinPatientDiscovery
Encoding: UTF-8
Http-Method: POST
Content-Type:application/soap+xml; charset=UTF-8
Headers:{accept-encoding=[gzip,deflate], connection=[Keep-Alive],Custom2=[correct2], Content-Length=[13284],content-type=[application/soap+xml;charset=UTF-8], Host=localhost:8080],User-Agent=[Apache-HttpClient/4.1.1 (java 1.5)]}
Payload: <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"><soap:Header><wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" soap:mustUnderstand="true"><saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_8e6bd4227e7c46ba9ae84295e3679cbc" IssueInstant="2016-10-25T01:52:51.051Z" Version="2.0"><saml2:Issuer Format="urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName">CN=SAML User,OU=SU,O=SAML.....