How can I set the comet event timeout on NIO2 protocol?
How to well handle the socket connection on NIO2 protocol?(e.g., close connection)
We have a simple servlet which implements Apache CometEvent for long polling connection on tomcat8. It works well when we used org.apache.coyote.http11.Http11NioProtocol, however, we have now change to using org.apache.coyote.http11.Http11Nio2Protocol and it will not work properly.
On NIO, the client can make a comet connection to a Connect servlet by POST and the other client can send message by POST to Trigger servlet. Every 300 seconds we will timeout the comet and the client app will make comet connection again.
The Connect servlet as below
public class Connect extends HttpServlet implements CometProcessor {
...
public void event(CometEvent event) throws IOException, ServletException {
HttpServletRequest request = event.getHttpServletRequest();
HttpServletResponse response = event.getHttpServletResponse();
if (event.getEventType() == CometEvent.EventType.BEGIN) {
String deviceid = request.getParameter("id");
MessageSender.getInstance().addConnection(deviceid, event);
request.setAttribute("org.apache.tomcat.comet.timeout", 300 * 1000);
event.setTimeout(300 * 1000);
} else if (event.getEventType() == CometEvent.EventType.ERROR) {
MessageSender.getInstance().removeConnection(event);
event.close();
} else if (event.getEventType() == CometEvent.EventType.END) {
MessageSender.getInstance().removeConnection(event);
event.close();
} else if (event.getEventType() == CometEvent.EventType.READ) {
throw new UnsupportedOperationException("This servlet does not accept data");
}
}
}
And we have another Trigger servlet for sending message to client:
public class Trigger extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
byte[] receieveByteArray = ByteUtil.getHttpServletRequestBody(req);
sendTrigger(req, resp, receieveByteArray);
}
private void sendTrigger(HttpServletRequest req, HttpServletResponse resp, byte[] trigger) throws IOException, ServletException
{
try
{
MessageSender.getInstance().sendTrigger(deviceId, trigger);
} catch (Exception e)
{
logger.error("Send trigger has thrown exception: ", e);
}
}
}
And the MessageSender class as below
public class MessageSender
{
private static final Map<String, CometEvent> connections = new ConcurrentHashMap<String, CometEvent>();
public void addConnection(String deviceId, CometEvent event) {
connections.put(deviceId, event);
}
public void removeConnection(CometEvent event) {
while (connections.values().remove(event)) {
}
public static MessageSender getInstance() {
return instance;
}
public void sendTrigger(String deviceId, byte[] triggerMessage) throws IOException, ConnectionNotFoundException {
CometEvent comet = connections.get(deviceId);
HttpServletResponse response = comet.getHttpServletResponse();
response.addHeader("Content-Length", Integer.toString(triggerMessage.length));
response.addHeader("Content-Language", "en-US");
ServletOutputStream servletOutputStream = response.getOutputStream();
servletOutputStream.write(triggerMessage);
servletOutputStream.flush();
servletOutputStream.close();
comet.close(); // add for NIO2
connections.remove(deviceId);
}
}
After we have changed the connector setting of tomcat http protocol to NIO2 as below
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS" connectionTimeout="60000"
keystoreFile="D:\localhost.jks" keystorePass="******" />
The timeout of event will not work as we have set it to 300 seconds, the comet connection will be disconnected after 60 seconds which I believe is the connector connection timeout. And there will have thrown an exception as below
28-Oct-2016 15:04:33.748 SEVERE [http-nio2-8443-exec-5] org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process Error reading request, ignored
java.lang.IllegalStateException: Reading not allowed due to timeout or cancellation
at sun.nio.ch.AsynchronousSocketChannelImpl.read(AsynchronousSocketChannelImpl.java:249)
at sun.nio.ch.AsynchronousSocketChannelImpl.read(AsynchronousSocketChannelImpl.java:297)
at org.apache.tomcat.util.net.SecureNio2Channel.read(SecureNio2Channel.java:792)
at org.apache.tomcat.util.net.Nio2Endpoint.awaitBytes(Nio2Endpoint.java:871)
at org.apache.coyote.http11.Http11Nio2Protocol$Http11ConnectionHandler.release(Http11Nio2Protocol.java:180)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:722)
at org.apache.tomcat.util.net.Nio2Endpoint$SocketProcessor.doRun(Nio2Endpoint.java:1073)
at org.apache.tomcat.util.net.Nio2Endpoint$SocketProcessor.run(Nio2Endpoint.java:1032)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
If the client make the comet connection again after this, and the other client try to send message to Trigger servlet. The comet will be END immediately and connection disconnected.
Any help is appreciated
Related
I have a simple netty connection pool and a simple HTTP endpoint to use that pool to send TCP messages to ServerSocket. The relevant code looks like this, the client (NettyConnectionPoolClientApplication) is:
#SpringBootApplication
#RestController
public class NettyConnectionPoolClientApplication {
private SimpleChannelPool simpleChannelPool;
public static void main(String[] args) {
SpringApplication.run(NettyConnectionPoolClientApplication.class, args);
}
#PostConstruct
public void setup() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.remoteAddress(new InetSocketAddress("localhost", 9000));
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new DummyClientHandler());
}
});
simpleChannelPool = new SimpleChannelPool(bootstrap, new DummyChannelPoolHandler());
}
#RequestMapping("/test/{msg}")
public void test(#PathVariable String msg) throws Exception {
Future<Channel> future = simpleChannelPool.acquire();
future.addListener((FutureListener<Channel>) f -> {
if (f.isSuccess()) {
System.out.println("Connected");
Channel ch = f.getNow();
ch.writeAndFlush(msg + System.lineSeparator());
// Release back to pool
simpleChannelPool.release(ch);
} else {
System.out.println("not successful");
}
});
}
}
and the Server (ServerSocketRunner)
public class ServerSocketRunner {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(9000);
while (true) {
Socket socket = serverSocket.accept();
new Thread(() -> {
System.out.println("New client connected");
try (PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));) {
String inputLine, outputLine;
out.println("Hello client!");
do {
inputLine = in.readLine();
System.out.println("Received: " + inputLine);
} while (!"bye".equals(inputLine));
System.out.println("Closing connection...");
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
DummyChannelPoolHandler and DummyClientHandler just print out events that happen, so they are not relevant. When the server and the client are started and I send a test message to test endpoint, I can see the server prints "New client connected" but the message sent by client is not printed. None of the consecutive messages sent by client are printed by the server.
If I try telnet, everything works fine, the server prints out messages. Also it works fine with regular netty client with same bootstrap config and without connection pool (SimpleNettyClientApplication).
Can anyone see what is wrong with my connection pool, I'm out of ideas
Netty versioin: 4.1.39.Final
All the code is available here.
UPDATE
Following Norman Maurer advice. I added
ChannelFuture channelFuture = ch
.writeAndFlush(msg + System.lineSeparator());
channelFuture.addListener(writeFuture -> {
System.out
.println("isSuccess(): " + channelFuture.isSuccess() + " : " + channelFuture.cause());
});
This prints out
isSuccess: false : java.lang.UnsupportedOperationException: unsupported message type: String (expected: ByteBuf, FileRegion)
To fix it, I just converted String into ByteBuf
ch.writeAndFlush(Unpooled.wrappedBuffer((msg + System.lineSeparator()).getBytes()));
You should check what the status of the ChannelFuture is that is returned by writeAndFlush(...). I suspect it is failed.
I have been migrating an existing application over to Spring Cloud's service discovery, Ribbon load balancing, and circuit breakers. The application already makes extensive use of the RestTemplate and I have been able to successfully use the load balanced version of the template. However, I have been testing the situation where there are two instances of a service and I drop one of those instances out of operation. I would like the RestTemplate to failover to the next server. From the research I have done, it appears that the fail-over logic exists in the Feign client and when using Zuul. It appears that the LoadBalancedRest template does not have logic for fail-over. In diving into the code, it looks like the RibbonClientHttpRequestFactory is using the netflix RestClient (which appears to have logic for doing retries).
So where do I go from here to get this working?
I would prefer to not use the Feign client because I would have to sweep A LOT of code.
I had found this link that suggested using the #Retryable annotation along with #HystrixCommand but this seems like something that should be a part of the load balanced rest template.
I did some digging into the code for RibbonClientHttpRequestFactory.RibbonHttpRequest:
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
try {
addHeaders(headers);
if (outputStream != null) {
outputStream.close();
builder.entity(outputStream.toByteArray());
}
HttpRequest request = builder.build();
HttpResponse response = client.execute(request, config);
return new RibbonHttpResponse(response);
}
catch (Exception e) {
throw new IOException(e);
}
}
It appears that if I override this method and change it to use "client.executeWithLoadBalancer()" that I might be able to leverage the retry logic that is built into the RestClient? I guess I could create my own version of the RibbonClientHttpRequestFactory to do this?
Just looking for guidance on the best approach.
Thanks
To answer my own question:
Before I get into the details, a cautionary tale:
Eureka's self preservation mode sent me down a rabbit hole while testing the fail-over on my local machine. I recommend turning self preservation mode off while doing your testing. Because I was dropping nodes at a regular rate and then restarting (with a different instance ID using a random value), I tripped Eureka's self preservation mode. I ended up with many instances in Eureka that pointed to the same machine, same port. The fail-over was actually working but the next node that was chosen happened to be another dead instance. Very confusing at first!
I was able to get fail-over working with a modified version of RibbonClientHttpRequestFactory. Because RibbonAutoConfiguration creates a load balanced RestTemplate with this factory, rather then injecting this rest template, I create a new one with my modified version of the request factory:
protected RestTemplate restTemplate;
#Autowired
public void customizeRestTemplate(SpringClientFactory springClientFactory, LoadBalancerClient loadBalancerClient) {
restTemplate = new RestTemplate();
// Use a modified version of the http request factory that leverages the load balacing in netflix's RestClient.
RibbonRetryHttpRequestFactory lFactory = new RibbonRetryHttpRequestFactory(springClientFactory, loadBalancerClient);
restTemplate.setRequestFactory(lFactory);
}
The modified Request Factory is just a copy of RibbonClientHttpRequestFactory with two minor changes:
1) In createRequest, I removed the code that was selecting a server from the load balancer because the RestClient will do that for us.
2) In the inner class, RibbonHttpRequest, I changed executeInternal to call "executeWithLoadBalancer".
The full class:
#SuppressWarnings("deprecation")
public class RibbonRetryHttpRequestFactory implements ClientHttpRequestFactory {
private final SpringClientFactory clientFactory;
private LoadBalancerClient loadBalancer;
public RibbonRetryHttpRequestFactory(SpringClientFactory clientFactory, LoadBalancerClient loadBalancer) {
this.clientFactory = clientFactory;
this.loadBalancer = loadBalancer;
}
#Override
public ClientHttpRequest createRequest(URI originalUri, HttpMethod httpMethod) throws IOException {
String serviceId = originalUri.getHost();
IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
RestClient client = clientFactory.getClient(serviceId, RestClient.class);
HttpRequest.Verb verb = HttpRequest.Verb.valueOf(httpMethod.name());
return new RibbonHttpRequest(originalUri, verb, client, clientConfig);
}
public class RibbonHttpRequest extends AbstractClientHttpRequest {
private HttpRequest.Builder builder;
private URI uri;
private HttpRequest.Verb verb;
private RestClient client;
private IClientConfig config;
private ByteArrayOutputStream outputStream = null;
public RibbonHttpRequest(URI uri, HttpRequest.Verb verb, RestClient client, IClientConfig config) {
this.uri = uri;
this.verb = verb;
this.client = client;
this.config = config;
this.builder = HttpRequest.newBuilder().uri(uri).verb(verb);
}
#Override
public HttpMethod getMethod() {
return HttpMethod.valueOf(verb.name());
}
#Override
public URI getURI() {
return uri;
}
#Override
protected OutputStream getBodyInternal(HttpHeaders headers) throws IOException {
if (outputStream == null) {
outputStream = new ByteArrayOutputStream();
}
return outputStream;
}
#Override
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
try {
addHeaders(headers);
if (outputStream != null) {
outputStream.close();
builder.entity(outputStream.toByteArray());
}
HttpRequest request = builder.build();
HttpResponse response = client.executeWithLoadBalancer(request, config);
return new RibbonHttpResponse(response);
}
catch (Exception e) {
throw new IOException(e);
}
//TODO: fix stats, now that execute is not called
// use execute here so stats are collected
/*
return loadBalancer.execute(this.config.getClientName(), new LoadBalancerRequest<ClientHttpResponse>() {
#Override
public ClientHttpResponse apply(ServiceInstance instance) throws Exception {}
});
*/
}
private void addHeaders(HttpHeaders headers) {
for (String name : headers.keySet()) {
// apache http RequestContent pukes if there is a body and
// the dynamic headers are already present
if (!isDynamic(name) || outputStream == null) {
List<String> values = headers.get(name);
for (String value : values) {
builder.header(name, value);
}
}
}
}
private boolean isDynamic(String name) {
return name.equals("Content-Length") || name.equals("Transfer-Encoding");
}
}
public class RibbonHttpResponse extends AbstractClientHttpResponse {
private HttpResponse response;
private HttpHeaders httpHeaders;
public RibbonHttpResponse(HttpResponse response) {
this.response = response;
this.httpHeaders = new HttpHeaders();
List<Map.Entry<String, String>> headers = response.getHttpHeaders().getAllHeaders();
for (Map.Entry<String, String> header : headers) {
this.httpHeaders.add(header.getKey(), header.getValue());
}
}
#Override
public InputStream getBody() throws IOException {
return response.getInputStream();
}
#Override
public HttpHeaders getHeaders() {
return this.httpHeaders;
}
#Override
public int getRawStatusCode() throws IOException {
return response.getStatus();
}
#Override
public String getStatusText() throws IOException {
return HttpStatus.valueOf(response.getStatus()).name();
}
#Override
public void close() {
response.close();
}
}
}
I had the same problem but then, out of the box, everything was working (using a #LoadBalanced RestTemplate). I am using Finchley version of Spring Cloud, and I think my problem was that I was not explicity adding spring-retry in my pom configuration. I'll leave here my spring-retry related yml configuration (remember this only works with #LoadBalanced RestTemplate, Zuul of Feign):
spring:
# Ribbon retries on
cloud:
loadbalancer:
retry:
enabled: true
# Ribbon service config
my-service:
ribbon:
MaxAutoRetries: 3
MaxAutoRetriesNextServer: 1
OkToRetryOnAllOperations: true
retryableStatusCodes: 500, 502
I am using GWTUplaod in my application which is working fine if I don't use cross domain.But it is not working correctly in cross domain scenario.
Let me explain,
GWT Server is at IP address 5.5.5.10 and GWT Client is at IP address 5.5.5.12 , earlier while uploading I was getting error in request due to cross domain then I override servlet doPost and doGetMethod
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
try {
FlowContextManager.getInstance().setFlowId(FlowContextManager.getNextFlowId());
processCrossDomainHeaders(request, response);
super.doGet(request, response);
} catch (Exception e) {
logger.error(e);
} finally {
FlowContextManager.getInstance().setFlowId("");
}
}
#Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException {
try {
FlowContextManager.getInstance().setFlowId(FlowContextManager.getNextFlowId());
processCrossDomainHeaders(request, response);
super.doPost(request, response);
} catch (Exception e) {
logger.error(e);
} finally {
FlowContextManager.getInstance().setFlowId("");
}
}
public void processCrossDomainHeaders(HttpServletRequest request, HttpServletResponse response) {
HttpServletResponse resp = response;
HttpServletRequest req = request;
String header = req.getHeader("Origin");
if (header != null) {
resp.addHeader("Access-Control-Allow-Origin", header);
}
resp.addHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
header = req.getHeader("Access-Control-Request-Headers");
if (header != null) {
resp.addHeader("Access-Control-Allow-Headers", header);
}
resp.addHeader("Access-Control-Max-Age", "100");
}
#Override
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.addHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
resp.addHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
String header = req.getHeader("Access-Control-Request-Headers");
resp.addHeader("Access-Control-Allow-Headers", header);
resp.addHeader("Access-Control-Max-Age", "100");
super.doOptions(req, resp);
}
Now my GWTUploadServlet is extending this abstract servlet.
In my web.xml I have specified following entries
<servlet>
<servlet-name>uploadServlet</servlet-name>
<!-- This is the default servlet, it puts files in session -->
<servlet-class>com.drishti.ameyo.webclient.server.WebClientUploadServlet</servlet-class>
<init-param>
<param-name>corsDomainsRegex</param-name>
<param-value>.*</param-value>
</init-param>
</servlet>
I have referred following link
http://code.google.com/p/gwtupload/issues/detail?id=187
Request is getting successful but I am getting following error on browser console.
Blocked a frame with origin "http://10.10.10.192:8888" from accessing a frame with origin "http://10.10.10.250:8888". Protocols, domains, and ports must match. FormPanelImpl.java:32
onSubmitComplete: null SuperDevModeLogger.java:71
onSubmitComplete exception parsing response: SuperDevModeLogger.java:71
com.google.gwt.xml.client.impl.DOMParseException: Failed to parse: error on line 1 at column 1: Document is empty
at createStackTrace
at fillInStackTrace_1
at fillInStackTrace
at $fillInStackTrace
at Throwable_1
at Exception_1
at RuntimeException_1
at DOMException_1
at DOMParseException_0
at throwDOMParseException
at parseImpl_0
at $parse_3
at parse_3
at onSubmitComplete_0
at $dispatch_22
at dispatch_23
at dispatch
at dispatchEvent_1
I didn't get some good solution on it.Please help for it.
I got this problem while trying to access REST web service using GWT client.
I inspect the chrome page then i got the following error from console
XMLHttpRequest cannot load http://localhost:8080/RestWeb/webresources/generic/get. Origin http://127.0.0.1:8888 is not allowed by Access-Control-Allow-Origin.
Following is my client side code
public void onModuleLoad() {
RequestBuilder builder = new RequestBuilder(RequestBuilder.GET,
"http://localhost:8080/RestWeb/webresources/generic/get");
builder.setCallback(new RequestCallback() {
#Override
public void onResponseReceived(Request request, Response response) {
Window.alert("onResponseReceived");
}
#Override
public void onError(Request request, Throwable exception) {
}
});
builder.setHeader("Content-Type",
"text/plain,application/json,text/xml");
builder.setHeader("Access-Control-Allow-Methods",
"PUT, GET, POST, DELETE, OPTIONS");
builder.setHeader("Access-Control-Allow-Headers", "Content-Type");
builder.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8888");
try {
builder.send();
} catch (RequestException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
My server side code is :
#Path("generic")
#WebService
public class GenericResource {
#Context
private UriInfo context;
#Context
private HttpServletResponse response;
private String content = "content";
/**
* Creates a new instance of GenericResource
*/
public GenericResource() {
}
#GET
#Path("/get")
#Produces("application/json,text/plain")
public String getXml() {
System.out.println("GET");
//response.addHeader(content, content);
return this.content + " from get method";
}
}
I tried in different ways to get answer. Please help me.
You need to change your server code to support CORS.
One option is a filter:
public class CorsFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) {
// CORS "pre-flight" request
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
response.addHeader("Access-Control-Allow-Headers", "Content-Type");
response.addHeader("Access-Control-Max-Age", "1800");//30 min
}
filterChain.doFilter(request, response);
}
}
The web.xml needs adding the following too:
<filter>
<filter-name>cors</filter-name>
<filter-class>com.xxx.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cors</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Since it's an old question, if one is facing similar issues today - he or she may want to consider using a nice "CORS Filter" that handles all CORS stuff for you in a completely transparent way. Here's the link
I am querying a REST API, for negative cases in response I am getting a 200 code and some weird Content-type in headers. Because of this I am unable to store the response, as it throws an exception while parsing.
Below image shows the headers from the response:
ResponseErrorHandler:
#Component
public class AutomationResponseErrorHandler implements ResponseErrorHandler{
private static final Logger logger = LoggerFactory.getLogger(AutomationResponseErrorHandler.class);
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
// TODO Auto-generated method stub
return response.getStatusCode() != HttpStatus.OK;
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
logger.error("Response Error: {} {} {}", response.getStatusCode(), response.getStatusText(), response.getBody());
}
}
Custom RestTemplate:
#Component
public class CustomRestTemplate {
#Autowired
AutomationResponseErrorHandler responseErrorHandler;
public RestTemplate getRestTemplate(boolean isHttpsRequired)
throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
// if https is not required,
if (!isHttpsRequired) {
return new RestTemplate();
}
// else below code adds key ignoring logic for https calls
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy)
.build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.setErrorHandler(responseErrorHandler);
return restTemplate;
}
}
Below is the code for saving response:
ResponseEntity<String> response = restTemplate.getForEntity(outBound, String.class);
Below is the Exception occured:
at org.springframework.http.MediaType.parseMediaType(MediaType.java:534)
at org.springframework.http.HttpHeaders.getContentType(HttpHeaders.java:869)
at org.springframework.web.client.HttpMessageConverterExtractor.getContentType(HttpMessageConverterExtractor.java:124)
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:88)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:991)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:974)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:725)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:680)
at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:359)
at com.att.aotsm.msnautomationscheduler.TicketCloseAutomation.queryTicketCloseAPI(TicketCloseAutomation.java:54)
at com.att.aotsm.msnautomationscheduler.AutomationInvokeWebService.queryTicketCloseAPI(AutomationInvokeWebService.java:71)
at com.att.aotsm.msnautomationscheduler.AutomationThreadProcess.run(AutomationThreadProcess.java:138)
at java.lang.Thread.run(Unknown Source)
Caused by: org.springframework.util.InvalidMimeTypeException: Invalid mime type "`colnames<-`(`*tmp*`, value = c("MSN/Port", "Count"))": does not contain '/'
at org.springframework.util.MimeTypeUtils.parseMimeType(MimeTypeUtils.java:194)
at org.springframework.http.MediaType.parseMediaType(MediaType.java:531)
... 12 more
I want the to save the response body, no matter whatever the content-type is.