I have to implement the same endpoint that return different data type. I can't use versioning in the URL so just wondering how I can use header content to map request to based on header value.
For example I will be sending val1 or val2 in header say decider. And I want to have controller methods like
#RequestMapping(value = "\someUrl")
public firstReturnType someMethod() {
}
#RequestMapping(value = "\someUrl")
public secondReturnType someOtherMethod() {
}
Any suggestion?
This is the HTTP Headers list: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
you can see that you don't have in HTTP headers that you can use to your needs or any other custom needs.
I would recommend differentiating between the 2 methods by adding HTTP verb, GET, POST, DELETE, PUT.
Or just change your URL to:
#RequestMapping(value = "/someUrl/{id}")
Or just use one method and use if statement to decide which answer to return:
#RequestMapping(value = "/someUrl")
public firstReturnType someMethod(#RequestParam("param") int param ) {
if(param == 1){
...
}
else if(param == 2){
...
}
}
Related
I have a simple post method that accepts temperature and checks is the temperature is greater than or equals to 37.4 and returns a response as cleared or not cleared.
#PostMapping(value = "/temperature")
public ResponseEntity<?> Temperature(#Valid #RequestBody Activation activation) throws UnsupportedEncodingException {
Temperature temperature = new Temperature();
temperature.setStatus(activation.getStatus());
temperature.setTemperature(activation.getTemp());
if (db_activation.getCode().equals(code)) {
temperature.setSource("VENUE");
temperatureRepository.save(temperature);
return ResponseEntity.ok(activation.getTemp() < 37.4 ? "Cleared" : "Not cleared");
}
}
how can I insert the response to Status column (which can be cleared or not cleared) to the database and will it be a post request or a put? Please suggest how to do that
To set the status, you can use something like below,
temperature.setStatus(activation.getTemp() < 37.4 ? "Cleared" : "Not cleared")
If you want to create the resource, POST should be the method, for updates, PUT should be used.
Choosing the right request type is just a matter of convention. Technically you could perform any operation with any type of request. By convention: POST sends some fresh data tonthe server, PUT sends an update of the server's data. So if it's a persisted data, POST would be "add" and PUT would be "update".
You could do it in the same method, like so:
if (activation.getTemp()< 37.4) {
temperature.setStatus("Cleared");
} else {
temperature.setStatus("Not cleared");
}
instead of this line:
temperature.setStatus(activation.getStatus());
I have a question about the following. We want to create some Rest calls. One of the parameters of the rest calls is the return format. So the parameter return_format can have the values json or xml. Is there a smart way to use the parameter and use the service that will produce the right output format?
If the call parameter return_format == json then
#Produces({"application/json"})
if the call parameter return_format == xml then
#Produces({"application/xml"})
You don't need a parameter like return_format. This can be controlled with the Accept header.
In the controller you can add both formats:
#RequestMapping(value = "/employee", method = RequestMethod.GET,
produces = { "application/json", "application/xml" })
public <yourResponse> get(#RequestHeader("Accept") String accept) {
// Check if Accept is XML or JSON
}
Context:
I want to write an endpoint that will return a Collection of users based on their usernames. How should those username be passed to the REST endpoint - note that I can (potentially) have a lot of usernames (say > 5000)?
Solution #1:
Use a GET endpoint, concatenate the usernames on client side and pass them as a single request parameter. Split the request parameter on server side to get the list of usernames.
#RestController
public class UserController {
#GetMapping
// able to deserialize `filename1,filename2` to List out of the box
public Collection<User> getUser(#RequestParam List<String> usernames) {
return userService.getUsersByUsername(usernames);
}
}
Solution #2:
Use a POST endpoint and pass the list of usernames as request body. Although cleaner form a coding perspective, I end up using a POST to fetch data.
#RestController
public class UserController {
#PostMapping
public Collection<User> getUser(#RequestBody List<String> usernames) {
return userService.getUsersByUsername(usernames);
}
}
Questions:
Which of the two solutions would be the better approach?
Do you have a better approach to pass the list of usernames into the endpoint?
Edits:
I've updated the signature of the first solution based on suggestions from answers. Spring is able to deserialize filename1,filename2 to List out of the box for #RequestParam.
POST looks like a cleaner approach in this case because -
Sending a huge string in a URL is not a good idea and there is scope for error
You need to write additional code (logic) to create the string on frontend and split it on backend.
Sending a huge string in a URL is not scalable as there are limits on the length of URL.
Get approach might result into an issue since URL length is limited and then you have to limit your query parameters.
Though its not a post request but in your case i think post is the only way out.
I would agree with all the answers given above. I would like to specify one more point , if you are going with post request you might have to increase the payload capacity a server can receive , the default post capacity(The maximum size in bytes) of spring boot is 2mb (based on your server). While testing your code might work fine with 1000-2000 usernames but make sure to change the property to accept more bytes in request.
GET is not limited, yet the browser is. Your server client does not seem to be the browser, so I would say GET is the way to go.
P.S GET can receive a body (not so great, but POST is not also the best match).
You don need to concatenated the string and add extra computation on server server, GET can receive a list of separate strings.
UPDATE with example:
#RestController
public class MyController {
#GetMapping(value = "/test")
public List<String> getTestParams(#RequestParam List<String> params) {
return params;
}
}
The test with 3000 params
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestMyController {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void testRequestWithParamsList() {
List<String> params = new ArrayList<>();
for (int i = 0; i < 3000; i++) {
params.add(String.valueOf(i));
}
List<String> result = restTemplate.getForObject(buildUrl(params),
List.class);
assertEquals(params, result);
}
private String buildUrl(List<?> params) {
return "/test?params=" + getUrlParameter(params);
}
private String getUrlParameter(List<?> params) {
return params.stream()
.map(Object::toString)
.collect(Collectors.joining(","));
}
}
If you are using tomcat you must specify also the max http header property in application.properties
server.max-http-header-size=30000
Requirement is to write a controller to handle POST requests from the below urls:
http://hostname:port/com/prod1?id=2&action=add
http://hostname:port/com/prod1?id=2&action=minus
http://hostname:port/com/prod2?id=2&action=add
http://hostname:port/com/prod2?id=2&action=minus
Can I have two methods, one for mapping urls with action=add, and another for urls with action=minus? All the requests are POST.
A couple years between question and answer, but yes it is possible to route based on query parameters with Spring MVC:
#RequestMapping(path = "/com/{product}", params = "add")
String add(#PathVariable("product") String product) {
System.out.println("add method; product=" + product);
return "add";
}
#RequestMapping(path = "/com/{product}", params = "minus")
String minus(#PathVariable("product") String product) {
System.out.println("minus method; product=" + product);
return "minus";
}
No to my knowledge. According to your url end points,
they are,
/com/prod1
/com/prod2
For these you can have 2 controller methods for each of these.
EDIT:
If I understand you correctly,
Instead of having the above end points, write 2 controller methods for request mappings,
com/add
com/minus
If you want to have it in a more granular manner, then
com/prod1/add
com/prod1/minus
com/prod2/add
com/prod2/minus
write request mapping methods for the above.
Then you have,
http://hostname:port/com/prod1/add?id=2
http://hostname:port/com/prod1/minus?id=2
http://hostname:port/com/prod2/add?id=2
http://hostname:port/com/prod2/munus?id=2
Or you can use another approach. which is, Just use the generic end points and depending on your request parameters, redirect to different request mappings such as,
com/add
com/minus
You cannot have two #RequestMapping methods to map to two URLs differing only by query parameters. The #RequestMapping only binds to the path portion of the URL.
You can have two methods if you dispatch on the action value, calling one method for add and another for minus.
Or, you can make add or minus a part of the URL path.
It is possible with the following code:
#GetMapping(path = "/com/{product}", params = "action=add")
public String add(#PathVariable("product") String product) {
System.out.println("add method; product=" + product);
return "add";
}
#GetMapping(path = "/com/{product}", params = "action=minus")
public String minus(#PathVariable("product") String product) {
System.out.println("minus method; product=" + product);
return "minus";
}
I am trying to read the authorization header for an HTTP request (because I need to add something to it), but I always get null for the header value. Other headers work fine.
public void testAuth() throws MalformedURLException, IOException{
URLConnection request = new URL("http://google.com").openConnection();
request.setRequestProperty("Authorization", "MyHeader");
request.setRequestProperty("Stackoverflow", "anotherHeader");
// works fine
assertEquals("anotherHeader", request.getRequestProperty("Stackoverflow"));
// Auth header returns null
assertEquals("MyHeader", request.getRequestProperty("Authorization"));
}
Am I doing something wrong? Is this a "security" feature? Is there a way to make this work with URLConnection, or do I need to use another HTTP client library?
Apparently, it's a security "feature". The URLConnection is actually an instance of sun.net.www.protocol.http.HttpURLConnection. It defines getRequestProperty as:
public String getRequestProperty (String key) {
// don't return headers containing security sensitive information
if (key != null) {
for (int i=0; i < EXCLUDE_HEADERS.length; i++) {
if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) {
return null;
}
}
}
return requests.findValue(key);
}
The EXCLUDE_HEADERS array is defined as:
// the following http request headers should NOT have their values
// returned for security reasons.
private static final String[] EXCLUDE_HEADERS = {
"Proxy-Authorization",
"Authorization"
};
I am not happy about the extra dependencies, but following the suggestion to switch to Commons Http solved the immediate problem for me.
I'd still like to know what the problem was with my original code.
As Devon's answer correctly states: it's not a bug, it's a "security" feature 😉
But you don't have to switch to a different library: it is always possible to access the underlying MessageHeader-collection via reflection and extract the "Authorization"-header value.
After some headscratch i've managed to come up with a working snippet here.
Have you tried using URLConnection.addRequestProperty()?
This is how I use to add HTTP Request Headers.