Search code examples
javajunitjerseyjersey-client

Java Jersey ClientResponse returns wrong Status from getClientResponseStatus


I feel like I am missing something here. I have a filter which prints out my server's returned information and I report that I am returning the correct response (403). I wrote a JUnit test to verify this logic and many times I am reporting 200 instead of 403. The weird part is that my server logs still show that I sent a 403. Is there some known bug in Jersey 1.17 that I am not aware of and I need to upgrade to resolve? I am not really in a position to upgrade at this point in time so I am hoping there is some bug in my logic. Here is my test case.

@Test
public void testIdsOwnedBySomeoneElse()
{
    final Login user1Cred = Logins.getInstance().getLogin(Logins.LoginType.User1);
    final Login user2Cred = Logins.getInstance().getLogin(Logins.LoginType.User2);
    final ServiceEndpointAuthenticated authUser1 = LoginHelper.Login(user1Cred);
    final ServiceEndpointAuthenticated authUser2 = LoginHelper.Login(user2Cred);

    // Create generic entry owned by user 1
    BigInteger user1Id = null;
    {
        final Object payload = endpoint.CreateEntity(authUser1.getUserId());
        final ClientResponse response = endpoint.Post(authUser1, payload);
        assertTrue(Status.OK == response.getClientResponseStatus());
        final byte[] data = Utilities.getBytes(response.getEntityInputStream());
        user1Id = endpoint.getEntityId(data);
    }

    // Using User2, try to use that id from user1!
    {
        // test 1
        final MyEndpointWrapper endpoint = new MyEndpointWrapper(user1Id, validId);
        final Object payload = endpoint.CreateEntity(authUser2.getUserId());
        final ClientResponse response = endpoint.Post(authUser2, payload);
        final Status status = response.getClientResponseStatus();
        System.out.println("Returned status = " + status);
        if (status != Status.FORBIDDEN)
        {
            byte[] data = Utilities.getBytes(response.getEntityInputStream());
            String toString = null;
            try
            {
                toString = new String(data, "UTF-8");
            }
            catch (UnsupportedEncodingException e)
            {
            }
            System.out.println("data: " + toString);
        }
        assertEquals("Status " + status + " is not forbidden!", Status.FORBIDDEN, status);
    }

    {
        // test 2
        final MyEndpointWrapper endpoint = new MyEndpointWrapper(validId, user1Id);
        final Object payload = endpoint.CreateEntity(authUser2.getUserId());
        final ClientResponse response = endpoint.Post(authUser2, payload);
        final Status status = response.getClientResponseStatus();
        System.out.println("Returned status = " + status);
        if (status != Status.FORBIDDEN)
        {
            int i = 9;
        }
        assertEquals("Status " + status + " is not forbidden!", Status.FORBIDDEN, status);
    }

    // Go ahead and delete this data for cleanup
    assertTrue(Status.OK == endpoint.Delete(authUser1, user1Id).getClientResponseStatus());
}

My generic code first logs into our server for the creds. These creds are "attached" to the WebResource and it attaches the proper headers automatically when I build my request. I first create an entity, post it, and store the returned id to be used by another user. I create another endpointwrapper which references that violation id and I attempt to post with that id. The server logs:

INFO: RESPONSE: 403 http://myendpoint MediaType:(application/json) Payload: 232 MyErrorMessage

I can even print this message out (as shown above)! The part I dont understand is that getClientResponseStatus returned to me OK. Why?

My Post code looks like:

@Override
public ClientResponse Post(ServiceEndpointAuthenticated endpoint, Object entity)
{
    MyUploadData uploadData = (MyUploadData)entity;
    return endpoint.getResourceBuilder("/myendpoint")
            .accept(MediaTypeExt.APPLICATION_JSON)
            .type(MediaTypeExt.APPLICATION_JSON)
            .post(ClientResponse.class, gson.toJson(uploadData));
}

[UPDATE] I ran wire capture and actually do see 200 being sent back! This does appear to be something inside of Jersey Server. Here is what I see:

When working:
Request: 1099   17.021219000    127.0.0.1   127.0.0.1   HTTP    2214    POST /myEndpoint HTTP/1.1  (application/json)
Response: 1153  17.042535000    127.0.0.1   127.0.0.1   HTTP    628 HTTP/1.1 400 Bad Request  (application/json)

When not working:
Request: 1161   17.044313000    127.0.0.1   127.0.0.1   HTTP    250 POST /myEndpoint HTTP/1.1  (application/json)
Response: 1217  17.066059000    127.0.0.1   127.0.0.1   HTTP    412 HTTP/1.1 200 OK 

When it works I see my normal headers in the response (eg: Access-Control-*, Pragma no cache, etc). When it doesn't work I dont see any of my headers but I do see "Transfer-Encoding: chunked" and my response is my error message but my response code is 200. I added an explicit Trace statement in the server right before I sent my response to ensure I am sending the right Status and I am.

I am okay with allowing chunked transfer but I am not really okay with losing my desired http response.


Solution

  • Incase anyone else encounters something similar. After digging around I finally found the problem. We have a heartbeat on some of our endpoints. Some endpoints can take longer than expected time. To ensure the client doesn't disconnect prematurely we have a component which attaches to the ServletOutputStream. This sends a space to the client to keep the connection alive.

    When an error is thrown (caught by our new exception remapper), this keep-alive component was not being shutdown properly. This caused Jersey to switch into chunked mode. Ensuring the keep-alive component was shutdown properly resolved the problem.