I'm implementing a Server-Sent Events library using OkHttp. Server Sent Events works by keeping an open HTTP connection to the server on which 'events' can be streamed back to the client. The connection will only close on errors, or if the client explicitly disconnects.
What's the best way to achieve this streaming behaviour using OkHttp? I've attempted to do something like:
response.body().source().readAll(new Sink() {
#Override
public void write(Buffer source, long byteCount) throws IOException {
Log.d(TAG, "write(): byteCount = "+byteCount);
}
#Override
public void flush() throws IOException {
Log.d(TAG, "flush()");
}
#Override
public Timeout timeout() {
return Timeout.NONE;
}
#Override
public void close() throws IOException {
Log.d(TAG, "close()");
}
});
With this approach I will eventually see the log message in write(), but it can sometimes take quite a while (minutes). That makes me think there might be some buffering going on under the hood and I don't get my data until the buffer is flushed.
I've used curl to verify the server is behaving correctly. The data is being sent on time, I'm just not getting my callback when it arrives.
My experience with OkHttp and Okio is very limited, so it's very possible I've messed something up, or have forgotten to set some option. Any help is greatly appreciated! :)
When you call readAll() Okio prefers net throughput over latency, and so your messages are buffered into chunks. Instead, write a loop that repeatedly reads into a Buffer. That'll get you messages as they arrive.
Buffer buffer = new Buffer();
while (!source.exhausted()) {
long count = response.body().source().read(buffer, 8192);
// handle data in buffer.
}
Related
I have a Blazor Server application which listens to messages on a message broker and updates the UI with the new messages as they arrive. The gist of the component looks like this below, except of course the data is much more complex.
#page "/"
#implements IDisposable
#inject MessageBroker MessageBroker
<h1>#message?.Data</h1>
#code
{
private Message message;
protected override void OnInitialized()
{
MessageBroker.OnMessage += OnMessage;
}
private async void OnMessage(object sender, Message m)
{
message = m;
await InvokeAsync(StateHasChanged);
}
public void Dispose()
{
MessageBroker.OnMessage -= OnMessage;
}
}
The MessageBroker service is listening to messages over AMQP and invoking the OnMessage event. I find that there can significant delays between the time when the message is received and the UI updates. This is exacerbated when there are numerous clients connected via their browser.
I'm trying to track down the source of these delays and one suspect is the BuildRenderTree method which I understand is called each time StateHasChanged is invoked.
Is there anyway to see how long this method is taking? Or if there are other Blazor methods which might be responsible for the delays?
I am trying to implement simple WebSocket server using Micronaut (and Groovy). I am new to Micronaut, so learning it as I go, and with it some RxJava seems required. So learning that as well.
I can make the basic example work:
#CompileStatic
#ServerWebSocket("/ws")
class TimeseriesWebSocket {
#OnOpen
public Publisher<String> onOpen(WebSocketSession session) {
println("opening connection")
return session.send("Hello")
}
#OnMessage
public Publisher<String> onMessage(String message, WebSocketSession session) {
println("message received")
return session.send("Thanks for the message")
}
#OnClose
public Publisher<String> onClose(WebSocketSession session) {
println("Closing")
//this seems not to be delivered, I guess due to session being closed already
return session.send("OK")
}
}
So this prints out all the messages I put there and works fine with a client connecting. The client also sees the "Hello" and "Thanks for the message" messages that are return with session.send(..).
Now my problem is, I am trying to send a message outside these methods. Like this:
#CompileStatic
#ServerWebSocket("/ws")
class MyWebSocket {
#OnOpen
public Publisher<String> onOpen(WebSocketSession session) {
println("opening connection")
startPing()
return session.send("Hello")
}
//...(same as above)
public void startPing(WebSocketSession session) {
PingPing ping = new PingPing(session)
ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
exec.scheduleAtFixedRate(ping, 0, 1, TimeUnit.SECONDS);
}
}
class PingPing {
WebSocketSession session
public PingPing(WebSocketSession session) {
this.session = session
}
#Override
void run() {
println("pinging..")
try {
session.send("Ping...")
} catch (Exception e) {
e.printStackTrace()
}
}
}
This executes but nothing shows up on the client end. Now if I change session.send() to session.sendSync() it works fine. The ping is delivered.
The send() signature is actually
default <T> Publisher<T> send(T message)
I figured at first I should provide the Publisher to some other source to have it sent. But I guess this is not the case. I realized it is sort of a Future object, so if I subscribe to it myself like this:
def publisher = session.send("Ping...")
publisher.subscribe(new Subscriber<GString>() {
#Override
void onSubscribe(Subscription s) {
println("subscription")
}
#Override
void onNext(GString gString) {
println("next")
}
#Override
void onError(Throwable t) {
println("error")
t.printStackTrace()
}
#Override
void onComplete() {
println("complete")
}
})
println("publisher: ${publisher}")
Running the above piece of code (with subscribe), I guess it triggers the session.send() on the current thread and returns a result. But where should I actually call this? On what thread? I looked at the RxJava schedulers but could not quite understand where to call it from.
Further, the result of running the above actually delivers the message to the client, but also throws an error:
error
io.reactivex.exceptions.MissingBackpressureException: create: could not emit value due to lack of requests
at io.reactivex.internal.operators.flowable.FlowableCreate$ErrorAsyncEmitter.onOverflow(FlowableCreate.java:438)
at io.reactivex.internal.operators.flowable.FlowableCreate$NoOverflowBaseAsyncEmitter.onNext(FlowableCreate.java:406)
at io.micronaut.http.netty.websocket.NettyRxWebSocketSession.lambda$null$2(NettyRxWebSocketSession.java:191)
at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:577)
What is the backpressure and values / requests this referring to, and what is the actual way I should process an async send of the message? I expect it to just send the single item I am trying to send..
The Micronaut API documentation mentions following the javax.websocket API closely but
the javax.websocket async API seems to make more sense in just providing a Future to listen to.
So the short question is, how to use the Micronaut Websocket API to send message with the async mode outside the Micronaut provided functions? Or am I doing it all wrong?
It seems like I might be making some general wrong assumption about this API, but cannot figure it out from docs and cannot find example.
I had the same problem with micronaut v1.3.2.
I managed to get it working with sendAsync/sendSync instead of send.
I had a look at the implementation of NettyRxWebSocketSession and it looks like send is implemented somewhat differently than sendAsync. Not clear if its due to configuration or just problem with the implementation of send.
I hope this helps.
I have two Java processes and I am connecting them using a websocket in spring boot. One process acts as the client and connects like this:
List<Transport> transports = new ArrayList<Transport>(1);
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
WebSocketClient client = new SockJsClient(transports);
WebSocketStompClient stompClient = new WebSocketStompClient(client);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
StompSessionHandler firstSessionHandler = new MyStompSessionHandler("Philip");
stompClient.connect("ws://localhost:8080/chat", firstSessionHandler);
The session handler extends StompSessionHandlerAdapter and provides these methods (I am subscribing by username so each client can receive its own messages):
#Override
public void afterConnected(
StompSession session, StompHeaders connectedHeaders) {
session.subscribe("/user/" + userName + "/reply", this);
session.send("/app/chat", getSampleMessage());
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
Message msg = (Message) payload;
// etc.....
}
On the server side I have a Controller exposed and I am writing data by calling the endpoint from a worker thread.
#Autowired
private SimpMessagingTemplate template;
#MessageMapping("/chat")
public void send(
Message message)
throws Exception {
template.convertAndSendToUser(message.getFrom(),
"/reply",
message);
}
In the websocket config I am overriding the method to set the limits:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/user");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.setMessageSizeLimit(500 * 1024);
registration.setSendBufferSizeLimit(1024 * 1024);
registration.setSendTimeLimit(20000);
}
My question is this, if the load on the server gets high enough and I overrun the limit, the websocket fails catastrophically, and I want to avoid this. What I would like to do is for the controller to have the ability to ask the message broker "will this message fit in the buffer?", so that I can throttle to stay under the limit. I searched the API documentation but I don't see any way of doing that. Are there any other obvious solutions that I am missing?
Thanks.
Actually I found a solution, so if anyone is interested, here it is.
On the server side configuration of the websockets I installed an Interceptor on the Outbound Channel (this is part of the API), which is called after each send from the embedded broker.
So I know how much is coming in, which I keep track of in my Controller class and I know how much is going out through the Interceptor that I installed, and this allows me to always stay under the limit.
The controller, before accepting any new messages to be queued up for the broker first determines if enough room is available and if not queues up the message in external storage until such time as room becomes available.
I have been using Netty for a while but mainly for using normal sockets when their channels always unique, thus I can map channels to know who are connecting to my server.
Now I have managed to implement http communication. The problem is that values of ChannelHandlerContext handlers (and either channels from those handlers) are not unique, I cannot detect who are connecting just by their handlers.
Questions:
Is that behaviour (ChannelHandlerContext handler values not
unique) normal or do I have some bugs in code?
Any idea, solution?
Many thanks
My ChannelInitializer looks like the following:
public class NettyHttpServerInitializer extends ChannelInitializer<SocketChannel> {
#Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("http", new HttpServerCodec()));
pipeline.addLast("dechunker", new HttpObjectAggregator(65536));
pipeline.addLast("handler", new HttpServerHandler());
}
}
My server handler looks like (values of ctx and ctx.channel() are not unique even trigged from same client):
public class HttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
#Override
protected void messageReceived(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
...
}
}
When making a http protocol, a connection can be reused, that means that 1 connection can handle multiple requests. You should not the priciple of a connection is a person for your game, but you should use cookies or some sort of access token in your protocol.
Under normal circumstances, browsers will keep a maximum of 2 connections to the same ip.
I am learning JMS and came across this statement: http://docs.oracle.com/javaee/1.3/jms/tutorial/1_3_1-fcs/doc/advanced.html#1023387
The PERSISTENT delivery mode, the default, instructs the JMS provider
to take extra care to ensure that a message is not lost in transit in
case of a JMS provider failure. A message sent with this delivery mode
is logged to stable storage when it is sent.
If JMS Provider failure occurs then how the JMS Provider can ensure that a message is not lost?
What does it mean that:
"A message sent with this delivery mode is logged to stable storage when it is sent."
Please help me in understanding the JMS concept here.
It means the message with PERSISTENT delivery mode is not lost when a messaging provider goes down for any reason and comes up again. The messaging provider saves messages with PERSISTENT delivery mode to disk and when the message provides restarts, the message is read from the disk and brought into memory.
Hope this is clear.
You can do a simple test to understand the concept. Refer the tutorial here on how to create producer and consumer.
You will see producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
Change it to producer.setDeliveryMode(DeliveryMode.PERSISTENT);
Now create two classes. One which calls only Producers and one only consumer.
public class AppOnlyProduce {
public static void thread(Runnable runnable, boolean daemon) {
Thread brokerThread = new Thread(runnable);
brokerThread.setDaemon(daemon);
brokerThread.start();
}
public static void main(String[] args) throws InterruptedException {
thread(new HelloWorldProducer(), false);
thread(new HelloWorldProducer(), false);
}
}
public class AppOnlyConsumer {
public static void thread(Runnable runnable, boolean daemon) {
Thread brokerThread = new Thread(runnable);
brokerThread.setDaemon(daemon);
brokerThread.start();
}
public static void main(String[] args) throws InterruptedException {
thread(new HelloWorldConsumer(), false);
thread(new HelloWorldConsumer(), false);
}
}
First run AppOnlyProduce. It will create two messages. Now run AppOnlyConsumer it will read two messages.
Now change back the line to producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
Again run AppOnlyProduce. It will create two messages. Now run AppOnlyConsumer You will see that it waits for sometime for message and they say Received: null
In first case mode was persistent. So though Java program ended messages were persisted and made available when JMS started (this time by consumer)
In second case mode was not persistent. So messages vanished as soon as program ended.