Fix masked error responses on XML REST endpoints (#403) - #405
Open
assadriaz wants to merge 2 commits into
Open
Conversation
Error mappers built an ErrorResponseEntity response without setting a media type, so on @produces(application/xml) endpoints (e.g. the NeTEx import endpoint) no MessageBodyWriter matched ErrorResponseEntity. Jersey then raised MessageBodyProviderNotFoundException and fell back to a generic HTTP 500, discarding the real cause — every import/validation failure looked identical and the message was only visible in the server log. Pin error responses to text/plain (the type the existing ErrorResponseEntityMessageBodyWriter already produces) in: - GeneralExceptionMapper - NoSuchElementMapper (also switched from the un-writable raw exception entity to a text/plain ErrorResponseEntity) Added unit tests asserting the response media type and serialized message.
Once error responses are pinned to text/plain (previous commit), the ErrorResponseEntityMessageBodyWriter is actually invoked on the XML endpoints. It then NPEs for exceptions with no message (e.g. a bare NullPointerException): PrintWriter.write(String) throws on null, so the error response still fails to serialize — masking one level deeper. - ErrorResponseEntityMessageBodyWriter: null-guard the write. - GeneralExceptionMapper / NoSuchElementMapper: fall back to the exception's toString() (class name) when getMessage() is null, so the client gets a meaningful message instead of a blank line. Added a writer test that reproduces the null-message NPE, and a mapper test for the no-message case.
Collaborator
|
It's better than the old implementation. Did you consider returning the error in XML? Something like (untested)
JAXB requires it for deserialization/marshalling:
It's no longer needed — Jersey's built-in JAXB provider handles @XmlRootElement classes automatically when the response type is application/xml.
Same change in NoSuchElementMapper. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes masked error responses on XML-producing REST endpoints (#403).
Any exception thrown while handling a request on a
@Produces(application/xml)endpoint(e.g. the NeTEx import endpoint
POST /services/stop_places/netex) was returned to theclient as an opaque, generic
HTTP 500— the real cause was discarded and only visible inthe server log. This made every import/validation failure look identical and slowed down
diagnosis considerably.
Root cause (two layers):
GeneralExceptionMapper#toResponsebuilt the response without setting a media type, soit inherited the resource's
@Produces(application/xml). The only writer forErrorResponseEntityis@Produces("text/plain"), so no writer matched →MessageBodyProviderNotFoundException→ generic 500, message dropped.text/plain,ErrorResponseEntityMessageBodyWriterisactually invoked — and it then NPE'd for exceptions with no message (e.g. a bare
NullPointerException), becausePrintWriter.write(String)throws onnull. So the errorstill failed to serialize, just one level deeper.
Changes:
GeneralExceptionMapper— pin error responses totext/plainso the existing writer matches.NoSuchElementMapper— same fix; also switched from setting the raw (un-writable) exceptionas the entity to a
text/plainErrorResponseEntity.toString()(class name) whengetMessage()isnull, so the client gets something meaningful instead of a blank line.ErrorResponseEntityMessageBodyWriter— null-guard the write so it can never NPE on a null message.Type of change
Issue
Closes #403.
as the same opaque
{"status":500,"error":"Internal Server Error"}; the actual causes (schemaparse error, missing time zone, non-numeric version, non-conforming IDs) were only recoverable
from
spring.log.MessageBodyWriterby the response's media type. Byexplicitly setting
text/plain(the typeErrorResponseEntityMessageBodyWriteralreadyproduces) the writer is always matched, regardless of the resource's
@Produces.text/plainwas chosen because it matches the existing writer and keeps theContent-Type honest. An alternative (broadening the writer's
@Producestoapplication/xml/application/json) would label a plain-text body as XML/JSON, so it was not taken.Unit tests
GeneralExceptionMapperTest— added assertions that the response media type istext/plain,that the cause message is carried through, and that an exception without a message still
yields a non-null message (
NullPointerException→ class name).NoSuchElementMapperTest(new) — asserts 404 +text/plain+ serialized message.ErrorResponseEntityMessageBodyWriterTest(new) — writes a normal message, and reproduces thenull-message case to prove the writer no longer NPEs.
mvn test -Dtest=..., 11 tests, BUILD SUCCESS).Documentation
null-guard /
toString()fallback are needed.