How do I get Jersey Test/Client to not fill in a default Accept header?
Asked Answered
C

3

7

I'm trying to handle a request with no Accept header in a particular way, but Jersey seems hell-bent on filling one in, no matter what I do, so it always looks like the request has an Accept header, even if it doesn't.

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;

import static org.junit.Assert.assertEquals;

public class JerseyTestTest extends JerseyTest {

    @Path("hello")
    public static class HelloResource {
        @GET
        public String getHello(@Context HttpHeaders httpHeaders) {
            String acceptHeader = httpHeaders.getHeaderString(HttpHeaders.ACCEPT);
            return acceptHeader != null ? acceptHeader : "No Accept Header";
        }
    }

    @Override
    protected Application configure() {
        return new ResourceConfig(HelloResource.class);
    }

    @Test
    public void test() {
        final String hello = target("hello").request()
                .header(HttpHeaders.ACCEPT, null) // null means remove header
                .get(String.class);
        assertEquals("No Accept Header", hello);
    }
}

This test results in:

org.junit.ComparisonFailure: 
Expected :No Accept Header
Actual   :text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2

Somehow there is a default Accept header of text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 that gets set somewhere. It's not documented, and I would love to figure out how to disable it. I've looked through the Jersey source, but can't seem to locate where this is happening or why.

Update: when I use curl to hit an endpoint without an Accept header, there is no generated Accept header, so the problem lies in Jersey Client or the Jersey Test environment, somehow.

Update 2: This bug exhibits when using the default Grizzly2 test container or the JDK test container, but NOT with the In Memory test container.

Centralize answered 1/12, 2016 at 0:54 Comment(8)
It probably gets set at the time when UserAgent is created for the request. There is a good insight on it tools.ietf.org/html/rfc7231#section-5.3.2. Also to solve the above can you try and override the Accept header with null/empty value within your Resource class?Papandreou
@nullpointer I found the code in Jersey that adds the User-Agent for requests, and I didn't see any code nearby that adds an Accept header, so no, that's not when it gets added. I know exactly what the Accept header is for, and I've read the RFCs and refer to them a lot. I have no idea what you mean by overriding the Accept header in the Resource class. The Resource class is what is on the server, there is nothing in the Jersey docs about overriding headers, and I'm not trying to replace the Accept header that I am receiving. I want to see what the Accept header was, or if there was none.Centralize
Have you tried with wget or curl as client? What exactly does the @Test method do?Disgraceful
@Disgraceful I tested the endpoint separately with curl and there is no Accept header generated. I added a note to the ticket, so the generated Accept header must be happening in the Jersey Test/Client code somewhere.Centralize
I don't suppose you ever figured this out? I have the same issue.Arc
@Arc I never did, beyond "Update 2" above. I did learn that the Jersey and related library code is underdocumented and overly complex. Recommend: avoid Jersey. Sadly, we're too far invested, but save yourself.Centralize
I'm also running into this. I found these two that might give other people looking into this a clue on what's wrong: bugs.openjdk.java.net/browse/JDK-8163921, jira.spring.io/browse/SWS-520Gelman
Did anyone ever create an issue for the Jersey team?Neurilemma
N
0

Not sure if this helps anyone else but we were seeing similar behavior calling a service in production with our client code. We are using Jersey 2.21.1.

Expanded the code from the original post and found the following to be true:

  • if Accept header is null then Jersey adds the default
  • if Accept header is an empty String then an empty Accept header is used
  • if Accept header has a value it is used

I'm not sure if there is a way to tell Jersey not to add the default value when a null is used.

public class JerseyAcceptHeaderTest extends JerseyTest {

    @Path("hello")
    public static class HelloResource {
        @GET
        public String getHello(@Context HttpHeaders httpHeaders) {
            String acceptHeader = httpHeaders.getHeaderString(HttpHeaders.ACCEPT);
            System.out.println("SERVER RECEIVED:" + acceptHeader);

            if (acceptHeader == null) {
                return "Null Accept Header";
            } else if (acceptHeader.equals("")) {
                return "No Accept Header";
            } else {
                return acceptHeader;
            }
        }
    }

    @Override
    protected Application configure() {
        return new ResourceConfig(HelloResource.class);
    }

    /**
     * this seems to be a bug in Jersey
     * it overrides a null Accept header
     */
    @Test
    public void test_accept_header_with_null() {
        final String acceptHeader = target("hello").request()
                .header(HttpHeaders.ACCEPT, null)
                .get(String.class);
        assertEquals("Null Accept Header", acceptHeader);
    }

    @Test
    public void test_accept_header_with_empty_string() {
        final String acceptHeader = target("hello").request()
                .header(HttpHeaders.ACCEPT, "")
                .get(String.class);
        assertEquals("No Accept Header", acceptHeader);
    }

    @Test
    public void test_accept_header_with_spaced_string() {
        final String acceptHeader = target("hello").request()
                .header(HttpHeaders.ACCEPT, "  ")
                .get(String.class);
        assertEquals("No Accept Header", acceptHeader);
    }

    @Test
    public void test_accept_header_with_value() {
        final String acceptHeader = target("hello").request()
                .header(HttpHeaders.ACCEPT, "application/json")
                .get(String.class);
        assertEquals("application/json", acceptHeader);
    }

}
Neurilemma answered 5/2, 2018 at 20:20 Comment(0)
B
0

I'm facing the same issue right now and after some research, I ended up finding the problem.

WebResourceFactory.invoke(final Object proxy, final Method method, final Object[] {
    ...
    Invocation.Builder builder = newTarget.request()
            .headers(headers) // this resets all headers so do this first
            .accept(accepts); // if @Produces is defined, propagate values into Accept header; empty array is NO-OP
    ...
}

The request builder is automatically adding into the existing headers the media types specified in the API (@Produce). Sadly I did not find a way to disable that behaviour. So I'll try to extend the WebResourceFactory class (which is final) used to create my client and overrided that method invoke to not call the accept on the request builder.

Be aware that I dont consider this as a solution but more as a workaround.

[EDIT] Since WebResourceFactory is final, I have duplicated it's content and removed the accept call on the request builder. A little bit ugly, I know and hope someone will find a better way.

Budweis answered 10/10, 2019 at 9:48 Comment(0)
C
0

I've implemented a workaround too. In case the input for the method which adds the Accept header to the headers list:

public void accept(final String... types)

from the ClientRequest class, is empty, I call WebTarget.request() instead of WebTarget.request().accept(inputValue)

In my opinion there should be no Accept header set if the input is null or an empty string. Therefore I've created an issue @Jersey for that topic: https://github.com/eclipse-ee4j/jersey/issues/4407

Collection answered 10/3, 2020 at 13:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.