Skip to content

Commit 33862f4

Browse files
committed
HTTPCLIENT-2396 - add Server-Sent Events client to HttpClient5.
EventSource API + SseExecutor, CHAR/BYTE parsers, pluggable BackoffStrategy, Last-Event-ID and Retry-After.
1 parent 79992d8 commit 33862f4

35 files changed

+4860
-1
lines changed

httpclient5-sse/pom.xml

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
====================================================================
4+
Licensed to the Apache Software Foundation (ASF) under one
5+
or more contributor license agreements. See the NOTICE file
6+
distributed with this work for additional information
7+
regarding copyright ownership. The ASF licenses this file
8+
to you under the Apache License, Version 2.0 (the
9+
"License"); you may not use this file except in compliance
10+
with the License. You may obtain a copy of the License at
11+
12+
http://www.apache.org/licenses/LICENSE-2.0
13+
14+
Unless required by applicable law or agreed to in writing,
15+
software distributed under the License is distributed on an
16+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
KIND, either express or implied. See the License for the
18+
specific language governing permissions and limitations
19+
under the License.
20+
====================================================================
21+
22+
This software consists of voluntary contributions made by many
23+
individuals on behalf of the Apache Software Foundation. For more
24+
information on the Apache Software Foundation, please see
25+
<http://www.apache.org />.
26+
--><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
27+
<modelVersion>4.0.0</modelVersion>
28+
<parent>
29+
<groupId>org.apache.httpcomponents.client5</groupId>
30+
<artifactId>httpclient5-parent</artifactId>
31+
<version>5.6-alpha1-SNAPSHOT</version>
32+
</parent>
33+
<artifactId>httpclient5-sse</artifactId>
34+
<name>Apache HttpClient sse</name>
35+
<inceptionYear>2011</inceptionYear>
36+
<description>Apache HttpComponents Client Server-Sent</description>
37+
<packaging>jar</packaging>
38+
39+
<properties>
40+
<Automatic-Module-Name>org.apache.httpcomponents.client5.httpclient5.sse</Automatic-Module-Name>
41+
</properties>
42+
43+
<dependencies>
44+
<dependency>
45+
<groupId>org.apache.httpcomponents.client5</groupId>
46+
<artifactId>httpclient5</artifactId>
47+
</dependency>
48+
<dependency>
49+
<groupId>org.apache.httpcomponents.client5</groupId>
50+
<artifactId>httpclient5</artifactId>
51+
<scope>test</scope>
52+
<classifier>tests</classifier>
53+
</dependency>
54+
<dependency>
55+
<groupId>org.slf4j</groupId>
56+
<artifactId>slf4j-api</artifactId>
57+
</dependency>
58+
<dependency>
59+
<groupId>org.apache.logging.log4j</groupId>
60+
<artifactId>log4j-slf4j-impl</artifactId>
61+
<scope>test</scope>
62+
</dependency>
63+
<dependency>
64+
<groupId>org.apache.logging.log4j</groupId>
65+
<artifactId>log4j-core</artifactId>
66+
<scope>test</scope>
67+
</dependency>
68+
<dependency>
69+
<groupId>org.junit.jupiter</groupId>
70+
<artifactId>junit-jupiter</artifactId>
71+
<scope>test</scope>
72+
</dependency>
73+
<dependency>
74+
<groupId>org.hamcrest</groupId>
75+
<artifactId>hamcrest</artifactId>
76+
<scope>test</scope>
77+
</dependency>
78+
<dependency>
79+
<groupId>org.mockito</groupId>
80+
<artifactId>mockito-core</artifactId>
81+
<scope>test</scope>
82+
</dependency>
83+
</dependencies>
84+
85+
<reporting>
86+
<plugins>
87+
<plugin>
88+
<artifactId>maven-project-info-reports-plugin</artifactId>
89+
<inherited>false</inherited>
90+
<reportSets>
91+
<reportSet>
92+
<reports>
93+
<report>index</report>
94+
<report>dependencies</report>
95+
<report>dependency-info</report>
96+
<report>summary</report>
97+
</reports>
98+
</reportSet>
99+
</reportSets>
100+
</plugin>
101+
</plugins>
102+
</reporting>
103+
104+
<build>
105+
<plugins>
106+
<plugin>
107+
<groupId>com.github.siom79.japicmp</groupId>
108+
<artifactId>japicmp-maven-plugin</artifactId>
109+
<version>0.21.2</version>
110+
<configuration>
111+
<skip>true</skip>
112+
</configuration>
113+
</plugin>
114+
</plugins>
115+
</build>
116+
117+
118+
</project>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
package org.apache.hc.client5.http.sse;
28+
29+
/**
30+
* Computes the next reconnect delay for SSE (in milliseconds).
31+
* <p>
32+
* Implementations may also override {@link #shouldReconnect(int, long, Long)}
33+
* to decline reconnects entirely (e.g., a "no strategy" that never reconnects).
34+
*/
35+
public interface BackoffStrategy {
36+
37+
/**
38+
* @param attempt consecutive reconnect attempt number (1-based)
39+
* @param previousDelayMs last delay used (0 for first attempt)
40+
* @param serverRetryHintMs value from server 'retry:' (ms) or HTTP Retry-After, or null if none
41+
* @return delay in milliseconds (>= 0)
42+
*/
43+
long nextDelayMs(int attempt, long previousDelayMs, Long serverRetryHintMs);
44+
45+
/**
46+
* Whether a reconnect should be attempted at all.
47+
* Default is {@code true} for backward compatibility.
48+
*
49+
* @param attempt consecutive reconnect attempt number (1-based)
50+
* @param previousDelayMs last delay used (0 for first attempt)
51+
* @param serverRetryHintMs value from server 'retry:' (ms) or HTTP Retry-After, or null if none
52+
* @return true to reconnect, false to stop
53+
*/
54+
default boolean shouldReconnect(final int attempt, final long previousDelayMs, final Long serverRetryHintMs) {
55+
return true;
56+
}
57+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
package org.apache.hc.client5.http.sse;
28+
29+
import java.util.Map;
30+
31+
/**
32+
* Represents a Server-Sent Events (SSE) connection to a remote resource.
33+
*
34+
* <p>This interface exposes the minimal control surface for an SSE stream:
35+
* starting, cancelling, header management, and basic state and offset (Last-Event-ID).
36+
* Implementations are provided by this module (for example, via
37+
* a factory such as {@code SseExecutor#open(...)}), which wires the
38+
* {@link EventSourceListener} that receives events.</p>
39+
*
40+
* <h3>Thread-safety</h3>
41+
* <p>Implementations should be safe for concurrent use: {@link #start()} and
42+
* {@link #cancel()} are expected to be idempotent, and header methods should be
43+
* safe to call at any time prior to (re)connect.</p>
44+
*
45+
* <h3>Last-Event-ID</h3>
46+
* <p>{@code Last-Event-ID} follows the SSE spec: the most recent non-null event
47+
* id is remembered by the implementation and can be sent on subsequent reconnects
48+
* so the server can resume the stream.</p>
49+
*
50+
* @since 5.6
51+
*/
52+
public interface EventSource {
53+
54+
/**
55+
* Begins streaming events.
56+
* <ul>
57+
* <li>Idempotent: calling when already started is a no-op.</li>
58+
* <li>Non-blocking: returns immediately.</li>
59+
* <li>Reconnects: implementation-specific and driven by configuration.</li>
60+
* </ul>
61+
*
62+
* @since 5.6
63+
*/
64+
void start();
65+
66+
/**
67+
* Cancels the stream and prevents further reconnects.
68+
* <ul>
69+
* <li>Idempotent: safe to call multiple times.</li>
70+
* <li>Implementations should eventually invoke the listener’s
71+
* {@code onClosed()} exactly once.</li>
72+
* </ul>
73+
*
74+
* @since 5.6
75+
*/
76+
void cancel();
77+
78+
/**
79+
* Returns the last seen event id or {@code null} if none has been observed.
80+
*
81+
* @return the last event id, or {@code null}
82+
* @since 5.6
83+
*/
84+
String lastEventId();
85+
86+
/**
87+
* Sets the outbound {@code Last-Event-ID} that will be sent on the next connect
88+
* or reconnect attempt. Passing {@code null} clears the value.
89+
*
90+
* @param id the id to send, or {@code null} to clear
91+
* @since 5.6
92+
*/
93+
void setLastEventId(String id);
94+
95+
/**
96+
* Sets or replaces a request header for subsequent (re)connects.
97+
* Header names are case-insensitive per RFC 7230/9110.
98+
*
99+
* @param name header name (non-null)
100+
* @param value header value (may be empty but not null)
101+
* @since 5.6
102+
*/
103+
void setHeader(String name, String value);
104+
105+
/**
106+
* Removes a previously set request header for subsequent (re)connects.
107+
*
108+
* @param name header name to remove (non-null)
109+
* @since 5.6
110+
*/
111+
void removeHeader(String name);
112+
113+
/**
114+
* Returns a snapshot of the currently configured headers that will be used
115+
* for the next request. The returned map is a copy and may be modified
116+
* by the caller without affecting the {@code EventSource}.
117+
*
118+
* @return copy of headers to be sent
119+
* @since 5.6
120+
*/
121+
Map<String, String> getHeaders();
122+
123+
/**
124+
* Indicates whether the SSE connection is currently open.
125+
* Implementations may report {@code false} during backoff between reconnects.
126+
*
127+
* @return {@code true} if the transport is open and events may be received
128+
* @since 5.6
129+
*/
130+
boolean isConnected();
131+
}

0 commit comments

Comments
 (0)