gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

sse-decoder.go (3738B)


      1 // Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
      2 // Use of this source code is governed by a MIT style
      3 // license that can be found in the LICENSE file.
      4 
      5 package sse
      6 
      7 import (
      8 	"bytes"
      9 	"io"
     10 	"io/ioutil"
     11 )
     12 
     13 type decoder struct {
     14 	events []Event
     15 }
     16 
     17 func Decode(r io.Reader) ([]Event, error) {
     18 	var dec decoder
     19 	return dec.decode(r)
     20 }
     21 
     22 func (d *decoder) dispatchEvent(event Event, data string) {
     23 	dataLength := len(data)
     24 	if dataLength > 0 {
     25 		//If the data buffer's last character is a U+000A LINE FEED (LF) character, then remove the last character from the data buffer.
     26 		data = data[:dataLength-1]
     27 		dataLength--
     28 	}
     29 	if dataLength == 0 && event.Event == "" {
     30 		return
     31 	}
     32 	if event.Event == "" {
     33 		event.Event = "message"
     34 	}
     35 	event.Data = data
     36 	d.events = append(d.events, event)
     37 }
     38 
     39 func (d *decoder) decode(r io.Reader) ([]Event, error) {
     40 	buf, err := ioutil.ReadAll(r)
     41 	if err != nil {
     42 		return nil, err
     43 	}
     44 
     45 	var currentEvent Event
     46 	var dataBuffer *bytes.Buffer = new(bytes.Buffer)
     47 	// TODO (and unit tests)
     48 	// Lines must be separated by either a U+000D CARRIAGE RETURN U+000A LINE FEED (CRLF) character pair,
     49 	// a single U+000A LINE FEED (LF) character,
     50 	// or a single U+000D CARRIAGE RETURN (CR) character.
     51 	lines := bytes.Split(buf, []byte{'\n'})
     52 	for _, line := range lines {
     53 		if len(line) == 0 {
     54 			// If the line is empty (a blank line). Dispatch the event.
     55 			d.dispatchEvent(currentEvent, dataBuffer.String())
     56 
     57 			// reset current event and data buffer
     58 			currentEvent = Event{}
     59 			dataBuffer.Reset()
     60 			continue
     61 		}
     62 		if line[0] == byte(':') {
     63 			// If the line starts with a U+003A COLON character (:), ignore the line.
     64 			continue
     65 		}
     66 
     67 		var field, value []byte
     68 		colonIndex := bytes.IndexRune(line, ':')
     69 		if colonIndex != -1 {
     70 			// If the line contains a U+003A COLON character character (:)
     71 			// Collect the characters on the line before the first U+003A COLON character (:),
     72 			// and let field be that string.
     73 			field = line[:colonIndex]
     74 			// Collect the characters on the line after the first U+003A COLON character (:),
     75 			// and let value be that string.
     76 			value = line[colonIndex+1:]
     77 			// If value starts with a single U+0020 SPACE character, remove it from value.
     78 			if len(value) > 0 && value[0] == ' ' {
     79 				value = value[1:]
     80 			}
     81 		} else {
     82 			// Otherwise, the string is not empty but does not contain a U+003A COLON character character (:)
     83 			// Use the whole line as the field name, and the empty string as the field value.
     84 			field = line
     85 			value = []byte{}
     86 		}
     87 		// The steps to process the field given a field name and a field value depend on the field name,
     88 		// as given in the following list. Field names must be compared literally,
     89 		// with no case folding performed.
     90 		switch string(field) {
     91 		case "event":
     92 			// Set the event name buffer to field value.
     93 			currentEvent.Event = string(value)
     94 		case "id":
     95 			// Set the event stream's last event ID to the field value.
     96 			currentEvent.Id = string(value)
     97 		case "retry":
     98 			// If the field value consists of only characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9),
     99 			// then interpret the field value as an integer in base ten, and set the event stream's reconnection time to that integer.
    100 			// Otherwise, ignore the field.
    101 			currentEvent.Id = string(value)
    102 		case "data":
    103 			// Append the field value to the data buffer,
    104 			dataBuffer.Write(value)
    105 			// then append a single U+000A LINE FEED (LF) character to the data buffer.
    106 			dataBuffer.WriteString("\n")
    107 		default:
    108 			//Otherwise. The field is ignored.
    109 			continue
    110 		}
    111 	}
    112 	// Once the end of the file is reached, the user agent must dispatch the event one final time.
    113 	d.dispatchEvent(currentEvent, dataBuffer.String())
    114 
    115 	return d.events, nil
    116 }