SpringBoot – add Cache Control Headers in Rest methods - java

I have a basic SpringBoot 2.0.5.RELEASE app. Using Spring Initializer, JPA, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR
I have created this Rest method:
#GetMapping(path = "/users/notifications", consumes = "application/json", produces = "application/json")
public ResponseEntity<List<UserNotification>> userNotifications(
#RequestHeader(value = "Authorization") String authHeader) {
User user = authUserOnPath("/users/notifications", authHeader);
List<UserNotification> menuAlertNotifications = menuService
.getLast365DaysNotificationsByUser(user);
return ResponseEntity.ok(menuAlertNotifications)
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS));;
}
and I want to add a Cache Control Headers, but I don't know how...
I got a compilation error:
Multiple markers at this line
- The method cacheControl(CacheControl) is undefined for the type
ResponseEntity<List<UserNotification>>
- CacheControl
- cacheControl
I also add this property in application.properties
security.headers.cache=false

When you use ResponseEntity.ok(T body) the return type is ResponseEntity<T> as it is a shortcut method to add data to the body part of the ResponseEntity.
You need the builder object that is created via ResponseEntity.ok() with no param which returns a Builder object. You then add your data yourself on via the body method.
So your code should be like this
#GetMapping(path = "/users/notifications", consumes = "application/json", produces = "application/json")
public ResponseEntity<List<UserNotification>> userNotifications(
#RequestHeader(value = "Authorization") String authHeader) {
User user = authUserOnPath("/users/notifications", authHeader);
List<UserNotification> menuAlertNotifications = menuService
.getLast365DaysNotificationsByUser(user);
return ResponseEntity.ok().cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS)).body(menuAlertNotifications);
}

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"

Can't figure out how to change Prometheus content type header

So my metrics all appear in one line at my end-point, not in new line per metric.
I use micrometer, spring, prometheus and scala.
My controller:
#RequestMapping(Array(""))
class MetricsController #Inject() (prometheusRegistry: PrometheusMeterRegistry) {
#RequestMapping(value = Array("/metrics"), method = Array(RequestMethod.GET))
def metricses(): String = {
prometheusRegistry.scrape()
}
}
Should it be enough to change the way I write the metrics them selves?
I have tried to add scrape(TextFormat.CONTENT_TYPE_004) but that changed nothing.
Does it have to do with the HTTP response header?
Would it work to add:
.putHeader(HttpHeaders.CONTENT_TYPE, TextFormat.CONTENT_TYPE_004)
.end(registry.scrape());
If so how would I do that in my case?
Thanks
Prometheus (or other compatible backends) will send you an Accept header that you should not ignore (please read about content negotiation) but if you want to ignore it:
#GetMapping(path = "/metrics", produces = MediaType.TEXT_PLAIN_VALUE)
#ResponseBody String metrics() {
return registry.scrape();
}
If you don't want to ignore it, TextFormat has a chooseContentType method that you can utilize to get the content type based on the Accept header:
#GetMapping(path = "/metrics")
#ResponseBody ResponseEntity<String> metrics(#RequestHeader("accept") String accept) {
String contentType = TextFormat.chooseContentType(accept);
return ResponseEntity
.ok()
.contentType(MediaType.valueOf(contentType))
.body(registry.scrape(contentType));
}
Or you can also set-up content negotiation: https://www.baeldung.com/spring-mvc-content-negotiation-json-xml

Spring boot rest endpoint returning null with fetch request

I have already created Rest Endpoint in Java spring boot. It returns appropriate response when I request it via Postman. But when I use react fetch it does not show any response in browser if return is Json.
Spring boot controller:
#RestController
#RequestMapping(path = "/v1/test")
#AllArgsConstructor(onConstructor_ = {#Autowired})
public class TestController {
...
}
Below endpoint is returning appropriate response.
#GetMapping(value = "/helloWorld", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public String getHelloWorld() {
return "Hello, World1!";
}
But when I try to hit below endpoint it returns null when I make fetch request. But it returns appropriate response when I hit it via postman.
#GetMapping(value = "/testEndpoint", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public String returnTestResponse() {
HashMap<String, Object> map = new HashMap<>();
map.put("key1", "value1");
map.put("results", "value2");
return "{\"a\":1, \"b\":\"foo\"}";
}
Also tried returning POJO object. But still no response.
#GetMapping(value = "/testModel", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public SearchResultsModel testModel() {
this.myService.getSearchResult();
}
React fetch call:
await fetch(ALL_ARTICLES_ENDPOINT, {
mode: 'no-cors',
method: 'GET',
redirect: 'follow',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
}).then(response => {
console.log(response);
})
.then(data => {
console.log('Success:', data);
}).catch((error) => {
console.error('Error:', error);
});
Postman have couple hidden headers which are being sent with all requests.
Check Hide auto-generated headers
What you are missing in react call is is Accept header with application/json value
EDIT:
Just saw that you are returning string as json. You need to wrap it in POJO object and return it in returnTestResponse class
SECOND EDIT:
This will work. Try to implement your POJO
#GetMapping(value = "/testEndpoint", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public YourObject returnTestResponse() {
HashMap<String, Object> map = new HashMap<>();
map.put("key1", "value1");
map.put("results", "value2");
return new YourObject(map);
}
Issue was caused by adding mode: 'no-cors' option in fetch request. This option helped me to get rid of cors error but it means that in return I won't be able to see body and headers in chrome.
To resolve the issue I removed mode: 'no-cors' and added #CrossOrigin annotation on my spring boot controller.

Changing the postmapping in Spring gives me a 404

Right now, I have I can send post requests to /message using Postman according to this
#RestController
#Api // For swagger document
public class MessageController {
private final MessageService messageService;
public MessageController(MessageService messageService) {
this.messageService = messageService;
}
#ApiOperation(value = "POST a message")
#PostMapping(value = "/message", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity createMessage(#Valid #RequestBody MessageVo messageVo,
#RequestParam(value = "callingApplication") String callingApplication) {
Map<String, String> response = new HashMap<>();
HttpHeaders responseHeaders = new HttpHeaders();
String id = messageService.createMessage(messageVo, callingApplication);
response.put("id", id);
return new ResponseEntity<>(response, responseHeaders, HttpStatus.CREATED);
}
}
If I change the #PostMapping(value = "/message") to #PostMapping(value = "/message2") for example, I am getting a 404 instead of a 200 (while also changing the URL to a 2 at the end). Is there somewhere else the mapping is defined?
Edit: I might add that I'm using "mvn clean package" to compile with Jooq as I'm also running a Postgres database locally. It might be that when I press the build button to compile on Intellij locally, it's not compiling the whole thing.
If #PostMapping(value = "/message2") then URL to hit in postman should be -
base_url/message2?callingApplication=abc
The 'callingApplication' is a mandatory query parameter.
Realized I had to compile with mvn clean package, not from ide

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);
}

Categories