Dumbo
Dumbo

Reputation: 1827

How to simplify REST controllers with same methods and different headers?

I have one Java REST API which is used by 2 different consumers. By default REST principles my API should define the names of request headers. But now I have not common situation. Consumers use different security layer which provides different headers which means same parameter in both ways.

Example method: (not real code)
For 1st consumer:

@PostMapping("/number")
Integer getNumber(@RequestHeader("no") String number, @RequestBody User user) {
    /*...*/
}

For 2nd consumer:

@PostMapping("/number")
Integer getNumber(@RequestHeader("number") String number, @RequestBody User user) {
    /*...*/
}

I have up to 10 methods in one controller and they should be with same name and logic, but different header. The request path prefix could be different.


Question:

How to simplify REST controller and don't create 2 different controllers with same methods and same logic?


What I tried:

I tried several examples to create one controller with 2 different interfaces with same methods, but different mapping.

Example:

Controller class

@RestController
@RequestMapping(path ="/application")
@Api(tags = {"application"})
public class ApplicationController implements AppMapping1, AppMapping2 {

    @Override
    public Integer getNumber(String number, User user) {
        /*...*/
    }
}

First interface

interface AppMapping1 {

    @PostMapping("/num")
    Integer getNumber(@RequestHeader("num") String number, @RequestBody User user);
}

Second interface

interface AppMapping2 {

    @PostMapping("/number")
    Integer getNumber(@RequestHeader("number") String number, @RequestBody User user);
}

Result:

Controller maps only with the first interface. So http://.../application/num works fine, but http://.../application/number - gets 404 error code. That means Java Spring-Boot doesn't have such functionality. Need some more ideas.

Project developed with Java 8; spring-boot:2.1.1.RELEASE; gradle

Upvotes: 1

Views: 5823

Answers (5)

tomasulo
tomasulo

Reputation: 1330

I found this answer on https://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/spring-mvc-request-header.html

Avoid ambiguity by using @RequestMapping(headers = ....)

We can fix the ambiguity similar to @RequestParam where we used 'params' . In case of @RequestHeader we can define related headers in @RequestMapping annotation.

@Controller
@RequestMapping("trades")
public class TradesController {

   @RequestMapping(headers = "User-Agent")
    public String handleAllTradesRequests (@RequestHeader("User-Agent") String userAgent,
                                           Model model) {
        model.addAttribute("msg", "all trades requests, User-Agent header  : "
                                                                    + userAgent);
        return "my-page";
    }

    @RequestMapping(headers = "From")
    public String handleRequestByFromHeader (@RequestHeader("From") String from,
                                             Model model) {
        model.addAttribute("msg", "trade request by From header  : " + from);
        return "my-page";
    }

Upvotes: 1

Mehrdad Yami
Mehrdad Yami

Reputation: 1681

According to this , If we're not sure which headers will be present, or we need more of them than we want in our method's signature, we can use the @RequestHeader annotation without a specific name.

You have a few choices for variable type: a Map, a MultiValueMap or an HttpHeaders object.

Sample

@PostMapping("/number")
public Integer getNumber(@RequestHeader Map<String, String> headers) {

    if (Optional.ofNullable(headers.get("no")).isPresent()){
        //...
    }
    else if (Optional.ofNullable(headers.get("number")).isPresent())
    {
        //...
    }

}

Upvotes: 2

h4t0n
h4t0n

Reputation: 101

The best way is to add the HttpServletRequest as an argument of your single controller and do some logic with the header map provided by the HttpServletRequest object.

If you want to see a full example take a look here. I have implemented I single controller that wraps all my logic accordingly to headers/methods and so on. You can customize the logic as you want with the HttpServletRequest.

Upvotes: 0

Dhaval Shewale
Dhaval Shewale

Reputation: 234

It is not maintenance friendly to repeat the same block of code twice or more times just to receive the same input with different names (number and no). Instead, it is advisable to read all the headers and traverse through it to fetch input using different names.

Sample Code

@PostMapping("/number")
public Integer getNumber(@RequestHeader Map<String, String> headers) {
    String number = headers.containsKey("number") ? headers.get("number") : headers.get("no");
    if(Objects.isNull(number)) {
        throw new RuntimeException("Number input not received from header!");
    }

    // relevant processing
}

Upvotes: 1

Bibek Khadka
Bibek Khadka

Reputation: 297

You could remove the @RequestHeader annotation and consider doing the following:

    @PostMapping("/number")
    Integer getNumber(HttpServletRequest request, @RequestBody User user) {
      String number = request.getHeader("num");
      if(number == null){
         number = request.getHeader("number");
      }    
      /*...*/
    }

If you want a cleaner approach, consider creating a util class that takes the HttpServletRequest object and returns the desired header value.

Upvotes: 0

Related Questions