Open mariuszs opened 6 years ago
With Spring MVC, you can also use standard content negotiation (which removes the need for a produces clause). You simply request GET /foos.json, and it will generate a JSON response, which in this case should default to HAL.
Alternatively, sending an Accept: application/hal+json header would trigger Spring MVC to force a HAL response.
P.S. Spring HATEOAS has a MediaTypes
class containing constants for the supported hypermedia types, saving you from managing that raw string value of application/hal+json
.
@gregturn This is true, and this works for most cases like @GetMapping public FooResource foo()
, but there is a bug in spring-hateos and this do not work for Resources
In attached code is example without produces
clause, and this fails with attached exception. So standard content negotiation is not working here.
Hmm, ok. This is because my browser sends Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
. This is strange, because this works from browser for others endpoints (like single resource).
@RestController
@RequestMapping(value = "/foos")
public class FooController {
@GetMapping
public Resources<FooResource> foos() {
List<FooResource> foos = Stream.of(new FooResource("foo")).collect(Collectors.toList());
return new Resources(foos);
}
@GetMapping("/{fooId}")
public FooResource foo(@PathVariable String fooId) {
return new FooResource(fooId);
}
}
Command curl -H 'accept: application/xml,*/*' http://localhost:8080/foos/bar
Returns:
HTTP/1.1 200
Content-Type: application/hal+json;charset=UTF-8
{"foo":"bar"}
but curl -H 'accept: application/xml,*/*' http://localhost:8080/foos
HTTP/1.1 500
Content-Type: application/xhtml+xml
<html><body><h1>Whitelabel Error Page</h1><p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p><div id='created'>Tue Jan 30 21:18:40 CET 2018</div><div>There was an unexpected error (type=Internal Server Error, status=500).</div><div>Could not marshal [Resources { content: [FooApplication.FooResource(foo=foo)], links: [] }]: null; nested exception is javax.xml.bind.MarshalException
- with linked exception:
[com.sun.istack.internal.SAXException2: unable to marshal type "com.foo.FooApplication$FooResource" as an element because it is missing an @XmlRootElement annotation]</div></body></html>⏎
This is somehow inconsistent.
@gregturn Can we do something with content negotiation for Resources
?
Why are you asking for XML? The exception is about something wrong in the rendering of an XML response.
I'll confess that the types of Spring HATEOAS include support for XML renderings but the support is only halfway there. Because JSON is what is most popular today. Those wanting XML probably want SOAP anyway.
So what happens in your scenario when you request application/Hal+JSON on the resources aggregate?
It works just fine for curl -H 'accept: application/hal+json' http://localhost:8080/foos
and for curl http://localhost:8080/foos
.
The reason why I'm asking for XML is because I was tested this endpoint from browser (like many others people), and browser sends text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
. After checking this header it fails for this combination application/xml,*/*
.
For example there is no error 500 for application/xml
.
# curl -I -H 'accept: application/xml' http://localhost:8080/foos
HTTP/1.1 406
# curl -H 'accept: */*' http://localhost:8080/foos
HTTP/1.1 200
Content-Type: application/hal+json;charset=UTF-8
{"_embedded":{"foos":[{"foo":"foo"}]}}
# curl -I -H 'accept: application/xml,*/*' http://localhost:8080/foos
HTTP/1.1 500
Content-Type: application/json;charset=UTF-8
Okay, so the nub of everything is you want XML? That wasn't evident when you're opening concern was having to apply the produces
clause inside @GetMapping(produces = {MediaType.APPLICATION_JSON_VALUE, "application/hal+json"})
to get a HAL JSON document.
Basically, there is no support for HAL XML at this point in time (application/hal+xml
), frankly because there hasn't been a huge demand, as stated before. If you look at MediaTypes you can see what is supported, and these are all JSON formats.
Oh, no. I want JSON. And generally all works smooth and I receive JSON. But when you open this HAL endpoint with resources list, then you get error 500, because for this specific endpoint hateos wants to use XML instead.
This is exactly the same problem like reported here: https://github.com/spring-guides/tut-bookmarks/issues/8
People are opening this endpoints in browser, and when endpoint return Resources
then this blow up, but works just fine from curl. For endpoint returning single Resource
there is no problem at all.
All is about inconsistency, different browsing result for Resources and Resource. I hope this is more clear now.
In my opinion, when we have HAL activated, and browser sends accept: application/xml;*/*
then server should return application/hal+json
. Why it is trying to send XML, if there is no XML support for HAL? application/hal+json
is what browser accepts and should be used instead.
??
Basically, when you ask to XML (which is what is listed in the first accept type), and Spring HATEOAS thanks to its XML annotations says that it can, it commits to going down that path.
I crafted a branch (https://github.com/spring-projects/spring-hateoas/tree/bug/handle-browser) with all of its XML annotations removed, and this issue goes away. I'm trying to consult with @olivergierke to get his opinion on the matter.
We’ve removed all JAXB annotations. If you could give the latest version a spin and see if the issue still persists I’d appreciate it.
Invalid content type is used, for Resources endpoint in HAL enabled application.
This requires special code
@GetMapping(produces = {MediaType.APPLICATION_JSON_VALUE, "application/hal+json"})
for endpoint method returningResources
.Logged error:
Sample application:
Dependencies:
This is similar to bug: https://github.com/spring-guides/tut-bookmarks/issues/8