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

net.oneandone.reactive.sse.servlet.SseReadableChannel Maven / Gradle / Ivy

There is a newer version: 0.10
Show newest version
/*
 * Copyright 1&1 Internet AG, https://github.com/1and1/
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.oneandone.reactive.sse.servlet;



import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;

import net.oneandone.reactive.sse.ServerSentEvent;
import net.oneandone.reactive.sse.ServerSentEventParser;

import com.google.common.collect.Lists;
import com.google.common.io.Closeables;



 
class SseReadableChannel {
    private final Object pendingConsumesLock = new Object();
    private final AtomicInteger numPendingConsumes = new AtomicInteger(0);

    private final SSEInputStream serverSentEventsStream;
    
    private final Consumer eventConsumer;
    private final Consumer errorConsumer;
    private final Consumer completionConsumer;
    

    
    public SseReadableChannel(ServletInputStream in, Consumer eventConsumer, Consumer errorConsumer, Consumer completionConsumer) {
        this.eventConsumer = eventConsumer;
        this.errorConsumer = errorConsumer;
        this.completionConsumer = completionConsumer;
        this.serverSentEventsStream = new SSEInputStream(in);
        
        in.setReadListener(new ServletReadListener());
    }

    
    private final class ServletReadListener implements ReadListener {
        
        @Override
        public void onAllDataRead() throws IOException {
            completionConsumer.accept(null);
        }
        
        @Override
        public void onError(Throwable t) {
            SseReadableChannel.this.onError(t);
        }
        
        @Override
        public void onDataAvailable() throws IOException {
            proccessPendingReads();
        }
    }

    
    
    /**
     * reads the 
     * @param consumer
     */
    public void consumeNextEvent() {
        
        synchronized (pendingConsumesLock) {   
            try {
                // reading has to be processed inside the sync block to avoid shuffling events 
                Optional optionalEvent = serverSentEventsStream.next();
                
                // got an event?
                if (optionalEvent.isPresent()) {
                    eventConsumer.accept(optionalEvent.get());
                 
                // no, queue the pending read request    
                // will be handled by performing the read listener's onDataAvailable callback     
                } else {
                    numPendingConsumes.incrementAndGet();
                }
                
            } catch (IOException | RuntimeException t) {
                onError(t);
            }
        }
    }
    
    
    private void proccessPendingReads() {
        
        synchronized (pendingConsumesLock) {
            try {
                while(numPendingConsumes.get() > 0) {
                    Optional optionalEvent = serverSentEventsStream.next();
                    if (optionalEvent.isPresent()) {
                        numPendingConsumes.decrementAndGet();
                        eventConsumer.accept(optionalEvent.get());
                    } else {
                        return;
                    }
                }
            } catch (IOException | RuntimeException t) {
                onError(t);
            }
        }
    }
    
    
    private void onError(Throwable t)  {
        errorConsumer.accept(t);
        close();
    }
    
    
    public void close() {
        serverSentEventsStream.close();
    }
    

    /**
     * SSEInputStream
     * 
     * @author grro
     */
    private static class SSEInputStream implements Closeable {
        
        // buffer
        private final Queue bufferedEvents = Lists.newLinkedList();
    
        // sse parser
        private final ServerSentEventParser parser = new ServerSentEventParser();
        private final byte buf[] = new byte[1024];
        private int len = -1;
        
        private final ServletInputStream is;
    
        
        public SSEInputStream(ServletInputStream is) {
            this.is = is;
        }
       
        
        public void close() {
            Closeables.closeQuietly(is);
        }
    
        
        public Optional next() throws IOException {
            
            // no events buffered        
            if (bufferedEvents.isEmpty()) {
                
                // read network
                while (bufferedEvents.isEmpty() && isNetworkdataAvailable() && (len = is.read(buf)) > 0) {            
                    parser.parse(ByteBuffer.wrap(buf, 0, len)).forEach(event -> bufferedEvents.add(event)); 
                }    
            }
              
            return Optional.ofNullable(bufferedEvents.poll());
        }
        
        private boolean isNetworkdataAvailable() {
            try {
                return is.isReady();
            } catch (IllegalStateException ise)  {
                return false;
            }
        }
    }
}    




© 2015 - 2024 Weber Informatics LLC | Privacy Policy