I am looking for a way to get opened input stream from rest template - I was trying to used ResponseExtractor, but the stream is getting closed before returning, as written here:
https://jira.spring.io/browse/SPR-7357
"Note that you cannot simply return the InputStream from the extractor, because by the time the execute method returns, the underlying connection and stream are already closed"
I hope that there is a way and I will not have to write to my output stream directly in the rest template.
I didn't find a way to do it, the stream is always getting closed. As a workaround I created the following code:
public interface ResourceReader {
void read(InputStream content);
}
with the following implementation:
public class StreamResourceReader implements ResourceReader {
private HttpServletResponse response;
public StreamResourceReader(HttpServletResponse response) {
this.response = response;
}
#Override
public void read(InputStream content) {
try {
IOUtils.copy(content, response.getOutputStream());
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
then in controller:
#RequestMapping(value = "document/{objectId}")
public void getDocumentContent(#PathVariable String objectId, HttpServletResponse response) {
ResourceReader reader = new StreamResourceReader(response);
service.readDocumentContent(objectId, reader);
}
call to rest template:
restTemplate.execute(uri, HttpMethod.GET, null,
new StreamResponseExtractor(reader));
and the string response extractor:
#Override
public ResponseEntity extractData(ClientHttpResponse response) throws IOException {
reader.read(response.getBody());
return null;
}
and it works like a charm! :)
Related
I am hitting one API using RestTemplate exchange method, Here I am getting responseEntity of ClientResponse Type. If we have any Bad request in first line of code, I'll get 400 and cursor will go to the catch and throwing Error. So remaining code(For setting a response Data) is not executing .Instead of this I want to set the response Data and I want to set status code also and want to execute remain code. How we can do it, Do we need to use Flag variable ??
ResponseEntity<ClientResponse> responseEntity = this.getRestTemplate().exchange(API_URL,
HttpMethod.POST, entity, ClientResponse.class);
response.setResponseEntity(responseEntity);
response.setValue(inputRequest.getValue));
response.setEndTime(LocalDateTime.now());
response.setRequestPayload(gson.toJson(inputRequest));
response.setHttpMethod(HttpMethod.POST);
response.setRequestHeaders(entity.getHeaders().toSingleValueMap());
} catch (Exception e) {
LOG.error("error occurred in service" + e.getMessage());
}
return response;
You can use restTemplate error handler for that. You can parse the error response returned from the rest template like 404 or some other error.
You can use those status code and error response to rest the response as per your need.
For that you need to define a class by implementing ResponseErrorHander.
public class ServiceResponseErrorHandler implements ResponseErrorHandler {
private List<HttpMessageConverter<?>> messageConverters;
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return (response.getStatusCode().is4xxClientError() ||
response.getStatusCode().is5xxServerError());
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
#SuppressWarnings({ "unchecked", "rawtypes" })
HttpMessageConverterExtractor<ServiceErrorResponse> errorMessageExtractor =
new HttpMessageConverterExtractor(ServiceErrorResponse.class, messageConverters);
ServiceErrorResponse errorObject = errorMessageExtractor.extractData(response);
throw new ResponseEntityErrorException(
ResponseEntity.status(response.getRawStatusCode())
.headers(response.getHeaders())
.body(errorObject)
);
}
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = messageConverters;
}
}
And use it in your rest template bean like this.
RestTemplateResponseErrorHandler errorHandler = new
RestTemplateResponseErrorHandler();
//pass the messageConverters to errror handler and let it convert json to object
errorHandler.setMessageConverters(restTemplate.getMessageConverters());
restTemplate.setErrorHandler(errorHandler);
I calling to the api with the basic retrofit Call object:
public interface dataApi {
#GET("animal/cats")
Call<AllAnimals> getAllData(
#Query("api_key") String apiKey
);
}
And I can get the response inside my view model like this:
call.enqueue(new Callback<AllAnimals>() {
#Override
public void onResponse(Call<AllAnimals> call, Response<AllAnimals> response) {
animals.setValue(response.body());
}
#Override
public void onFailure(Call<AllAnimals> call, Throwable t) {
Log.i(TAG, "onFailure: " + t);
}
});
Nothing speical here.
I've several problem with this approach
FIRST - if I give the wrong api key for example, the response should give me a response with the code of the problem, instead I just get null body.
SECOND I am planning to have more api calls, and it's a huge code duplication to handle errors every call I wrote.
How can I implement custom error handling for this situation, that will be apply to other calls too?
I think you can use okhttp interceptor and define yourself ResponseBody converter to fix your problem.
First,intercept you interested request and response;
Second,check the response,if response is failed then modify the response body to empty。
define a simple interceptor
Interceptor interceptor = new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
String url = request.url().toString();
System.out.println(request.url());
okhttp3.Response response = chain.proceed(request);
if (!response.isSuccessful() && url.contains("animal/cats")) {
// request failed begin to modify response body
response = response.newBuilder()
.body(ResponseBody.create(MediaType.parse("application/json"), new byte[] {}))
.build();
}
return response;
}
};
define self ResponseBody converter
most code from com.squareup.retrofit2:converter-jackson we just add two lines:
final class JacksonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final ObjectReader adapter;
JacksonResponseBodyConverter(ObjectReader adapter) {
this.adapter = adapter;
}
#Override public T convert(ResponseBody value) throws IOException {
try {
if (value.contentLength() == 0) {
return null;
}
return adapter.readValue(value.charStream());
} finally {
value.close();
}
}
}
the below code is added:
if (value.contentLength() == 0) {
return null;
}
i am making a rest call using Spring Oauth2RestTemplate. I am trying to catch any exception while trying to make a restAPI call and continue the flow of Exception.
Two ways i tried:
Way I(Using try catch).
public ResponseEntity<Object> getResponse(URI uri, HttpHeaders httpHeaders,
Object obj) {
ResponseEntity<Object> response = null;
try {
response = restTemplate.exchange(uri, HttpMethod.POST, new HttpEntity<>(obj, httpHeaders),
Object.class);
} catch (Exception serverEx) {
LOGGER.error("ERROR while calling API.Full Exception: ",serverEx);
response.getBody().setLink(object.getUrl());
}
return response;
}
Way II(Custom Handling).
public class RestTemplateResponseErrorHandler implements ResponseErrorHandler {
private static final Logger LOGGER = LogManager.getLogger(RestTemplateResponseErrorHandler.class);
#Override
public boolean hasError(ClientHttpResponse httpResponse) throws IOException {
return (httpResponse.getStatusCode().series() == Series.CLIENT_ERROR
|| httpResponse.getStatusCode().series() == Series.SERVER_ERROR);
}
#Override
public void handleError(ClientHttpResponse httpResponse) {
//Log The Error but contibue the flow
}
}
But neither way the execution gets stopped. I want to continue the flow of the execution. if the call fails i want to handle it and continue the flow. Can any one please suggest whats happening here?
Exception:
Caused by: java.io.IOException: Attempted read from closed stream.
at org.apache.http.impl.io.ContentLengthInputStream.read(ContentLengthInputStream.java:131)
at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:118)
at java.io.FilterInputStream.read(FilterInputStream.java:83)
at java.io.PushbackInputStream.read(PushbackInputStream.java:139)
at org.springframework.web.client.MessageBodyClientHttpResponseWrapper.hasEmptyMessageBody(MessageBodyClientHttpResponseWrapper.java:102)
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:82)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:932)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:916)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:663)
... 223 more
Error Response OutboundJaxrsResponse{status=500, reason=Internal Server Error, hasEntity=true, closed=false, buffered=false}
Any suggestion on this please?
This is my OkHttp Post Form Parameter Method using OkHttp's Async Get
public Call postGetCountries(Callback callback) {
RequestBody body = new FormEncodingBuilder()
.add("op", "op")
.build();
Log.d(TAG_PARAMS, "op=sgetcountrylist, app_type=1");
Request request = new Request.Builder()
.url(GATEWAY_URL)
.post(body)
.build();
Call call = CLIENT.newCall(request);
call.enqueue(callback);
return call;
}
This is my custom Callback.
private class GetCountriesCallback implements Callback {
#Override
public void onFailure(Request request, IOException e) {
Log.e("OkHttp", e.getMessage());
}
#Override
public void onResponse(Response response) throws IOException {
Log.d("PASSED", "PASS");
Log.d(Connection.TAG_RETURN, response.body().string());
try {
InputStream is = response.body().byteStream();
List test = connectionParser.parse(is, "op");
} catch (XmlPullParserException e) {
Log.e("PARSE ERROR", e.getMessage());
}
}
}
This is my instantiated parse method.
public List parse(InputStream in, String op) throws XmlPullParserException, IOException {
try {
XmlPullParser parser = Xml.newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
parser.setInput(in, null);
parser.nextTag();
return readFeed(parser, op);
} finally {
in.close();
}
}
I'm currently testing if it works unfortunately I receive a return of
10-06 11:54:42.492 6336-6892/ D/PASSED: PASS
10-06 11:54:42.692 6336-6892/ E/PARSE ERROR: Invalid stream or encoding: java.io.IOException: closed (position:START_DOCUMENT null#1:1) caused by: java.io.IOException: closed
This is what I use on my onCreate on the activity to start the whole process:
private Connection connect = Connection.getInstance();
connect.postGetCountries(new GetCountriesCallback());
I don't understand as to why the InputStream gets closed.
Two things could be going on. First, you can only read the body once. If you want to read it more than once, you need to store the result somewhere. You are reading the body twice, once here --
Log.d(Connection.TAG_RETURN, response.body().string());
and then here --
InputStream is = response.body().byteStream();
List test = connectionParser.parse(is, "op");
by the time you start to parse, you have already exhausted the available input in the stream. The quick solution is to remove the log statement.
Another thing that might be tripping you up, or could trip you up in the future is onResponse is called even in the event of HTTP returning an error code. You should check the Response's code() or isSuccesful() methods to decide if you should even attempt to parse the response.
I want to add logging to my Servlet, so I've created Filter which should display request and go to the Servlet. But unfortunately I've encoutered exception:
java.lang.IllegalStateException: getReader() has already been called for this request
at org.apache.catalina.connector.Request.getInputStream(Request.java:948)
at org.apache.catalina.connector.RequestFacade.getInputStream(RequestFacade.java:338)
at com.noelios.restlet.ext.servlet.ServletCall.getRequestEntityStream(ServletCall.java:190)
So to fix this problem I've found solution with Wrapper, but it doesn't work. What else can I use/change in code? Any ideas?
[MyHttpServletRequestWrapper]
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper
{
public MyHttpServletRequestWrapper(HttpServletRequest request)
{
super(request);
}
private String getBodyAsString()
{
StringBuffer buff = new StringBuffer();
buff.append(" BODY_DATA START [ ");
char[] charArr = new char[getContentLength()];
try
{
BufferedReader reader = new BufferedReader(getReader());
reader.read(charArr, 0, charArr.length);
reader.close();
}
catch (IOException e)
{
e.printStackTrace();
}
buff.append(charArr);
buff.append(" ] BODY_DATA END ");
return buff.toString();
}
public String toString()
{
return getBodyAsString();
}
}
[MyFilter]
public class MyFilterimplements Filter
{
#Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
final HttpServletRequestWrapper requestWrapper = new MyHttpServletRequestWrapper(httpServletRequest);
final String requestBody = requestWrapper.toString();
chain.doFilter(request, response);
}
}
Looks like the restlet framework has called getRequestEntityStream() on the Request object which in turn calls getInputStream(), so calling getReader() on the request throws IllegalStateException. The Servlet API documentation for getReader() and getInputStream() says:
public java.io.BufferedReader getReader()
...
...
Throws:
java.lang.IllegalStateException - if getInputStream() method has been called on this request
public ServletInputStream getInputStream()
...
...
Throws:
java.lang.IllegalStateException - if the getReader() method has already been called for this request
From the documentation it seems that we cannot call both getReader() and getInputStream() on the Request object. I suggest you use getInputStream() rather than getReader() in your wrapper.
Use ContentCachingRequestWrapper class. Wrap HttpServletRequest in thi will resolve issue
Sample : if you want to convert your "HttpServletRequest servletRequest" you can do some thing like
import org.springframework.web.util.ContentCachingRequestWrapper;
ContentCachingRequestWrapper request = new ContentCachingRequestWrapper(servletRequest);
Hope it helps!!!
As far as I can tell servlets are fundamentally broken in this regard. You can try and work around this problem as outlined here but that causes other mysterious problems when other things try and work with it.
Effectively he suggests cloning the request, reading the body and then in the the cloned class overriding the getReader and getInputStream methods to return the stuff already retrieved.
The code I ended up with was this:
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
//this class stops reading the request payload twice causing an exception
public class WrappedRequest extends HttpServletRequestWrapper
{
private String _body;
private HttpServletRequest _request;
public WrappedRequest(HttpServletRequest request) throws IOException
{
super(request);
_request = request;
_body = "";
try (BufferedReader bufferedReader = request.getReader())
{
String line;
while ((line = bufferedReader.readLine()) != null)
_body += line;
}
}
#Override
public ServletInputStream getInputStream() throws IOException
{
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(_body.getBytes());
return new ServletInputStream()
{
public int read() throws IOException
{
return byteArrayInputStream.read();
}
};
}
#Override
public BufferedReader getReader() throws IOException
{
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
Anyway this appeared to be working fine until we realised that uploading a file from the browser wasn't working. I bisected through the changes and discovered this was the culprit.
Some people in the comments in that article say you need to override methods to do with parameters but don't explain how to do this.
As a result I checked to see if there was any difference in the two requests. However after cloning the request it had identical sets of parameters (both original request + cloned had none) aswell as an identical set of headers.
However in some manner the request was being effected and screwing up the understanding of the request further down the line - in my case causing a bizaare error in a library (extdirectspring) where something was trying to read the contents as Json. Taking out the code that read the body in the filter made it work again.
My calling code looked like this:
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException
{
HttpServletRequest properRequest = ((HttpServletRequest)request);
String pathInfo = properRequest.getPathInfo();
String target = "";
if(pathInfo == null)
pathInfo = "";
if(pathInfo.equals("/router"))
{
//note this is because servlet requests hate you!
//if you read their contents more than once then they throw an exception so we need to do some madness
//to make this not the case
WrappedRequest wrappedRequest = new WrappedRequest(properRequest);
target = ParseExtDirectTargetFrom(wrappedRequest);
request = wrappedRequest;
}
boolean callingSpecialResetMethod = pathInfo.equals("/resetErrorState") || target.equals("resetErrorState");
if(_errorHandler.IsRejectingRequests() && !callingSpecialResetMethod)
return;
try {
filterChain.doFilter(request, response);
}
catch (Exception exception) {
((HttpServletResponse) response).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "ERROR");
_errorHandler.NotifyOf(exception);
}
}
I've ommitted the contents of ParseExtDirectTargetFrom but it calls getReader().
In my case the filter was working for all other requests but the strange behaviour in this case made me realise something wasn't quite right and what I was trying to do (implement sensible exception handling behaviour for tests) wasn't worth potentially breaking random future requests (as I couldn't figure out what had caused the request to become broken).
Also it's worth noting that the broken code is unavoidable - I assumed it might be something from spring but ServletRequest goes all the way up - thats all you get even if you were making a servlet from scratch by subclassing HttpServlet
My recommendation would be this - don't read the request body in a filter. You'll be opening up a can of worms that will cause strange problems later on.
The main problem is that you can't read the input both as binary stream and character stream, not even if the one is called in a filter and the other in the servlet.
Well, maybe this is something quite obvious, but I want to share with you this code that work OK for me. In a Spring boot project with JWT, for request of client, was necesary save all requests with their responses in a database table, and the same time authorize the access to consume the resources. Off Course i use getReader() for get request body, but i was obtain java.lang.IllegalStateException...
#Slf4j
#Component
#RequiredArgsConstructor
public class CustomAuthorizationFilter extends OncePerRequestFilter {
private final AuthorizationService authorizationService;
private String requestBody;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
HttpRequestDto requestDto = new HttpRequestDto();
try {
if (RequestMethod.POST.name().equalsIgnoreCase(request.getMethod()) && requestBody != null) { //This line and validation is useful for me [requestBody != null]
requestBody = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
}
//Do all JWT control
requestDto.setRequestURI(request.getRequestURI());
requestDto.setMethod(request.getMethod());
requestDto.setBody(requestBody);
}catch (IOException ie) {
responseError(_3001, response, ie);
} finally {
try {
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
filterChain.doFilter(request, responseWrapper);
saveResponse(responseWrapper, requestDto);
} catch (ServletException | IOException se) {
responseError(_3002, response, se);
}
}
}
private void saveResponse(ContentCachingResponseWrapper responseWrapper, HttpRequestDto requestDto) {
try {
HttpResponseDto responseDto = new HttpResponseDto();
responseDto.setStatus(responseWrapper.getStatus());
byte[] responseArray = responseWrapper.getContentAsByteArray();
String responseBody = new String(responseArray, responseWrapper.getCharacterEncoding());
responseDto.setBody(responseBody);
responseWrapper.copyBodyToResponse();
authorizationService.seveInDatabase(requestDto, responseDto);
} catch (Exception e) {
log.error("Error ServletException | IOException in CustomAuthorizationFilter.saveResponse", e);
}
}
private void responseError(LogCode code, HttpServletResponse response, Exception e) {
try {
Map<String, Object> error = new HashMap<>();
error.put("log", LogUtil.getLog(code));
error.put("message", e.getMessage());
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), error);
e.printStackTrace();
} catch (IOException ie) {
log.error("Error IOException in HttpLoggingFilter.responseError:", ie);
}
}
public String getRequestBody() {
return requestBody;
}
public void setRequestBody(String requestBody) {
this.requestBody = requestBody;
}
}
So my solution was use getter and setter methods of de local attribute requestBody, for validate if this is or not null and does not call again getReader() method because save in memory when set value. This worked perfect for me. Regards.