UnsupportedMediaException -> how do you get the actual response? - java

I'm calling a remote web service and am occasionally getting the following error:-
Error caught: com.sun.xml.internal.ws.server.UnsupportedMediaException: Unsupported Content-Type: text/plain;charset=ISO-8859-1 Supported ones are: [text/xml]
Does anyone know how to get the actual message that was returned by the server? It sounds like it might be text or a web page but I'm unable to get it.
I can catch the UnsupportedMediaException but I don't know what to do to extract the actual response. Here's the code:-
val selectedDate = exchange.`in`.getHeader("selectedDate").toString()
val accountNumberMinor = exchange.`in`.getHeader("accountNumberMinor").toString()
val accountNumberMajor = exchange.`in`.getHeader("accountNumberMajor").toString()
val accountIdentifier = if (accountNumberMinor.trim() != "") accountNumberMinor else accountNumberMajor
val effectiveDate = SimpleDateFormat("yyyy-MM-dd").parse(selectedDate)
val response = webRequest.getResponse(accountIdentifier, selectedDate)
val result = response.result as FixedIncomeCurrencyForwardAccountV10Result
Thanks,
Adam

An HTML page is usually a server error yes. Probably a static service page (like 404 or 5xx). It could even be an error in your request that should be returned as a SOAPFault, but is not implemented as such by the specific server.
Sometimes the server does communicate a valid SOAP (Fault) message, but the content type header is just wrong. In that case you're better off rewriting the Content-Type from the response with a proxy server. See for references on this subject:
SOAP unsupported media exception text/plain Supported ones are: [text/xml]
So, what can you do to view the HTML content?
With JAX-WS you can enable all HTTP web service traffic to be logged to System.out with the following vm options:
-Dcom.sun.xml.ws.transport.http.client.HttpTransportPipe.dump=TRUE
-Dcom.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump=TRUE
-Dcom.sun.xml.ws.transport.http.HttpAdapter.dump=TRUE
-Dcom.sun.xml.internal.ws.transport.http.HttpAdapter.dump=TRUE
-Dcom.sun.xml.internal.ws.transport.http.HttpAdapter.dumpTreshold=999999
See for references:
https://www.rgagnon.com/javadetails/java-logging-with-jax-ws.html
https://www.javatips.net/api/com.sun.xml.ws.transport.http.client.httptransportpipe
Now, this will dump all http requests and responses, but you might only be interested in the ones where you don't get soap/xml.
So, what else can you do?
You could set these options programmatically and re-send the request when you catch the UnsupportedMediaException. But in the time this takes the error might have disappeared. Note that these properties are cached, so setting them needs to go through com.sun.xml.ws.transport.http.client.HttpTransportPipe#setDump(Boolean)
If you're willing to switch to the JAX-WS runtime, you could also create your own com.sun.xml.ws.api.pipe.TransportTubeFactory since jaxws-rt can load custom instances of this factory. I have successfully created my own TransportTubeFactory that uses a custom com.sun.xml.ws.transport.http.client.HttpTransportPipe (by extending it and overriding processRequest) that reads the http response from the com.sun.xml.ws.api.pipe.Codec upon catching the UnsupportedMediaException. By wrapping the Codec you can store the input stream on the decode method call.
This runtime is nearly identical to the internal runtime, and should be fully compatible.
This may also work with the internal classes from the Java runtime, but since those are located in RT.jar it's difficult to depend on it and build your project. So i would advice switching to the external JAX-WS runtime.
What you then do with the input stream is up to you (which is the body of the HTTP response at the moment of catching the UnsupportedMediaException).
Note that you can also rewrite most* content type headers in code with this codec wrapper.
See for reference how to add your own implementation of this factory via META-INF/services here:
https://www.javadoc.io/doc/com.sun.xml.ws/jaxws-rt/latest/com.sun.xml.ws/com/sun/xml/ws/api/pipe/TransportTubeFactory.html
In short:
Create a file in META-INF/services called com.sun.xml.ws.api.pipe.TransportTubeFactory
The contents of this file should be a single line with the full class name of your custom factory, for example my.soap.transport.MyTransportTubeFactory
Note; if you're using the classes from the Java runtime instead of jaxws-rt, use com.sun.xml.internal.ws as the package for everything in this post that references com.sun.xml.ws.
*Note: newer versions of this runtime (jaxws-rt-2.3.x or jre 8+ internal) throw a different exception on text/html responses. Sadly before calling Codec.decode. So in that case you would have to copy more code into your custom HttpTransportPipe. text/plain currently still works though.
Some snippets of my code:
public class TransportTubeFactoryImpl extends TransportTubeFactory {
#Override
public Tube doCreate(ClientTubeAssemblerContext context) {
String scheme = context.getAddress().getURI().getScheme();
if (scheme != null) {
if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")) {
CodecWrapper codec = new CodecWrapper(context.getCodec());
return new HttpTransportPipeImpl(codec, context.getBinding());
}
}
throw new WebServiceException("Unsupported endpoint address: " + context.getAddress());
}
}
public class CodecWrapper implements Codec {
private Codec wrapped;
public CodecWrapper(Codec wrapped) {
this.wrapped = wrapped;
}
#Override
public void decode(InputStream in, String contentType, Packet response) throws IOException {
copyInputStream(in); // todo: implement this
wrapped.decode(in, contentType, response);
}
}
public class HttpTransportPipeImpl extends HttpTransportPipe {
private CodecWrapper codec;
public HttpTransportPipeImpl(CodecWrapper codec, WSBinding binding) {
super(codec, binding);
this.codec = codec;
}
#Override
public NextAction processRequest(Packet request) {
try {
return super.processRequest(request);
} catch (UnsupportedMediaException ex) {
// todo: here you can access the stored data from the codec wrapper
}
}
}
I have also created a complete working demonstration of this principle on my github: https://github.com/s-lindenau/SoapContentTypeDemo
If you still have the option to switch to a completely different client library, you could also check Apache CXF:
How can I make jaxws parse response without checking Content-Type header

Related

Extracting HTTP status codes from the MethodOutcome/ outcome of a create operation in HAPI FHIR

I want to extract the HTTP status of a HAPI FHIR create Method.
MethodOutcome outcome = client.create().resource(medicationOrders[0]).prettyPrint().encodedXml().execute();
Is there any way to recover it from the MethodOutcome or any other workaround exists?
There are a few things that can be useful..
If the method returns successfully, then you have gotten an HTTP 2xx response back. There isn't a way to tell if it was a 200 or a 204 for example, but it was a successful response.
If the method throws a BaseServerResponseException of some sort, the server returned a 4xx or 5xx status code. You can call BaseServerResponseException#getStatusCode() to find out which one.
If you need to know the exact response in all cases, you can use a client interceptor to find that.
You can obtain status code using Kotlin like this with client interceptors,
Create an interceptor to pick status codes,
private fun createClientInterceptor(statusCodes: MutableList<Int>):
IClientInterceptor {
return object : IClientInterceptor {
override fun interceptRequest(theRequest: IHttpRequest?) {}
override fun interceptResponse(theResponse: IHttpResponse?) {
if (theResponse != null) {
println(theResponse.status)
}
}
}
}
Create a client and register the interceptor
val ctx = FhirContext.forR4()!!
val restfulGenericClient = ctx.newRestfulGenericClient(getServerUrl())
restfulGenericClient.registerInterceptor(createClientInterceptor(statusCodes))
In this way, you can collect status codes of responses in Kotlin, appropriately you can change the code to Java as well.

How to selectively GZIP encode POST and PUT requests

I'm using Jersey on both the server and client of a web application. On the server I have Interceptors as noted in https://jersey.java.net/documentation/latest/filters-and-interceptors.html to handle GZIP compression going out and coming in. From the server side, it's easy enough to select which resource methods are compressed using the #Compress annotation. However, if I also want to selectively compress entities from the Client to the Server, what's the best way to do that?
I had started adding a Content-Encoding: x-gzip header to the request, but my client side Interceptor does not see that header (presumably because it's not an official client side header).
Before you point to section 10.6 of the Jersey documentation, note that this works for the Server side. Although I could do something similar on the Client, I don't want to restrict it by URL. I'd rather control the compression flag as close to the request as possible (i.e. Header?).
Here's what I have so far, but it does not work since my header is removed:
class GzipWriterClientInterceptor implements WriterInterceptor {
private static final Set<String> supportedEncodings = new GZipEncoder().getSupportedEncodings(); //support gzip and x-gzip
#Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
if (supportedEncodings.contains(context.getHeaders().getFirst(HttpHeaderConstants.CONTENT_ENCODING_HEADER))) {
System.out.println("ZIPPING DATA");
final OutputStream outputStream = context.getOutputStream();
context.setOutputStream(new GZIPOutputStream(outputStream));
} else {
context.headers.remove(HttpHeaderConstants.CONTENT_ENCODING_HEADER) //remove it since we won't actually be compressing the data
}
context.proceed();
}
}
Sample Request:
Response response = getBaseTarget().path(getBasePath()).path(graphic.uuid.toString())
.request(DEFAULT_MEDIA_TYPE)
.header(HttpHeaderConstants.CONTENT_ENCODING_HEADER, MediaTypeConstants.ENCODING_GZIP)
.put( Entity.entity(graphic, DEFAULT_MEDIA_TYPE))
I also have a logging filter as well that shows all the request headers. I've simplified the above, but all other headers I add are logged.

How to send a large input stream to a Spring REST service?

Have a Spring Rest application that run inside an embedded Jetty container.
On Client I use RestTemplate(try to).
Use case :
Having an InputStream (I don't have the File), I want to send it to the REST service.
The InputStream can be quite large (no byte[] !).
What I've tried so far :
Added StandardServletMultipartResolver to the Dispatcher context;
On servlet registration executed :
ServletRegistration.Dynamic dispatcher = ...
MultipartConfigElement multipartConfigElement = new MultipartConfigElement("D:/temp");
dispatcher.setMultipartConfig(multipartConfigElement);
On client :
restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
parts.add("attachmentData", new InputStreamResource(data) {
// hacks ...
#Override
public String getFilename() {
//avoid null file name
return "attachment.zip";
}
#Override
public long contentLength() throws IOException {
// avoid calling getInputStream() twice
return -1L;
}
}
ResponseEntity<Att> saved = restTemplate.postForEntity(url, parts, Att.class)
On server :
#RequestMapping("/attachment")
public ResponseEntity<Att> saveAttachment(#RequestParam("attachmentData") javax.servlet.http.Part part) {
try {
InputStream is = part.getInputStream();
// consume is
is.close();
part.delete();
return new ResponseEntity<Att>(att, HttpStatus.CREATED);
}
}
What is happening :
The uploaded InputStream is stored successfully in the configured temp folder (MultiPart1970755229517315824), the Part part parameter is correctly Injected in the handler method.
The delete() method does not delete the file (smth still has opened handles on it).
Anyway it looks very ugly.
Is there a smoother solution ?
You want to use HTTP's Chunked Transfer Coding. You can enable that by setting SimpleClientHttpRequestFactory.setBufferRequestBody(false). See SPR-7909.
You should rather use byte[], and write a wrapper around the webservice to actually send the "large string" in chunks. Add a parameter in the webservice which will indicate the "contentID" of the content, so that the other side knows this part belongs to which half-filled "bucket". Another parameter "chunkID" would help in sequencing of the chunks on the other side. Finally, third parameter, "isFinalChunk" would be set if whatever you are sending is the final thing. This is pretty non-fancy functionality achievable in less than 100 lines of code.
The only issue with this is that you end up making "n" calls to the webservice rather than just one call, which would aggregate the connect delays etc. For realtime stuff, some more network QoS is required, but otherwise you should be fine.
I think this is much simpler, and once you have your own class wrapper to do this simple chopping and gluing, it is scalable to a great extent if your server can handle multiple webservice calls.

Java Servlet API - How can I set the response status and reason phrase without commiting the response

I am writing a REST application using Tomcat and Spring WebMVC.
I want to signal errors to my client using HTTP status codes along with some XML payload that contains more information about what went wrong.
To catch all errors regardless of where they occur, I have written a Filter which wraps the response and overrides the sendError() method:
private static final class GenericErrorResponseWrapper
extends HttpServletResponseWrapper
{
#Override
public void sendError(int sc, String msg) throws IOException {
final HttpServletResponse wrappedResponse = (HttpServletResponse) getResponse();
wrappedResponse.setStatus(sc, msg);
wrappedResponse.setContentType("application/xml");
PrintWriter writer = wrappedResponse.getWriter();
try {
SimpleXmlWriter xmlWriter = SimpleXmlWriterWrapper.newInstance(writer);
xmlWriter.writeStartElement("ns2", "genericError")
.writeAttribute("xmlns:ns2", "http://mynamespace")
.writeCharacters(msg)
.writeEndDocument().flush();
writer.flush();
wrappedResponse.flushBuffer();
} finally {
writer.close();
}
}
}
This implementation has two problems:
It generates a deprecation warning in Eclipse, since HttpServletResponse.setStatus(sc, msg) is deprecated.
The HTTP response header generated by Tomcat is not correct, it starts with the first line "HTTP/1.1 500 OK". 500 is correct, but instead of OK the reason phrase should be "Internal Server Error".
How can I implement my filter so that it does the right thing and is free of deprecation warnings? Both alternatives named in the Javadoc are not usable for me:
sendError(sc, msg) is not usable, since it commits the response body and I can't write XML payload any more
setStatus(sc) with just the error code is theoretically usable, but it also creates the hardcoded "OK" string in the first line of the response header.
There is unfortunately no way to avoid the deprecation warning. As you already mention yourself, the two alternatives which are referred to in the API documentation do not cover the same functionality. You may of course annotate your method with #SuppressWarnings("deprecation") to indicate that the usage of the deprecated method is intended.
The other thing, that Tomcat does not use your message string, even if one is provided, is a configuration issue. For some strange reason, Tomcat will by default ignore the provided message string and use a default error message based on the passed return code. You must set the system property org.apache.coyote.USE_CUSTOM_STATUS_MSG_IN_HEADER to true to force Tomcat to use your provided error message instead. More details on this can be found in the Tomcat documentation.
As an alternative answer - you could first write the XML payload, without calling flush/flushBuffer, and only after that do sendError(int, String), which would flush the buffer.

Getting raw XML response from Java web service client

I am trying to get the raw XML response from a web service, instead of the usual set of POJOs.
I am using a webservice client that I generated (so I have access to the client's code) from a WSDL and some schemas. The client is generated in RAD 7.5, I think using JAX-WS. I've been looking at the client code itself, but I'm not even sure if the client code ever handles raw XML or if it passes it off to other libraries.
You can do it using
javax.xml.ws.handler.soap.SOAPHandler<javax.xml.ws.handler.soap.SOAPMessageContext>
you can simply get message using SOAPMessageContext#getMessage() and convert message to String using method
public static String getXmlMessage(SOAPMessage message) throws Exception
{
ByteArrayOutputStream os = new ByteArrayOutputStream();
message.writeTo(os);
final String encoding = (String) message.getProperty(SOAPMessage.CHARACTER_SET_ENCODING);
if (encoding == null)
{
return new String(os.toByteArray());
}
else
{
return new String(os.toByteArray(), encoding);
}
}
Also you can read here about SOAP handler on client side
Article
It's not widely documented, but you can use the Dispatch interface to implement JAXWS clients which work directly w/ the XML. Here and here are some articles for getting started.

Categories

Resources