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 }