Modify/Replace ClientHttpResponse body inside interceptor (ClientHttpRequestInterceptor) - java

I'm adding message level encryption (MLE) to an existing code base for outgoing requests. To do this, I simply wrote an interceptor that will catch outgoing requests, encrypt their bodies, and then send the request out. The response we get is also encrypted, and must be decrypted. This all is working fine for me. The only problem I'm having is that I must replace the ClientHttpResponse encrypted body with the now decrypted JSON. How can I do this? I don't see any methods that will let me alter the response body. Thanks in advance.
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
ClientHttpResponse response;
String bodyStr = new String(body);
// Encrypt the body and send
bodyStr = encrypt(bodyStr);
try {
response = execution.execute(request, bodyStr.getBytes());
} catch (Exception e) {
throw e;
}
// Decrypt the response body
String decryptedResponseBody = decrypt(response.getBody());
// Set the response body to the decrypted data (JSON)
// response.setBody(decryptedResponseBody)?????????
return response;
}

You will need to create an implementation of ClientHttpResponse which is not too hard since there are only a few methods to override, I added an example of how you would fix this. I hope this helps. I would suggest adding a named bean for this type of request, you don't want to have all your resttemplates being encrypted/decrypted.
restTemplate.getInterceptors().add( (ClientHttpRequestInterceptor)
(request, body, execution) -> {
ClientHttpResponse response;
String bodyStr = new String(body);
// Encrypt the body and send
bodyStr = encrypt(bodyStr);
try {
response = execution.execute(request, bodyStr.getBytes());
String text = IOUtils.toString(response.getBody(), StandardCharsets.UTF_8.name());
// Decrypt the response body
String decryptedResponseBody = decrypt(text);
} catch (Exception e) {
throw e;
}
InputStream inputStream = inputStream = new ByteArrayInputStream(decryptedResponseBody.getBytes());
return new ClientHttpResponse() {
#Override
public HttpHeaders getHeaders() {
return response.getHeaders();
}
#Override
public InputStream getBody() throws IOException {
return inputStream;
}
#Override
public HttpStatus getStatusCode() throws IOException {
return response.getStatusCode();
}
#Override
public int getRawStatusCode() throws IOException {
return response.getRawStatusCode();
}
#Override
public String getStatusText() throws IOException {
return response.getStatusText();
}
#Override
public void close() {
response.close();
}
};
}))

Related

HttpServletResponse won't stream bytes to client

I've been trying to figure out why the Servlet won't return the bytes to the client with this code (although the bytes is read based on the logs):
Redirector redirector = new SelfInjectingRedirector(getContext(), targetPattern,
Redirector.MODE_SERVER_OUTBOUND){
#Inject
FileStore fileStore;
String fileName = "something_for_sample";
boolean isBufferFirst = true;
#Override
public void handle(Request request, Response response) {
try {
HttpServletRequest servletRequest = ServletUtils.getRequest(request);
HttpServletResponse servletResponse = ServletUtils.getResponse(response);
//
// Either statement here wont return the bytes to the client
//
if(isBufferFirst) {
byte[] bytes = fileStore.get(fileName);
System.out.println("Bytes read: " + bytes.length); // Bytes read: 5731
servletResponse.getOutputStream().write(bytes, 0, bytes.length)
} else {
fileStore.get(fileName, servletResponse.getOutputStream());
}
} catch (Exception e) {
response.setStatus(Status.SERVER_ERROR_INTERNAL);
e.printStackTrace();
}
System.out.println("Handle Done");
}
};
The solution is to add the "Content-Length" header.

Send byte array and receive String through REST web service

in my Spring Rest web service I send a file (even big size) as byte array but when I receive the information, the object is a String so when I make the cast from Object to byte[] I receive the following error:
java.lang.ClassCastException: java.lang.String cannot be cast to [B
The originl file is converted through
Files.readAllBytes(Paths.get(path))
and this byte[] is filled in one object with a field result of Object type.
When the Client retrieve this object and it gets result class with cast to byte[] it appears the above exception, this is the client code
Files.write(Paths.get("test.txt"),((byte[])response.getResult()));
If I use a cast to string and then to bytes the content of the file is different from original file. I don't care the file type, file content, I only have to copy from server to client directory
How can I do?Thanks
server class:
#Override
#RequestMapping(value = "/", method = RequestMethod.GET)
public #ResponseBody Response getAcquisition(#RequestParam(value="path", defaultValue="/home") String path){
try {
byte[] file = matlabClientServices.getFile(path);
if (file!=null){
FileTransfer fileTransfer= new FileTransfer(file, Paths.get(path).getFileName().toString());
return new Response(true, true, fileTransfer, null);
}
else
return new Response(false, false, "File doesn't exist!", null);
} catch (Exception e) {
ErrorResponse errorResponse= ErrorResponseBuilder.buildErrorResponse(e);
LOG.error("Threw exception in MatlabClientControllerImpl::getAcquisition :" + errorResponse.getStacktrace());
return new Response(false, false, "Error during file retrieving!", errorResponse);
}
}
and FileTransfer is:
public class FileTransfer {
private byte[] content;
private String name;
..get and set
client class:
#RequestMapping(value = "/", method = RequestMethod.GET)
public #ResponseBody Response getFile(#RequestParam(value="path", defaultValue="/home") String path){
RestTemplate restTemplate = new RestTemplate();
Response response = restTemplate.getForObject("http://localhost:8086/ATS/client/file/?path={path}", Response.class, path);
if (response.isStatus() && response.isSuccess()){
try {
#SuppressWarnings("unchecked")
LinkedHashMap<String,String> result= (LinkedHashMap<String,String>)response.getResult();
//byte[] parseBase64Binary = DatatypeConverter.parseBase64Binary((String)fileTransfer.getContent());
Files.write(Paths.get(result.get("name")), DatatypeConverter.parseBase64Binary(result.get("content")));
return new Response(true, true, "Your file has been written!", null);
} catch (IOException e) {
return new Response(true, true, "Error writing your file!!", null);
}
}
return response;
}
So the client should be something like this
#RequestMapping(value = "/test/", method = RequestMethod.GET)
public Response getFileTest(#RequestParam(value="path", defaultValue="/home") String path){
RestTemplate restTemplate = new RestTemplate();
Response response = restTemplate.getForObject("http://localhost:8086/ATS/client/file/?path={path}", Response.class, path);
if (response.isStatus() && response.isSuccess()){
try {
byte[] parseBase64Binary = DatatypeConverter.parseBase64Binary((String)response.getResult());
Files.write(Paths.get("test.txt"),parseBase64Binary );
} catch (IOException e) {
}
}
return response;
}
I believe the content-type here is text/plain, therefore the content of the file is a plain text. Simply generate byte array from the response:
Files.write(Paths.get("test.txt"),((String)response.getResult()).getBytes());

Spring: Logging outgoing HTTP requests

I am trying to log all the outgoing Http requests in my spring based web application. Is there is interceptor for this purpose? I want to log all outgoing the contents and headers before it leaves the application. I am using spring-ws to send SOAP requests. So basically, I want to log not only the SOAP request xml (as mentioned here How can I make Spring WebServices log all SOAP requests?) but the http request as a whole.
Intercept the request/response using a ClientInterceptor on the WebServiceGatewaySupport:
// soapClient extends WebServiceGatewaySupport
soapClient.setInterceptors(new ClientInterceptor[]{new ClientInterceptor() {
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
messageContext.getRequest().writeTo(os);
} catch (IOException e) {
throw new WebServiceIOException(e.getMessage(), e);
}
String request = new String(os.toByteArray());
logger.trace("Request Envelope: " + request);
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
messageContext.getResponse().writeTo(os);
} catch (IOException e) {
throw new WebServiceIOException(e.getMessage(), e);
}
String response = new String(os.toByteArray());
logger.trace("Response Envelope: " + response);
return true;
}
...
To get the headers as well you need an instance of TransportOutputStream.
Unfortunately the class is abstract, so you need to subclass is. Here's how it might look:
class ByteArrayTransportOutputStream extends TransportOutputStream {
private ByteArrayOutputStream outputStream;
#Override
public void addHeader(String name, String value) throws IOException {
createOutputStream();
String header = name + ": " + value + "\n";
outputStream.write(header.getBytes());
}
public byte[] toByteArray() {
return outputStream.toByteArray();
}
#Override
protected OutputStream createOutputStream() throws IOException {
if (outputStream == null) {
outputStream = new ByteArrayOutputStream();
}
return outputStream;
}
}

Validate empty body request of PUT, POST method in JAX-RS

I'm curious if there is an annotation/filter/interceptor capability in JAX-RS to detect if PUT or POST method contains an empty body.
Currently I have method that, if request has empty body, possibly throws NPE.
#PUT
#Produces("application/json")
#Consumes("application/json")
#Path("/update/{id}")
public Response updateCustomer(#PathParam("id") final String customerIdStr, final CustomerJson customer) {
// if request body is empty -> customer == null
return Response.ok().build();
}
I can check customer for null . But since I have plenty of such methods, it's better to have filter to do such validation.
Please!
Did you try to use Bean Validation, using an #NotNull annotation on your CustomerJson method parameter ?
Interceptors read the HTTP body and I dont find a way to send the body for further processing. But you can do this by Servlet Filter and HTTP servlet request wrapper,
public class EmptyCheckFilter implements javax.servlet.Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
if (req.getMethod().equals("POST") || req.getMethod().equals("PUT")) {
boolean dirty = false;
HttpRequestWrapper wrapper = new MyHTTPRequestWrapper(req);
try {
// check body is empty by wrapper.getBody() and set dirty = true;
} catch (Exception e) {
}
if (dirty) {
res.sendError(400, "Invalid input");
} else
chain.doFilter(wrapper, response);
} else
chain.doFilter(request, response);
}
#Override
public void destroy() {
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
}
public class MyHTTPRequestWrapper extends HttpServletRequestWrapper {
private final String body;
public MyHTTPRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
if (request.getCharacterEncoding() != null)
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, request.getCharacterEncoding()));
else
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
body = stringBuilder.toString();
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(
body.getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
return servletInputStream;
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
public String getBody() {
return this.body;
}
}

HttpRequestHandler : Sending a file in response

I m working with httpCore in order to create my own basic web server. Upon receiving a certain request from the user a file transfer operation has to be initiated. I m handling the request with HttpRequestHandler. My code looks like this
private HttpRequestHandler mRequestHandler = new HttpRequestHandler()
{
#Override
public void handle(HttpRequest request, HttpResponse response, HttpContext httpContext) throws HttpException, IOException
{
try{
HttpEntity entity=null;
String contentType="text/html";
entity = new EntityTemplate(new ContentProducer()
{
public void writeTo(final OutputStream outstream)throws IOException
{
OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8");
String resp = "Server is up and running";
writer.write(resp);
writer.flush();
}
});
((EntityTemplate)entity).setContentType(contentType);
response.setEntity(entity);
}catch(Exception e)
{
e.printStackTrace();
}
}
};
This is a very basic response , however What I m looking to transfer a file from the server to the client machine. How can I send a file in response ? Thanks
I figured it out myself. If someone stumbles across the same problem, here is how I got it working.
private HttpRequestHandler mRequestHandler = new HttpRequestHandler()
{
#Override
public void handle(HttpRequest request, HttpResponse response, HttpContext context)
throws HttpException, IOException
{
try{
File file = new File("/mnt/sdcard/Music/song.mp3");
FileEntity body = new FileEntity(file, "audio/mpeg");
response.setHeader("Content-Type", "application/force-download");
response.setHeader("Content-Disposition","attachment; filename=song.mp3");
response.setEntity(body);
}catch(Exception e)
{
e.printStackTrace();
}
}
};

Categories