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

net.oneandone.reactive.sse.ServerSentEventParser 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;


import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.List;

import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;



/**
 * ServerSentEvent parser
 */
public class ServerSentEventParser {
    private final List lines = Lists.newArrayList(); 
    private final LineParser lineParser = new LineParser();
    
    private String id = null;
    private String event = null;
    private String data = "";
    private Integer retry = null;
    
    private String field = ""; 
    private String value = "";
    
        
 
    /**
     * parse the events 
     * 
     * @param buf      the buffer
     * @return the parsed events  
     */
    public ImmutableList parse(ByteBuffer buf) {
        List events = Lists.newArrayList();
        
        lineParser.parse(buf, lines);
        
        if (!lines.isEmpty()) {
            for (String line : lines) {
                String trimmedLine = line.trim();
                
                // If the line is empty (a blank line)
                if (trimmedLine.length() == 0) {
                    
                    // If the data buffer's last character is a U+000A LINE FEED (LF) character,
                    // then remove the last character from the data buffer.
                    if (data.endsWith("\n")) {
                        data = data.substring(0, data.length() - 1);
                    }

                    // If the data buffer is an empty string, set the data buffer and the event type 
                    // buffer to the empty string and abort these steps.

                    if ((data.length() > 0 ) || (id != null) || (event != null)) {
                        events.add(ServerSentEvent.newEvent()
                                           .id(id)
                                           .event(event)
                                           .data(data)
                                           .retry(retry));
                    }
                    
                    resetEventParsingData();
                    
                } else {

                    // line starts with a U+003A COLON character (:)
                    // Ignore the line

                    if (!trimmedLine.startsWith(":")) {
                        int idx = line.indexOf(":");
                        
                        // is not empty but does not contain a U+003A COLON character (:)
                        // using the whole line as the field name, and the empty string as the field value.
                        if (idx == -1) {
                            field = line;
                            value = "";
                            
                        } else {
                            // Collect the characters on the line before the first U+003A COLON character (:),
                            // and let field be that string.
                            field += line.substring(0, idx);
                            value = line.substring(idx + 1 , line.length());
                            
                            //  If value starts with a U+0020 SPACE character, remove it from value.
                            value = CharMatcher.is('\u0020').trimLeadingFrom(value);
                            
                            
                            // if the field name is "id" -> Set the last event ID buffer to the field value.
                            String trimmedField = field.trim();
                            if (trimmedField.equalsIgnoreCase("id")) {
                                id = value;
                                        
                            // If the field name is "event" -> Set the event type buffer to field value.
                            } else if (trimmedField.equalsIgnoreCase("event")) { 
                                event = value;
                                
                            // If the field name is "data"-> Append the field value to the data buffer, then
                            // append a single U+000A LINE FEED (LF) character to the data buffer.
                            } else if (trimmedField.equalsIgnoreCase("data")) {
                                data += value + '\n';
                            
                            // If the field value consists of only ASCII digits, then interpret the field 
                            // value as an integer in base ten, and set the event stream's reconnection time to that integer.
                            // Otherwise, ignore the field.
                            } else if (trimmedField.equalsIgnoreCase("retry")) {
                                try {
                                    retry = Integer.getInteger(value);
                                } catch (NumberFormatException ignore) { }
                            }
                            
                            resetFieldValueParsingData();
                        }
                    }
                }
            }
            
            lines.clear();
        }
        
        return ImmutableList.copyOf(events);
    }
    

    private void resetEventParsingData() {
        id = null;
        event = null;
        data = "";
        
        resetFieldValueParsingData();
    }
    
    private void resetFieldValueParsingData() {
        field = "";
        value = "";
    }

    
    
    
    /**
     * @author grro
     */
    private static final class LineParser {
        
        private static final int BUFFER_SIZE = 100; 
        private static final byte CR = 0x0D;
        private static final byte LF = 0x0A;
        

        private byte[] lineBuffer = new byte[BUFFER_SIZE];
        private int pos = 0;
        private boolean isIgnoreLF = false;

        
        
        void parse(ByteBuffer buf, List lines) {
            
            try {
                while (buf.hasRemaining()) {
                    
                    byte i = buf.get();
                    
                    
                    if (i == CR) {    // end-of-line   = ( cr lf / cr / lf )
                        isIgnoreLF = true;
    
                        // new line
                        lines.add(new String(lineBuffer, 0, pos, "UTF-8"));
                        pos = 0;
                        
                    } else if (i == LF) {
                        
                        if (!isIgnoreLF) {
                            // new line
                            lines.add(new String(lineBuffer, 0, pos, "UTF-8"));
                            pos = 0;
                        }
                        
                        isIgnoreLF = false;
                        
                    } else {
                        isIgnoreLF = false;
                        
                        lineBuffer[pos] = i;
                        pos++;
                        
                        incLineBufferIfNecessary();
                    }
                }
            } catch (UnsupportedEncodingException use) {
                throw new RuntimeException(use);
            }
        }
        
        
        private void incLineBufferIfNecessary() {
            if (pos == lineBuffer.length) {
                byte[] newLineBuffer = new byte[lineBuffer.length + BUFFER_SIZE];
                System.arraycopy(lineBuffer, 0, newLineBuffer, 0, lineBuffer.length);
                lineBuffer = newLineBuffer;
            }
        }
    }   
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy