Gzip specific controller in Spring MVC - java

Is there a way to add Gzip for one method or controller in spring
#RequestMapping(value = "/system", method = {RequestMethod.GET})
#Gzip //<- Something similar this,
public ApiResponse status() throws Exception{
}
I dont want to enable it for the entire server using tomcat configuration, since my clients are not yet ready for consuming gzip,

You can put java.io.OutputStream or javax.servlet.http.HttpServletResponse (for specific gzip HTTP headers) to your Controller method as parameter wrapping it with java.util.zip.GZIPOutputStream before writing the content to the client
#RequestMapping(value = "/system", method = {RequestMethod.GET})
public void status(HttpServletResponse response) throws Exception {
try(GZIPOutputStream out = new GZIPOutputStream(response.getOutputStream())) {
// write to out the content
}
}

Related

How to wrap an external API inside a custom API with Spring Boot

Let's say I have a third party rest api ("https://example.com/write") that allows POST requests with the following body structure:
{
"id": "abc",
"Config": {"Comments":"text"}
}
I am completely new to Java and the Spring Framework, but I want to create a custom API with Spring that only allow users to change the text part of the body. Other parts of the JSON body should have a fixed value (for example id is always "abc"). Basically, when user input a custom text string, my api will compile the input and consume the external api and get the results from it accordingly
I understand the basics of #Getmapping / #RequestMapping after doing some research. Here is what I have so far for my custom API, and I am stuck at the post mapping section.
#RestController
#RequestMapping("/api")
public class ApiController {
#Autowired
private Environment env;
// GET
#RequestMapping(value = "/retrive", method = { RequestMethod.GET })
public ResponseEntity<?> retrive (HttpServletRequest request, HttpServletResponse response) throws IOException {
// URL
URL u = new URL("https://example.com/get");
HttpURLConnection uc = (HttpURLConnection) u.openConnection();
// Create HttpHeaders for ResponseEntity
HttpHeaders responseHeaders = new HttpHeaders();
uc.setRequestProperty ("Authentication", "Bearer "+ env.getProperty("api-key"));
try (InputStream inputStream = uc.getInputStream();
OutputStream outputStream = response.getOutputStream();
)
{IOUtils.copy(inputStream, outputStream);
}
return new ResponseEntity<>(responseHeaders, HttpStatus.OK);
}
// POST
#RequestMapping(value = "/write", method = { RequestMethod.POST },
consumes = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity process(#RequestBody Root input) throws IOException {
// Operation goes here...
return new ResponseEntity<>(input, HttpStatus.OK);
}
public class Root{
private String Comments;
// Getters and Setters
}
Create a Custom DTO class which will represent the response from Third party API. What It means is it should have all fields which should map with corresponding required field of response payload of third party API. It will be populated on de-serializing the third party API response. You can take help of feign client here. Its very easy and declarative way to make REST API call.
Create a separate response DTO with 2 fields 1 which will have the static value and second the DTO which we created above (which will be populated with the response of third party API). This DTO will be the response from POST "/write"

Combine file upload and request body on a single endpoint in rest controller

The UI for my webapp has the ability to either upload a file(csv), or send the data as json in request body. However either a file upload, or a json request would be present in the request and not both. I am creating a spring rest controller which combine file upload and also accepts the request json values as well.
With the below endpoint tested from postman, I am not getting exception:
org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
#RestController
public class MovieController {
private static final Logger LOGGER = LoggerFactory.getLogger(MovieController.class);
#PostMapping(value="/movies", consumes = {"multipart/form-data", "application/json"})
public void postMovies( #RequestPart String movieJson, #RequestPart(value = "moviesFile") MultipartFile movieFile ) {
// One of the below value should be present and other be null
LOGGER.info("Movies Json Body {}", movieJson);
LOGGER.info("Movies File Upload {}", movieFile);
}
}
Appreciate any help in getting this issue solved?
Note: I was able to build two separate endpoint for file upload and json request, but that won't suffice my requirement. Hence I'm looking for a solution to combine both
Try something like:
#RequestMapping(value = "/movies", method = RequestMethod.POST, consumes = { "multipart/form-data", "application/json" })
public void postMovies(
#RequestParam(value = "moviesFile", required = false) MultipartFile file,
UploadRequestBody request) {
In RequestBody you can add the parameters you want to send.
This will not send the data as JSON.
Edit:- I forgot to add the variable for the Multipart file and I mistakenly used the RequestBody which is reserved keyword in spring.
Hope it helps.
I would suggest to create two separate endpoints. This splits and isolates the different functionality and reduces the complexity of your code. In addition testing would be easier and provides better readability.
Your client actually has to know which variable to use. So just choose different endpoints for your request instead of using different variables for the same endpoint.
#PostMapping(value="/movies-file-upload", consumes = {"multipart/form-data"})
public void postMoviesFile(#RequestPart(value = "moviesFile") MultipartFile movieFile ) {
LOGGER.info("Movies File Upload {}", movieFile);
}
#PostMapping(value="/movies-upload", consumes = {"application/json"})
public void postMoviesJson( #RequestPart String movieJson) {
LOGGER.info("Movies Json Body {}", movieJson);
}

Java spring file download proxy with rest calls

I need to create a rest service in java which will in turn connect to another rest service for file download. For now, I just need to transfer the file from the other backend to client but in future some processing/transformations would be done.
For all the web services in my project, we are using spring rest (for providing as well as consuming the services).
My question is what would be the appropriate way of doing it considering that the files would be large and I don't want to run into OutOfMemory errors.
People in some other posts have suggested to use streams on both the ends but is that really possible? For this, do I need to write the file on disk first?
My current code for file download (consumer) -
public BackendResponse<byte[]> callBackendForFile(BackendRequest request) {
String body = null;
ResponseEntity<byte[]> responseEntity = null;
URI uri = createURI(request);
MultiValueMap<String, String> requestHeaders = getHeadersInfo(request.getHttpRequest());
if (HttpMethod.GET.equals(request.getMethod())) {
responseEntity = restTemplate.exchange(uri, request.getMethod(),
new HttpEntity<String>(body, requestHeaders), byte[].class);
} else {
LOG.error("Method:{} not supported yet", request.getMethod());
}
BackendResponse<byte[]> response = new BackendResponse<>();
response.setResponse(responseEntity);
return response;
}
My client code (provider):
#RequestMapping(value = "/file", method = RequestMethod.GET, produces = "application/xml")
#ResponseBody
public void downloadFileWithoutSpring(HttpMethod method, HttpServletRequest httpRequest,
HttpServletResponse httpResponse) {
BackendRequest request = new BackendRequest(method,
httpRequest.getRequestURI(), httpRequest.getQueryString(), httpRequest);
BackendResponse<byte[]> backendResponse = dutyplanService.getFile(request);
ResponseEntity<byte[]> response = backendResponse.getResponse();
httpResponse.addHeader("Content-Disposition", "attachment; filename=\"" + "attachment.zip" + "\"");
httpResponse.getOutputStream().write(response.getBody());
httpResponse.flushBuffer();
}
Note: The code above doesn't work somehow as the attachment downloaded is a corrupt file
I don't think you will need to create that file on server as long as you are having the bytearray content of it received from another server.
You can try changing value of produces annotation to the value application/zip (or application/octet-stream, depending on the target browser) instead of 'application/xml'
you can pass HttpServletResponse#getOutputStream() directly in restTemplate and write it without save file in server.
public void getFile(HttpServletResponse response) throws IOException {
restTemplate.execute(
"http://ip:port/temp.csv",
HttpMethod.GET,
null,
clientHttpResponse -> {
StreamUtils.copy(clientHttpResponse.getBody(), response.getOutputStream());
return null;
}
);
}
note that after call getFile(), you should close outputStream like this
response.getOutputStream().close()

How to set HttpServletResponse on Jboss EAP 6.2?

I have a simple method where I need to change the contentType to "text/plain" but when I am using Jboss EAP 6.2 it is ignored and the returned value is "application/json.
#RestController
#RequestMapping("/upload")
public class UploadController {
...
#RequestMapping(method = RequestMethod.POST)
public Result foo(MultipartFile arquivo, HttpServletResponse response) {
response.setContentType("text/plain");
return uploadService.saveFile(arquivo);
}
}
I am using SpringMVC (rest controller) and Jackson to parse to JSON. The exactly same method works fine when I use Jetty instead of Jboss.
I can't use #RequestMapping(produces="text/plain") because the actual method has a bit more logic into it and sometimes it will return application/json others text/plain, depending if the request came from IE9 browser or not.
Can someone please tell me how can I set the contentType on Jboss?
Unfortunately the only way I could get this working for Jboss was creating a different method for IE9:
#RequestMapping(value = "/ie9", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE)
public String fooIE9(#RequestParam("arquivo") MultipartFile arquivo) throws JsonProcessingException {
ObjectWriter ow = new ObjectMapper().writer();
return ow.writeValueAsString(uploadService.saveFile(arquivo));
}

Response authorization with Jersey

I have a Jersey 2 application containing resources that consume and produce json. My requirement is to add a signature to an Authorization response header generated from a combination of various piece of response data (similar to the Amazon Webservices request signature). One of these pieces of data is the response body but I cant see that there are any filter or interception points that will allow me access to the json content. I imagine this is mainly because the response outputstream is for writing not reading.
Any ideas as to how I can read the response body - or alternative approaches ?
Thank you.
My understanding is that when your application is responding to a request, you want to modify the Authorization header by adding a signature to it's value.
If that's the case, you want to implement a ContainerResponseFilter:
public class MyContainerResponseFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException {
// You can get the body of the response from the ContainerResponseContext
Object entity = containerResponseContext.getEntity();
// You'll need to know what kind of Object the entity is in order to do something useful though
// You can get some data using these functions
Class<?> entityClass = containerResponseContext.getEntityClass();
Type entityType = containerResponseContext.getEntityType();
// And/or by looking at the ContainerRequestContext and knowing what the response entity will be
String method = containerRequestContext.getMethod();
UriInfo uriInfo = containerRequestContext.getUriInfo();
// Then you can modify your Authorization header in some way
String authorizationHeaderValue = containerResponseContext.getHeaderString(HttpHeaders.AUTHORIZATION);
authorizationHeaderValue = authorizationHeaderValue + " a signature you calculated";
containerResponseContext.getHeaders().putSingle(HttpHeaders.AUTHORIZATION, authorizationHeaderValue);
}
}
Be warned that the filter function will be called for all requests to your application, even when Jersey couldn't find a matching resource for the request path, so you may have to do some extra checking.
You can implement ContainerRequestFilter in order to access the content, and once you are finished with your interception logic, forward it to the request. E.g.
import java.io.*;
import com.sun.jersey.api.container.ContainerException;
import com.sun.jersey.core.util.ReaderWriter;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
public class ExampleFilter implements ContainerRequestFilter {
#Override
public ContainerRequest filter(ContainerRequest req) {
try(InputStream in = req.getEntityInputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();) {
if (in.available() > 0) {
StringBuilder content = new StringBuilder();
ReaderWriter.writeTo(in, out);
byte[] entity = out.toByteArray();
if (entity.length > 0) {
content.append(new String(entity)).append("\n");
System.out.println(content);
}
req.setEntityInputStream(new ByteArrayInputStream(entity));
}
} catch (IOException ex) {
//handle exception
}
return req;
}
}

Categories