Search code examples
spring-bootspring-cloudnetflix-eurekahateoasfeign

Feign HATEOS response contains wrong server,port information


I am working on Spring Cloud and using the sample project by Josh Long at

Bootiful Microservice by Josh Long

There is an API gateway reservation-client which consumes data from the service reservation-service which provides a HATEOAS response, which then is converted to a simple JSON response.

@RestController
@RequestMapping("/reservations")
class ReservationApiGateway {

Method:

@HystrixCommand(fallbackMethod = "fallback")
@RequestMapping(method = RequestMethod.GET, value = "/names")
public Collection<String> names() {
    return this.reservationReader
            .read()
            .getContent()
            .stream()
            .map(Reservation::getReservationName)
            .collect(Collectors.toList());
}

I modify it to forward me the HATEOAS response like this.

@HystrixCommand(fallbackMethod = "fallback")
@RequestMapping(method = RequestMethod.GET, value = "/names")
public Resources<Resource<Reservation>> names() {
    return this.reservationReader
            .read();
}

This is giving me a HATEAOS response, but the links are all from the reservation-service - .

  "_links" : {

    "self" : {

      "href" : "**http://192.168.0.3:7000/reservations/1**"

    },

    "reservation" : {

      "href" : "http://192.168.0.3:7000/reservations/1"

    }

  }

How do I make sure Feign updates the links to the server and port of the API Gateway? - http://192.168.0.3:9999/reservations/1

Same response from reservation-client(same as reservation-service):

{

  "_embedded" : {

    "reservations" : [ {

      "reservationName" : "Josh",

      "_links" : {

        "self" : {

          "href" : "http://192.168.0.3:7000/reservations/1"

        },

        "reservation" : {

          "href" : "http://192.168.0.3:7000/reservations/1"

        }

      }

    }, {

      "reservationName" : "Dr. Johnson",

      "_links" : {

        "self" : {

          "href" : "http://192.168.0.3:7000/reservations/2"

        },

        "reservation" : {

          "href" : "http://192.168.0.3:7000/reservations/2"

        }

      }

    }, {

      "reservationName" : "Dr. Syer",

      "_links" : {

        "self" : {

          "href" : "http://192.168.0.3:7000/reservations/3"

        },

        "reservation" : {

          "href" : "http://192.168.0.3:7000/reservations/3"

        }

      }

    }, {

      "reservationName" : "Dr. Pollack",

      "_links" : {

        "self" : {

          "href" : "http://192.168.0.3:7000/reservations/4"

        },

        "reservation" : {

          "href" : "http://192.168.0.3:7000/reservations/4"

        }

      }

    } ]

  },

  "_links" : {

    "self" : {

      "href" : "http://192.168.0.3:7000/reservations{?page,size,sort}",

      "templated" : true

    },

    "profile" : {

      "href" : "http://192.168.0.3:7000/profile/reservations"

    },

    "search" : {

      "href" : "http://192.168.0.3:7000/reservations/search"

    }

  }

}

Solution

  • I figured it out.

    The solution was in the X-Forwarded-Host http header. X-Forwarded-Host essentially tells Spring that any HATEOS response which has this header, the host and port information in the links should be updated to what is mentioned in the X-Forwarded-Host http header.

    So, in the API Gateway reservation-client code, I added this snippet which intercepts Feign's call to the backend service reservation-service and adds the http header to the request.

    @Component
    class LanguageRequestInterceptor implements RequestInterceptor {
        private static final String X_FORWARDED_HOST = "X-Forwarded-Host";
    
        @Override
        public void apply(RequestTemplate requestTemplate) {
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (requestAttributes == null) {
                return;
            }
            HttpServletRequest request = requestAttributes.getRequest();
            if (request == null) {
                return;
            }
    
            requestTemplate.header(X_FORWARDED_HOST, "localhost:9999");
        }
    }
    

    Now, all the HATOES response has the host and port information of the API Gateway, not the backend HATEOS service.