1

I'm developing Spring Boot v2.2.2.RELEASE and API versioning using Custom Header. By default I want latest version of endpoint to be executed.

Here in below code its X-API-VERSION=2. Before executing the endpoint I need to check mapping and then set the latest version in for the Custom Header and then execute it.

@GetMapping(value = "/student/header", headers = {"X-API-VERSION=1"})
public StudentV1 headerV1() {
    return serviceImpl.headerV1();
}

@GetMapping(value = "/student/header", headers = {"X-API-VERSION=2"})
public StudentV1 headerV2() {
    return serviceImpl.headerV2();
}
halfer
  • 19,824
  • 17
  • 99
  • 186
PAA
  • 1
  • 46
  • 174
  • 282
  • Please try to refrain from adding "please help me" or other similar pleading statements to your questions. We value succinctness here. – halfer Feb 10 '20 at 23:07

2 Answers2

4

If I got your question right, you want that the request sent without X-API-VERSION header will automatically be routed to headerV2() method

This can be done:

Conceptually you'll need a Web Filter that gets executed before the Spring MVC Dispatch Servlet and you'll have to check whether the request contains a header and if it doesn't add a header of the version that you think should be the latest one (from configuration or something).

One caveat here is that the HttpServletRequest doesn't allow adding Headers so you'll have to create a HttpServletRequestWrapper and implement the logic of header addition there.

Here is an example (I've borrowed the implementation from this question):

// The web filter 
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;

@Component
@Order(1)
public class LatestVersionFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        if(req.getHeader("X-API-VERSION")== null) {
            HeaderMapRequestWrapper wrappedRequest = new HeaderMapRequestWrapper(req);
            wrappedRequest.addHeader("X-API-VERSION", resolveLastVersion());
            filterChain.doFilter(wrappedRequest, servletResponse);
        } else {
            filterChain.doFilter(servletRequest, servletResponse);
        }
    }

    private String resolveLastVersion() {
        // read from configuration or something
        return "2";
    }
}

Note that the request is wrapped to add the header as I've explained:

class HeaderMapRequestWrapper extends HttpServletRequestWrapper {
        /**
         * construct a wrapper for this request
         *
         * @param request
         */
        public HeaderMapRequestWrapper(HttpServletRequest request) {
            super(request);
        }

        private Map<String, String> headerMap = new HashMap<String, String>();

        /**
         * add a header with given name and value
         *
         * @param name
         * @param value
         */
        public void addHeader(String name, String value) {
            headerMap.put(name, value);
        }

        @Override
        public String getHeader(String name) {
            String headerValue = super.getHeader(name);
            if (headerMap.containsKey(name)) {
                headerValue = headerMap.get(name);
            }
            return headerValue;
        }

        /**
         * get the Header names
         */
        @Override
        public Enumeration<String> getHeaderNames() {
            List<String> names = Collections.list(super.getHeaderNames());
            for (String name : headerMap.keySet()) {
                names.add(name);
            }
            return Collections.enumeration(names);
        }

        @Override
        public Enumeration<String> getHeaders(String name) {
            List<String> values = Collections.list(super.getHeaders(name));
            if (headerMap.containsKey(name)) {
                values.add(headerMap.get(name));
            }
            return Collections.enumeration(values);
        }

    }

With this implementation (make sure you put the servlet in the package next to controller or something so that the spring boot will recognize it as a component) you'll see:

  1. GET with header X-API-VERSION=1 will be routed to headerV1()
  2. GET with header X-API-VERSION=2 will be routed to headerV2()
  3. GET without X-API-VERSION header will be routed to headerV2()
Mark Bramnik
  • 39,963
  • 4
  • 57
  • 97
  • why you have to wrap the request? cant you just overwrite/add the header and send the original request next? – Zavael Feb 10 '20 at 07:45
  • There is no method like "addHeader" in the original request. So you can't put a header (or alter headers in general) in the existing request when it hits the server. – Mark Bramnik Feb 10 '20 at 07:54
  • @MarkBramnik - I'm really happy with the way you suggested, it works, however the kind of mapping that I created, unable to read all the properties keys and take decision. created question here: https://stackoverflow.com/questions/60146164/read-multiple-properties-file-in-one-go-using-spring-boot – PAA Feb 10 '20 at 07:55
  • 1
    Not trying to get additional points or something ;) but I think the question in the link is an entirely different one and has nothing to do with this one... Jokes aside, I'll try to help you there if the time permits but to this question I don't think I can add anything important and consider this solved. – Mark Bramnik Feb 10 '20 at 08:01
1

You can add default method without header, like that:

@GetMapping(value = "/student/header")
public StudentV1 headerDefault() {
    return headerV2();
}

Or just remove headers = {"X-API-VERSION=2"} from method that should be current.

lczapski
  • 4,026
  • 3
  • 16
  • 32
  • your first suggestion is better I think, because I used the second way of having the default behaviour without version (either in header or in path). The problem arised everytime on upgrading the api, all the clients were automaticaly using the new apis. If you own the clients, you can controll that process but if you are not the owner, they can get surpirsed with that behaviour, therefore it is better to have them always use specific api version (even the latest) and force them to make the upgrade to new api explicit and fully aware ot the consequences. – Zavael Feb 10 '20 at 07:35