user1230890
user1230890

Reputation: 51

JAX-WS Java client, WCF service interoperability: "400: Bad Request"

I have also asked this question on the Mirth forum.

We are currently trying to connect to a WCF service using the open source health care integration engine Mirth . Mirth is Java-based, uses Mule internally which uses JAX-WS. The WCF server is returning HTTP status code "400: Bad Request". We don’t have easy access to the WCF server.

Communication with the client works well in C#. In Visual Studio, add Service Reference, and then in main():

PatientRegistryQueryFulfiller.GetDemographicsClient svc = new PatientRegistryQueryFulfiller.GetDemographicsClient();
doc.Load(@"C:\MirthTesting\PRPA_EX201307NO_10_PatientReg_GetDemographics.xml");
PatientRegistryQueryFulfiller.PRPA_IN201307NO patientRegistryRequest = (PatientRegistryQueryFulfiller.PRPA_IN201307NO)ObjectSerializer.DeserializeObject(doc, typeof(PatientRegistryQueryFulfiller.PRPA_IN201307NO));
PatientRegistryQueryFulfiller.PRPA_IN201307NOResponse patientRegistryResponse = svc.GetDemographics(patientRegistryRequest);
doc = ObjectSerializer.SerializeObject(patientRegistryResponse.Item);

The autogenerated WCF client (from the WSDL) has an app.config with an endpoint, and this binding:

<bindings>
    <basicHttpBinding>
        <binding name="PatientRegistryQueryFulfiller_Binding" closeTimeout="00:01:00"
         openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
         allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
         maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
         messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
         useDefaultWebProxy="true">
        <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="655360"
           maxBytesPerRead="4096" maxNameTableCharCount="655360" />
        <security mode="None">
        <transport clientCredentialType="None" proxyCredentialType="None"
             realm="" />
        <message clientCredentialType="UserName" algorithmSuite="Default" />
      </security>
    </binding>
  </basicHttpBinding>
</bindings>

The only thing done differently in the WCF-solution is extending the maxNameTableCharCount and maxArrayLength in the readerQuotas tag for the binding in question, the rest is left with default values. However, I have not found a way to set these in Mirth, should this indeed be the reason for the error.

We’re running Mirth v 2.2.1 (most recent checkout), and the Mirth Channel is set to both read and send a HL7v3 document. The problem arises only when trying to communicate with the WCF-service. The destination is a Web Service Sender, with both service and port read from the WSDL. There is no authenthication, and the envelope is generated from the only available operation. We’re not using MTOM.

We’re fairly certain that this is related to JAX-WS and WCF interoperability. Any general hints?

We’ve tried setting properties on JAX-WS connection. Deep in the bowels of mirth, we’ve tried setting the http chunk size: dispatch.getRequestContext().put(JAXWSProperties.HTTP_CLIENT_STREAMING_CHUNK_SIZE, 8192) (WebServiceMessageDispatcher.Java line 140)

The stack trace is as follows:

ERROR-410: Web Service Connector error
ERROR MESSAGE: Error connecting to web service.
com.sun.xml.internal.ws.client.ClientTransportException: The server sent HTTP status code 400: Bad Request
at com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.checkStatusCode(Unknown Source)
at com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.process(Unknown Source)
at com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.processRequest(Unknown Source)
at com.sun.xml.internal.ws.transport.DeferredTransportPipe.processRequest(Unknown Source)
at com.sun.xml.internal.ws.api.pipe.Fiber.__doRun(Unknown Source)
at com.sun.xml.internal.ws.api.pipe.Fiber._doRun(Unknown Source)
at com.sun.xml.internal.ws.api.pipe.Fiber.doRun(Unknown Source)
at com.sun.xml.internal.ws.api.pipe.Fiber.runSync(Unknown Source)
at com.sun.xml.internal.ws.client.Stub.process(Unknown Source)
at com.sun.xml.internal.ws.client.dispatch.DispatchImpl.doInvoke(Unknown Source)
at com.sun.xml.internal.ws.client.dispatch.DispatchImpl.invoke(Unknown Source)
at com.mirth.connect.connectors.ws.WebServiceMessageDispatcher.processMessage(WebServiceMessageDispatcher.java:176)
at com.mirth.connect.connectors.ws.WebServiceMessageDispatcher.doDispatch(WebServiceMessageDispatcher.java:106)
at com.mirth.connect.connectors.ws.WebServiceMessageDispatcher.doSend(WebServiceMessageDispatcher.java:204)
at org.mule.providers.AbstractMessageDispatcher.send(AbstractMessageDispatcher.java:164)
at org.mule.impl.MuleSession.sendEvent(MuleSession.java:191)
at org.mule.impl.MuleSession.sendEvent(MuleSession.java:130)
at org.mule.routing.outbound.AbstractOutboundRouter.send(AbstractOutboundRouter.java:85)
at org.mule.routing.outbound.FilteringMulticastingRouter.route(FilteringMulticastingRouter.java:54)
at org.mule.routing.outbound.OutboundMessageRouter$1.doInTransaction(OutboundMessageRouter.java:78)
at org.mule.transaction.TransactionTemplate.execute(TransactionTemplate.java:48)
at org.mule.routing.outbound.OutboundMessageRouter.route(OutboundMessageRouter.java:82)
at org.mule.impl.model.DefaultMuleProxy.onCall(DefaultMuleProxy.java:247)
at org.mule.impl.model.seda.SedaComponent.doSend(SedaComponent.java:209)
at org.mule.impl.model.AbstractComponent.sendEvent(AbstractComponent.java:277)
at org.mule.impl.MuleSession.sendEvent(MuleSession.java:201)
at org.mule.routing.inbound.InboundMessageRouter.send(InboundMessageRouter.java:176)
at org.mule.routing.inbound.InboundMessageRouter.route(InboundMessageRouter.java:143)
at org.mule.providers.AbstractMessageReceiver$DefaultInternalMessageListener.onMessage(AbstractMessageReceiver.java:487)
at org.mule.providers.AbstractMessageReceiver.routeMessage(AbstractMessageReceiver.java:266)
at org.mule.providers.AbstractMessageReceiver.routeMessage(AbstractMessageReceiver.java:225)
at com.mirth.connect.connectors.vm.VMMessageReceiver.getMessages(VMMessageReceiver.java:223)
at org.mule.providers.TransactedPollingMessageReceiver.poll(TransactedPollingMessageReceiver.java:108)
at org.mule.providers.PollingMessageReceiver.run(PollingMessageReceiver.java:97)
at org.mule.impl.work.WorkerContext.run(WorkerContext.java:290)
at edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1061)
at edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:575)
at java.lang.Thread.run(Unknown Source)

Upvotes: 5

Views: 7792

Answers (1)

JanBrogger
JanBrogger

Reputation: 31

We found a solution. Long description of the solution on the Mirth forum.

The problem was that the web services gave us a "400: bad request" when we tried to send it a request that we thought was meaningful. "400 bad request" is a very generic and not so informative error.

A solution for testing

This solution creates a debuggable file-based testing harness that you can use to understand what Mirth does. It is not a production-quality solution. YMMV.

  • Install Mirth
  • Download the WSDL files of the web service (e.g. EncounterManager) and put them under Mirth Server\public_html\EncounterManager so that Mirth can host them.
  • In the local WSDL file above, modify it and make sure that the SOAP action at the end points to the actual web service:

soap:address location="http://your-server/HL7Connector/GetDemographicsService30/"/

  • Restart Mirth if it is already started.
  • In Mirth Connect, create a new channel, for example Sender
  • Just for testing, make the Source of this channel a file reader that reads something in (e.g. a patient identifier from a text file). For example, make it poll C:\MirthTesting\unread. Tell Mirth to move the file after it is finished to C:\MirthTesting\read
  • On the summary page of the new transformer, click "Set data types".
  • Set data types to Source connector inbound = Delimited text, Source connector outbound=HL7, Destination 1 outbound = Delimited text. This is just for testing, you can do fancy HL7 stuff later.
  • In the source, click on Edit transformer.
  • Click "Add new step", enter the new variable name "patientIdWanted" and enter for the mapping, "messageObject.getRawData()". This makes Mirth read the content of whatever text file you put into the "Unread" directory and put into a variable that you can use later (into the channel map)
  • Make the Destination of your new channel into a Web service sender. Enter the URL of the local WSDL (e.g. http://localhost:8080/EncounterManager/EncounterManagerQueryFulfiller.wsdl)
  • Click Get operations, then Generate Envelope.
  • The SOAP envelope is very large and full of HL7 non-sense (to me). Replace it with a simple example that works, and remove all the unncessary HL7 cruft. Your provider should give you a working example.
  • You now need to put the variables you read in from the file reader step, into the SOAP envelope. Your SOAP envelope should contain a "payload", e.g. the patient identifier somewhere. For the GetDemographics query, it looked like this (portion of it). Note that the ${patientIdWanted} is where Mirth replaces the value in the template, from whatever we put into the channel map from the text file above.
  • Now save this channel.
  • Create a new channel to receive what the web service in the previous step sent you. Call it Receiver
  • In the new channel, set the data types all to "Delimited text". Again, just for testing.
  • Set the source of the "Receiver" channel to "Channel reader"
  • Set the destination of the new channel to File writer. Enter a directory and a file name, e.g. C:\MirthTesting\read\webservice-response.txt. In the template, enter ${message.rawData} to see everything that Mirth has.
  • Go back to the Sender channel, select the destination, enter the Receiver channel as the destination of the response.
  • Save changes, validate connector, redeploy all channels.
  • Now create a text file with a patient identifier in it, and put it into C:\MirthTesting\unread
  • Mirth will read this file (then move it to the "read" directory). Your first channel will get the text file in, move the content into the channel map ${patientIdWanted}. Your web service will then get the SOAP envelope with this patient identifier in it. The response will be sent to the Receiver channel, and dumped as plain text.

Stuff that was helpful

  • Realizing that Mirth is a nuclear sub: powerful and mysterious. No manual!
  • Being somewhat proficient in C# and Java
  • Having examples of HL7 messages that make sense
  • Using Visual Studio to generate a pure C# client to verify that we could read from the web service with plain C#. (We used Mirth to host the WSDL files, but you could use IIS)
  • Using Eclipse to generate a Java client to verify that we could read from the web service with Java. We used the SOAPUI (the same as Mirth uses) with Eclipse Indigo EE edition (from the command line using wsimport.bat)
  • Downloading Mirth from source and running it from Eclipse.
  • Turning on HTTP dumping (in Eclipse). Enter under "Run config", VM arguments. This lets you see exactly what Mirth (or your Java client) sends to the web service: -Dcom.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump=true

  • Add a pre-processing script to your sender channel to see what Mirth actually does:

    FileUtil.write('C:/MirthTesting/read/sender_preprocessmessage_in.txt', false, message);return message;
    
  • Add a post-procesing script to your sender channel to see what Mirth does:

    FileUtil.write('C:/MirthTesting/read/dipssender_postprocessmessage.txt', false, message);
    return;
    

One code change

To actually make this work, we had to make one change to the Mirth source code. With JDK1.7 that we used, Mirth didn't actually send the SOAP action. In Server/.../WebServiceMessageDispatcher.Java line 137, we had to add:

    dispatch.getRequestContext().put(BindingProvider.SOAPACTION_USE_PROPERTY, true);

(taken from this blog post)

Upvotes: 3

Related Questions