Клиент отправленного сервера отправлен с дополнительным файлом cookie

Я пытаюсь использовать unit test ресурс сервера отправленного события с дополнительным файлом cookie. Я уже использую Jersey для EventSource и JavaX для клиента. Следующий код работает нормально:

    WebTarget target = ClientBuilder.newBuilder()
        .register(SseFeature.class)
        .build()
        .target("http://localhost:8080/sse");
    EventSource eventSource = EventSource.target(target).build();
    EventListener listener = new EventListener() {
        @Override
        public void onEvent(InboundEvent inboundEvent) {
            LOG.info(inboundEvent.readData(String.class));
        }
    };
    eventSource.register(listener);
    eventSource.open();
    serverEventManager.send("/sse", "foo");
    eventSource.close();

Однако для дополнительного unit test мне нужно добавить дополнительный файл cookie в запрос. Я уже пробовал следующие

target.(...).request.cookie("foo", "bar");

Но это возвращает построитель, из которого я не могу создать требуемый WebTarget для EventSource.

Ответ 1

Здесь, что происходит в EventSource, чтобы установить соединение с данным WebTarget:

       private Invocation.Builder prepareHandshakeRequest() {
            Invocation.Builder request = EventSource.this.target
                    .request(new MediaType[] { SseFeature.SERVER_SENT_EVENTS_TYPE });
            if ((this.lastEventId != null) && (!(this.lastEventId.isEmpty()))) {
                request.header("Last-Event-ID", this.lastEventId);
            }
            if (EventSource.this.disableKeepAlive) {
                request.header("Connection", "close");
            }
            return request;
          }

Как мы видим, нет никакой возможности добавить здесь cookie.

Итак, WebTarget.request(new MediaType[] { SseFeature.SERVER_SENT_EVENTS_TYPE }) должен вернуть Builder, который уже добавлен желаемый файл cookie.

Рассмотрим этот делегирующий класс, добавляющий желаемый файл cookie ко всем методам типа request*:

public class CookieAddedWebTarget implements WebTarget {

    private WebTarget base;

    private Cookie cookie;

    public CookieAddedWebTarget(WebTarget base, Cookie cookie) {
        this.base = base;
        this.cookie = cookie;
    }

    // Inject that cookie whenever someone requests a Builder (like EventSource does):
    public Builder request() {
        return base.request().cookie(cookie);
    }

    public Builder request(String... paramArrayOfString) {
        return base.request(paramArrayOfString).cookie(cookie);
    }

    public Builder request(MediaType... paramArrayOfMediaType) {
        return base.request(paramArrayOfMediaType).cookie(cookie);
    }

    public Configuration getConfiguration() {
        return base.getConfiguration();
    }

    //All other methods from WebTarget are delegated as-is:

    public URI getUri() {
        return base.getUri();
    }

    public UriBuilder getUriBuilder() {
        return base.getUriBuilder();
    }

    public WebTarget path(String paramString) {
        return base.path(paramString);
    }

    public WebTarget matrixParam(String paramString, Object... paramArrayOfObject) {
        return base.matrixParam(paramString, paramArrayOfObject);
    }

    public WebTarget property(String paramString, Object paramObject) {
        return base.property(paramString, paramObject);
    }

    public WebTarget queryParam(String paramString, Object... paramArrayOfObject) {
        return base.queryParam(paramString, paramArrayOfObject);
    }

    public WebTarget register(Class<?> paramClass, Class<?>... paramArrayOfClass) {
        return base.register(paramClass, paramArrayOfClass);
    }

    public WebTarget register(Class<?> paramClass, int paramInt) {
        return base.register(paramClass, paramInt);
    }

    public WebTarget register(Class<?> paramClass, Map<Class<?>, Integer> paramMap) {
        return base.register(paramClass, paramMap);
    }

    public WebTarget register(Class<?> paramClass) {
        return base.register(paramClass);
    }

    public WebTarget register(Object paramObject, Class<?>... paramArrayOfClass) {
        return base.register(paramObject, paramArrayOfClass);
    }

    public WebTarget register(Object paramObject, int paramInt) {
        return base.register(paramObject, paramInt);
    }

    public WebTarget register(Object paramObject, Map<Class<?>, Integer> paramMap) {
        return base.register(paramObject, paramMap);
    }

    public WebTarget register(Object paramObject) {
        return base.register(paramObject);
    }

    public WebTarget resolveTemplate(String paramString, Object paramObject) {
        return base.resolveTemplate(paramString, paramObject);
    }

    public WebTarget resolveTemplate(String paramString, Object paramObject, boolean paramBoolean) {
        return base.resolveTemplate(paramString, paramObject, paramBoolean);
    }

    public WebTarget resolveTemplateFromEncoded(String paramString, Object paramObject) {
        return base.resolveTemplateFromEncoded(paramString, paramObject);
    }

    public WebTarget resolveTemplates(Map<String, Object> paramMap) {
        return base.resolveTemplates(paramMap);
    }

    public WebTarget resolveTemplates(Map<String, Object> paramMap, boolean paramBoolean) {
        return base.resolveTemplates(paramMap, paramBoolean);
    }

    public WebTarget resolveTemplatesFromEncoded(Map<String, Object> paramMap) {
        return base.resolveTemplatesFromEncoded(paramMap);
    }

}

Теперь вы сможете повторить тест:

EventSource eventSource = EventSource.target(new CookieAddedWebTarget(target, 
                            new Cookie("name", "value"))).build();

И файл cookie должен быть вставлен.

Предостережение. У меня нет возможности проверить это. Решение основано только на исходном коде чтения jersey-media-sse-2.22.1.

Удачи.

Ответ 2

Вы можете установить cookie в ClientRequestFilter. Хотя getCookies() на ClientRequestContext неизменен, вы должны помнить, что файл cookie - это не что иное, как заголовок. А карта заголовков в контексте запроса изменчива. Таким образом, вы можете сделать что-то вроде

public static class SseCookieFilter implements ClientRequestFilter {

    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        Cookie cookie = new Cookie("foo", "bar");
        requestContext.getHeaders().add("Cookie", cookie.toString());
    }
}

Просто зарегистрируйте фильтр с клиентом (client.register(new SseCookieFilter())). Это был бы тот же результат, что и вы

target.(...).request().cookie("foo", "bar");

Вот полный пример, используя тестовую структуру Джерси

public class SseCookieFilterTest extends JerseyTest {

    @Path("events")
    public static class SseResource {

        @GET
        @Produces(SseFeature.SERVER_SENT_EVENTS)
        public EventOutput getServerSentEvents(@CookieParam("foo") String foo) {
            final EventOutput eventOutput = new EventOutput();
            new Thread(() -> {
                try {
                    final OutboundEvent.Builder eventBuilder
                            = new OutboundEvent.Builder();
                    eventBuilder.name("message");
                    eventBuilder.data(String.class, "Blah " + foo + "!!!");
                    final OutboundEvent event = eventBuilder.build();
                    eventOutput.write(event);

                } catch (IOException e) {
                    throw new RuntimeException(e);
                } finally {
                    try {
                        eventOutput.close();
                    } catch (IOException ioClose) {
                        throw new RuntimeException(ioClose);
                    }
                }
            }).start();
            return eventOutput;
        }
    }

    public static class SseCookieFilter implements ClientRequestFilter {

        @Override
        public void filter(ClientRequestContext requestContext) throws IOException {
            Cookie cookie = new Cookie("foo", "bar");
            requestContext.getHeaders().add("Cookie", cookie.toString());
        }
    }

    @Override
    public ResourceConfig configure() {
        return new ResourceConfig(SseResource.class)
                .register(new LoggingFilter());
    }

    @Test
    public void doit() throws Exception {
        Client client = ClientBuilder.newBuilder()
                .register(SseFeature.class).build();
        client.register(new SseCookieFilter());
        WebTarget target = client.target("http://localhost:9998/events");
        EventSource eventSource = EventSource.target(target).build();
        EventListener listener = (InboundEvent inboundEvent) -> {
            System.out.println("From server ---====++++>  " 
                    + inboundEvent.readData(String.class));
        };
        eventSource.register(listener, "message");
        eventSource.open();
        Thread.sleep(100);
        eventSource.close();
    }
}

Это единственные зависимости, необходимые для тестирования

<dependency>
    <groupId>org.glassfish.jersey.test-framework.providers</groupId>
    <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
    <version>${jersey2.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-sse</artifactId>
    <version>${jersey2.version}</version>
    <scope>test</scope>
</dependency>

Вот результат на стороне сервера из LoggingFilter, который я зарегистрировал на сервере в тесте

INFO: 1 * Server has received a request on thread grizzly-http-server-2
1 > GET http://localhost:9998/events
1 > accept: text/event-stream
1 > connection: close
1 > cookie: $Version=1;foo=bar
1 > host: localhost:9998
1 > user-agent: Jersey/2.19 (HttpUrlConnection 1.8.0_31)

INFO: 1 * Server responded with a response on thread grizzly-http-server-2
1 < 200
1 < Content-Type: text/event-stream

From server ---====++++>  Blah bar!!!