All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.king.platform.net.http.netty.sse.SseClientImpl Maven / Gradle / Ivy

// Copyright (C) king.com Ltd 2016
// https://github.com/king/king-http-client
// Author: Magnus Gustafsson
// License: Apache 2.0, https://raw.github.com/king/king-http-client/LICENSE-APACHE

package com.king.platform.net.http.netty.sse;


import com.king.platform.net.http.*;
import com.king.platform.net.http.netty.eventbus.Event;
import com.king.platform.net.http.netty.eventbus.ExternalEventTrigger;
import com.king.platform.net.http.netty.requestbuilder.BuiltNettyClientRequest;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;

import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;

public class SseClientImpl implements SseClient {
	private final ExternalEventTrigger externalEventTrigger;
	private final BuiltNettyClientRequest builtNettyClientRequest;
	private final DelegatingHttpCallback httpCallback;
	private final VoidResponseConsumer responseBodyConsumer;
	private final ServerEventDecoder serverEventDecoder;
	private final DelegatingNioHttpCallback nioCallback;

	private AtomicReference state = new AtomicReference<>(State.DISCONNECTED);

	private final ConcurrentHashMap> eventCallbackMap = new ConcurrentHashMap<>();
	private List dataCallback = new CopyOnWriteArrayList<>();

	private CountDownLatch countDownLatch;

	public SseClientImpl(SseExecutionCallback providedSseExecutionCallback, BuiltNettyClientRequest builtNettyClientRequest, Executor httpClientCallbackExecutor) {
		if (providedSseExecutionCallback == null) {
			providedSseExecutionCallback = new EmptySseExecutionCallback();
		}

		providedSseExecutionCallback = new WrappedSseExecutionCallback(providedSseExecutionCallback);

		this.builtNettyClientRequest = builtNettyClientRequest;

		externalEventTrigger = new ExternalEventTrigger();

		httpCallback = new DelegatingHttpCallback(providedSseExecutionCallback);
		responseBodyConsumer = new VoidResponseConsumer();
		serverEventDecoder = new ServerEventDecoder(providedSseExecutionCallback, httpClientCallbackExecutor);
		nioCallback = new DelegatingNioHttpCallback(serverEventDecoder, providedSseExecutionCallback, httpClientCallbackExecutor);
	}


	@Override
	public void close() {
		externalEventTrigger.trigger(Event.CLOSE, null);
	}

	@Override
	public void subscribe(String eventName, SseCallback callback) {
		List sseCallbacks = eventCallbackMap.get(eventName);
		if (sseCallbacks == null) {
			sseCallbacks = new CopyOnWriteArrayList<>();
			List prevValue = eventCallbackMap.putIfAbsent(eventName, sseCallbacks);
			if (prevValue != null) {
				sseCallbacks = prevValue;
			}
		}
		sseCallbacks.add(callback);
	}

	@Override
	public void subscribe(SseCallback callback) {
		dataCallback.add(callback);
	}

	@Override
	public void awaitClose() throws  InterruptedException {
		countDownLatch.await();
	}


	@Override
	public void connect() {
		if (!state.compareAndSet(State.DISCONNECTED, State.RECONNECTING)) {
			throw new RuntimeException("sse client is not in disconnected state");
		}

		if (countDownLatch != null) {
			countDownLatch.countDown();
		}

		countDownLatch = new CountDownLatch(1);

		serverEventDecoder.reset();

		builtNettyClientRequest.execute(httpCallback, responseBodyConsumer, nioCallback, externalEventTrigger);
	}

	private class WrappedSseExecutionCallback implements SseExecutionCallback {
		private final SseExecutionCallback sseExecutionCallback;

		private WrappedSseExecutionCallback(SseExecutionCallback sseExecutionCallback) {
			this.sseExecutionCallback = sseExecutionCallback;
		}

		@Override
		public void onConnect() {
			sseExecutionCallback.onConnect();
		}

		@Override
		public void onDisconnect() {
			sseExecutionCallback.onDisconnect();
		}

		@Override
		public void onError(Throwable throwable) {
			sseExecutionCallback.onError(throwable);
		}

		@Override
		public void onEvent(String lastSentId, String event, String data) {
			sseExecutionCallback.onEvent(lastSentId, event, data);

			invokeCallbacks(lastSentId, event, data, dataCallback);

			if (event != null) {
				List sseCallbacks = eventCallbackMap.get(event);
				invokeCallbacks(lastSentId, event, data, sseCallbacks);
			}
		}

		private void invokeCallbacks(String lastSentId, String event, String data, List sseCallbacks) {
			if (sseCallbacks != null) {
				for (SseCallback sseCallback : sseCallbacks) {
					sseCallback.onEvent(lastSentId, event, data);
				}
			}
		}

	}

	private class DelegatingNioHttpCallback implements NioCallback {

		private final ServerEventDecoder serverEventDecoder;
		private final SseExecutionCallback providedSseExecutionCallback;
		private final Executor httpClientCallbackExecutor;

		public DelegatingNioHttpCallback(ServerEventDecoder serverEventDecoder, SseExecutionCallback providedSseExecutionCallback, Executor httpClientCallbackExecutor) {
			this.serverEventDecoder = serverEventDecoder;
			this.providedSseExecutionCallback = providedSseExecutionCallback;
			this.httpClientCallbackExecutor = httpClientCallbackExecutor;
		}

		@Override
		public void onConnecting() {

		}

		@Override
		public void onConnected() {
			state.set(State.CONNECTED);
			httpClientCallbackExecutor.execute(new Runnable() {
				@Override
				public void run() {
					providedSseExecutionCallback.onConnect();
				}
			});
		}

		@Override
		public void onWroteHeaders() {

		}

		@Override
		public void onWroteContentProgressed(long progress, long total) {

		}

		@Override
		public void onWroteContentCompleted() {

		}

		@Override
		public void onReceivedStatus(HttpResponseStatus httpResponseStatus) {

		}

		@Override
		public void onReceivedHeaders(HttpHeaders httpHeaders) {

		}

		@Override
		public void onReceivedContentPart(int len, ByteBuf buffer) {
			serverEventDecoder.onReceivedContentPart(buffer);
		}

		@Override
		public void onReceivedCompleted(HttpResponseStatus httpResponseStatus, HttpHeaders httpHeaders) {

		}

		@Override
		public void onError(Throwable throwable) {

		}
	}


	private class DelegatingHttpCallback implements HttpCallback {
		private final SseExecutionCallback sseExecutionCallback;

		DelegatingHttpCallback(SseExecutionCallback sseExecutionCallback) {
			this.sseExecutionCallback = sseExecutionCallback;
		}

		@Override
		public void onCompleted(HttpResponse httpResponse) {
			sseExecutionCallback.onDisconnect();
			state.set(State.DISCONNECTED);
			countDownLatch.countDown();

		}

		@Override
		public void onError(Throwable throwable) {
			sseExecutionCallback.onError(throwable);
			state.set(State.DISCONNECTED);
			countDownLatch.countDown();
		}
	}


	private enum State {
		CONNECTED,
		DISCONNECTED,
		RECONNECTING,
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy