Search code examples
springspring-mvcspring-bootspring-hateoasspring-web

spring boot using HATEOAS generates links without host and port


I am using spring boot version 2.0.6 which comes with HATEOAS version 0.25. I am using a resource assembler that utlises ControllerLinkBuilder to generate resource links. However, the problem is it generates relative links, how to configure it to use a host and port scheme from either request headers (the app will run as a docker container in dev, qa and prod) or config properties when running it locally from IDE.

I get link that respects X-Forwarded-Host header

"_links": {
        "self": {
            "href": "http://something.io/data/api/customers"
        }
    }

if I explicitly add header X-Forwarded-Host and generate the self link with this code

Link self = new Link(
                        ServletUriComponentsBuilder.fromRequestUri(request).buildAndExpand(pageable).toUri().toString(),
                        "self");

But when in resource assembler I rely on the usual linkTo calls from ControllerLinkBuilder the host and port are not rendered in the link.

"_links": {
        "self": {
            "href": "/customers/1"
        },
        "customers": {
            "href": "/customers"
        },
        "contact": {
            "href": "/customers/1/contact"
        }
    }

The controller definition

@Slf4j
@RestController
@RequestMapping("/customers")
@ExposesResourceFor(Customer.class)
public class CustomerController {
}

and the get method

@GetMapping(produces = MediaTypes.HAL_JSON_VALUE)
    public DeferredResult<ResponseEntity<Resources<Resource<Customer>>>> getAllCustomers(
            @PageableDefault(page = 0, size = 20, sort = "name", direction = Direction.ASC) Pageable pageable,
            PagedResourcesAssembler<Customer> assembler, HttpServletRequest request) {
}

I'm passing request object here because linkTo gives a url without host and port

And I'm using a Customer Resource assembler from the code here, spring hateoas exmaples which is autowired to this controller

@Autowired
private CustomerResourceAssember customerResourceAssembler;

and this is how I call paged resource assembler

assembler.toResource(result, customerResourceAssembler, self)

Solution

  • The UriComponentsBuilder in spring-hateoas calls RequestContextHolder.getRequestAttributes() to get the request attributes. These are attached to the thread by the dispatcher servlet. As you're using DeferredResult, you will already have returned the thread with the request attributes. The thread being used to build the links doesn't have any request attributes on it. This results in relative paths being used.

    There are a couple of issues about it already, but they don't suggest that there is anyway you can use any properties to set the base url. Which seems a shame.

    There are a few simlar questions, the answers seem to suggest you need to pass the request details to the new thread. Here's a couple in case you haven't found them already.