Skip to content

[BUG] httpmate.configured(toCustomizeResponsesUsing(...)) causes a silent connection termination when the ResponseMapper does not set RESPONSE_STATUS #6

@lestephane

Description

@lestephane

Describe the bug

When attempting to force the response to be json (as a workaround to #5), I get in a situation where the connection between the client and the server is terminated server-side, and no error is written anywhere, leaving the user guessing as to what the problem is.

To Reproduce

(shortened for convenience, but if you think information from the ellided sections is required, I can provide more on demand).

anHttpMateConfiguredAs(USE_CASE_DRIVEN)
    .get("/api/usecase", UseCase.class)
    .mappingRequestsAndResponsesUsing(mapMate()
        .matchingTheContentType(fromString("application/x-www-form-urlencoded"))
            .toTheMarshallerType(...)
        .assumingTheDefaultContentType(ContentType.json())
            .usingTheSerializer(...)
            .andTheDeserializer(...))
    .configured(toCreateUseCaseInstancesUsing(injector::getInstance))
    .configured(Configurator.configuratorForType(EventModule.class, eventModule -> {
        eventModule.setDefaultRequestToEventMapper(byDirectlyMappingAllData());
    }))
    ...
    .configured(toLogUsing((message, metaData) ->
        log.info("{} (MetaData: {})", message, metaData)))
    .configured(toCustomizeResponsesUsing(metaData ->               <<<<< These lines
            metaData.set(CONTENT_TYPE, ContentType.json())))        <<<<< cause the trouble
    .build();
  • Breakpoint the metaData.set line -> B1
  • Breakpoint the entry of the main use case invocation method (in my case GetUserInfo.invoke()) -> B2
  • Breakpoint the Serializer.serializeFromMap method -> B3
  • Issue a request to the use case endpoint (/api/usecase)
  • B1 is hit first, continue execution
  • B2 is hit next, continue execution
  • B3 is hit
    • the marshallingType looks correct (MarshallingType(type=json)),
    • the method returns the correctly serialized json value
  • Add an Exception breakpoing (for any exception) -> B4
  • Continue execution
  • B4 is hit at line 61 in com.envimate.httpmate.chains.MetaData
public <T> T get(final MetaDataKey<T> key) {
    return getOptional(key).orElseThrow(() -> new RuntimeException(format(
            "Could not find meta datum %s in %s", key.key(), map)));
}
  • Evaluate the expression for the exception
java.lang.RuntimeException: Could not find meta datum RESPONSE_STATUS in {PATH=Path(path=/api/...}
  • Step out in the debugger
  • Assuming you are using the PureJavaEndpoint on top of the oracle jvm 12.0.1, you arrive in sun.net.httpserver.ServerImpl, which closes the connection:
 catch (Exception e4) {
    logger.log (Level.TRACE, "ServerImpl.Exchange (2)", e4);
    closeConnection(connection);
}
  • The HTTP client that issued the request gets an abrupt connection termination
[2019-07-08 14:38:14.003] NO_REQUEST_ID INFO o.a.h.i.c.DefaultHttpClient - I/O exception (org.apache.http.NoHttpResponseException) caught when processing request to {}->http://localhost:3000: The target server failed to respond 
[2019-07-08 14:38:14.004] NO_REQUEST_ID INFO o.a.h.i.c.DefaultHttpClient - Retrying request to {}->http://localhost:3000 
[2019-07-08 14:38:14.011] NO_REQUEST_ID INFO o.a.h.i.c.DefaultHttpClient - I/O exception (org.apache.http.NoHttpResponseException) caught when processing request to {}->http://localhost:3000: The target server failed to respond 
[2019-07-08 14:38:14.011] NO_REQUEST_ID INFO o.a.h.i.c.DefaultHttpClient - Retrying request to {}->http://localhost:3000 
[2019-07-08 14:38:14.014] NO_REQUEST_ID INFO o.a.h.i.c.DefaultHttpClient - I/O exception (org.apache.http.NoHttpResponseException) caught when processing request to {}->http://localhost:3000: The target server failed to respond 
[2019-07-08 14:38:14.015] NO_REQUEST_ID INFO o.a.h.i.c.DefaultHttpClient - Retrying request to {}->http://localhost:3000 

org.apache.http.NoHttpResponseException: localhost:3000 failed to respond
  • No logging occurs server side

Expected behavior

  • The internal RuntimeException should be reported server-side to enable troubleshooting, probably to the callback configured earlier:
.configured(toLogUsing((message, metaData) -> ...))
  • An HTTP 500 must be returned instead of forcibly closing the connection, as the last resort when such an error condition is encountered.

    If you don't do this, as you can see, depending on the client, there
    may be multiple retries, each time invoking the use case. If the invoked logic isn't idempotent, there be dragons.

  • My initial expectation was that I only override in the MetaData the exact keys I need to override. And that the code would otherwise run as usual (first), and then my key modifications come after (as an overlay so to speak).

    When I don't add the customizer, a http response of HTTP 200 occurs, even though I did not explicitly set the response status anywhere.

    So, adding the customizer that does not touch the response status should result in a HTTP 200 as well?

  • If the customizer absolutely must configure the response status, then maybe try to make it harder or impossible to ignore (or rather, forget) it, by having ResponseTemplate.apply() return an HttpStatusCode.

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions