mirror of
https://github.com/square/okhttp.git
synced 2025-11-26 06:43:09 +03:00
Additional error checking for SSE (#4082)
* Additional error checking for SSE * move to sse package, more tests, kotlin null friendly
This commit is contained in:
@@ -55,6 +55,18 @@
|
|||||||
</links>
|
</links>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.0.2</version>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifestEntries>
|
||||||
|
<Automatic-Module-Name>okhttp3.sse</Automatic-Module-Name>
|
||||||
|
</manifestEntries>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -20,12 +20,13 @@ import javax.annotation.Nullable;
|
|||||||
import okhttp3.Call;
|
import okhttp3.Call;
|
||||||
import okhttp3.Callback;
|
import okhttp3.Callback;
|
||||||
import okhttp3.EventListener;
|
import okhttp3.EventListener;
|
||||||
import okhttp3.EventSource;
|
import okhttp3.MediaType;
|
||||||
import okhttp3.EventSourceListener;
|
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
import okhttp3.internal.Util;
|
import okhttp3.internal.Util;
|
||||||
|
import okhttp3.sse.EventSource;
|
||||||
|
import okhttp3.sse.EventSourceListener;
|
||||||
import okio.BufferedSource;
|
import okio.BufferedSource;
|
||||||
|
|
||||||
public final class RealEventSource
|
public final class RealEventSource
|
||||||
@@ -50,24 +51,43 @@ public final class RealEventSource
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override public void onResponse(Call call, Response response) {
|
@Override public void onResponse(Call call, Response response) {
|
||||||
//noinspection ConstantConditions main body is never null
|
|
||||||
BufferedSource source = response.body().source();
|
|
||||||
ServerSentEventReader reader = new ServerSentEventReader(source, this);
|
|
||||||
|
|
||||||
response = response.newBuilder()
|
|
||||||
.body(Util.EMPTY_RESPONSE)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
listener.onOpen(this, response);
|
//noinspection ConstantConditions main body is never null
|
||||||
while (reader.processNextEvent()) {
|
BufferedSource source = response.body().source();
|
||||||
}
|
ServerSentEventReader reader = new ServerSentEventReader(source, this);
|
||||||
} catch (Exception e) {
|
|
||||||
listener.onFailure(this, e, response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
listener.onClosed(this);
|
if (!response.isSuccessful()) {
|
||||||
|
listener.onFailure(this, null, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaType contentType = response.body().contentType();
|
||||||
|
if (!isEventStream(contentType)) {
|
||||||
|
listener.onFailure(this,
|
||||||
|
new IllegalStateException("Invalid content-type: " + contentType), response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
response = response.newBuilder().body(Util.EMPTY_RESPONSE).build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
listener.onOpen(this, response);
|
||||||
|
while (reader.processNextEvent()) {
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
listener.onFailure(this, e, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listener.onClosed(this);
|
||||||
|
} finally {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isEventStream(@Nullable MediaType contentType) {
|
||||||
|
return contentType != null && contentType.type().equals("text") && contentType.subtype()
|
||||||
|
.equals("event-stream");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void onFailure(Call call, IOException e) {
|
@Override public void onFailure(Call call, IOException e) {
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
/** Private support classes for server-sent events. */
|
||||||
|
@javax.annotation.ParametersAreNonnullByDefault
|
||||||
|
package okhttp3.internal.sse;
|
||||||
@@ -13,7 +13,9 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package okhttp3;
|
package okhttp3.sse;
|
||||||
|
|
||||||
|
import okhttp3.Request;
|
||||||
|
|
||||||
public interface EventSource {
|
public interface EventSource {
|
||||||
/** Returns the original request that initiated this event source. */
|
/** Returns the original request that initiated this event source. */
|
||||||
@@ -13,9 +13,10 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package okhttp3;
|
package okhttp3.sse;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
public abstract class EventSourceListener {
|
public abstract class EventSourceListener {
|
||||||
/**
|
/**
|
||||||
@@ -43,6 +44,7 @@ public abstract class EventSourceListener {
|
|||||||
* Invoked when an event source has been closed due to an error reading from or writing to the
|
* Invoked when an event source has been closed due to an error reading from or writing to the
|
||||||
* network. Incoming events may have been lost. No further calls to this listener will be made.
|
* network. Incoming events may have been lost. No further calls to this listener will be made.
|
||||||
*/
|
*/
|
||||||
public void onFailure(EventSource eventSource, Throwable t, @Nullable Response response) {
|
public void onFailure(EventSource eventSource, @Nullable Throwable t,
|
||||||
|
@Nullable Response response) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,8 +13,10 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package okhttp3;
|
package okhttp3.sse;
|
||||||
|
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
import okhttp3.internal.sse.RealEventSource;
|
import okhttp3.internal.sse.RealEventSource;
|
||||||
|
|
||||||
public final class EventSources {
|
public final class EventSources {
|
||||||
3
okhttp-sse/src/main/java/okhttp3/sse/package-info.java
Normal file
3
okhttp-sse/src/main/java/okhttp3/sse/package-info.java
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/** Support for server-sent events. */
|
||||||
|
@javax.annotation.ParametersAreNonnullByDefault
|
||||||
|
package okhttp3.sse;
|
||||||
@@ -15,16 +15,18 @@
|
|||||||
*/
|
*/
|
||||||
package okhttp3.internal.sse;
|
package okhttp3.internal.sse;
|
||||||
|
|
||||||
import okhttp3.EventSource;
|
|
||||||
import okhttp3.EventSources;
|
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.mockwebserver.MockResponse;
|
import okhttp3.mockwebserver.MockResponse;
|
||||||
import okhttp3.mockwebserver.MockWebServer;
|
import okhttp3.mockwebserver.MockWebServer;
|
||||||
|
import okhttp3.sse.EventSource;
|
||||||
|
import okhttp3.sse.EventSources;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
public final class EventSourceHttpTest {
|
public final class EventSourceHttpTest {
|
||||||
@Rule public final MockWebServer server = new MockWebServer();
|
@Rule public final MockWebServer server = new MockWebServer();
|
||||||
|
|
||||||
@@ -38,14 +40,35 @@ public final class EventSourceHttpTest {
|
|||||||
@Test public void event() {
|
@Test public void event() {
|
||||||
server.enqueue(new MockResponse().setBody(""
|
server.enqueue(new MockResponse().setBody(""
|
||||||
+ "data: hey\n"
|
+ "data: hey\n"
|
||||||
+ "\n"));
|
+ "\n").setHeader("content-type", "text/event-stream"));
|
||||||
|
|
||||||
EventSource source = newEventSource();
|
EventSource source = newEventSource();
|
||||||
|
|
||||||
|
assertEquals("/", source.request().url().encodedPath());
|
||||||
|
|
||||||
listener.assertOpen();
|
listener.assertOpen();
|
||||||
listener.assertEvent(null, null, "hey");
|
listener.assertEvent(null, null, "hey");
|
||||||
listener.assertClose();
|
listener.assertClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test public void badContentType() {
|
||||||
|
server.enqueue(new MockResponse().setBody(""
|
||||||
|
+ "data: hey\n"
|
||||||
|
+ "\n").setHeader("content-type", "text/plain"));
|
||||||
|
|
||||||
|
EventSource source = newEventSource();
|
||||||
|
listener.assertFailure("Invalid content-type: text/plain");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void badResponseCode() {
|
||||||
|
server.enqueue(new MockResponse().setBody(""
|
||||||
|
+ "data: hey\n"
|
||||||
|
+ "\n").setHeader("content-type", "text/event-stream").setResponseCode(401));
|
||||||
|
|
||||||
|
EventSource source = newEventSource();
|
||||||
|
listener.assertFailure(null);
|
||||||
|
}
|
||||||
|
|
||||||
private EventSource newEventSource() {
|
private EventSource newEventSource() {
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(server.url("/"))
|
.url(server.url("/"))
|
||||||
|
|||||||
@@ -19,13 +19,14 @@ import java.io.IOException;
|
|||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.LinkedBlockingDeque;
|
import java.util.concurrent.LinkedBlockingDeque;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import okhttp3.EventSource;
|
import okhttp3.sse.EventSource;
|
||||||
import okhttp3.EventSourceListener;
|
import okhttp3.sse.EventSourceListener;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
import okhttp3.internal.platform.Platform;
|
import okhttp3.internal.platform.Platform;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public final class EventSourceRecorder extends EventSourceListener {
|
public final class EventSourceRecorder extends EventSourceListener {
|
||||||
@@ -89,6 +90,18 @@ public final class EventSourceRecorder extends EventSourceListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void assertFailure(@Nullable String message) {
|
||||||
|
Object event = nextEvent();
|
||||||
|
if (!(event instanceof Failure)) {
|
||||||
|
throw new AssertionError("Expected Failure but was " + event);
|
||||||
|
}
|
||||||
|
if (message != null) {
|
||||||
|
assertEquals(message, ((Failure) event).t.getMessage());
|
||||||
|
} else {
|
||||||
|
assertNull(((Failure) event).t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static final class Open {
|
static final class Open {
|
||||||
final EventSource eventSource;
|
final EventSource eventSource;
|
||||||
final Response response;
|
final Response response;
|
||||||
|
|||||||
Reference in New Issue
Block a user