github.com/philippseith/signalr@v0.6.3/jsonhubprotocol.go (about) 1 package signalr 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "reflect" 10 11 "github.com/go-kit/log" 12 ) 13 14 // jsonHubProtocol is the JSON based SignalR protocol 15 type jsonHubProtocol struct { 16 dbg log.Logger 17 } 18 19 // Protocol specific messages for correct unmarshaling of arguments or results. 20 // jsonInvocationMessage is only used in ParseMessages, not in WriteMessage 21 type jsonInvocationMessage struct { 22 Type int `json:"type"` 23 Target string `json:"target"` 24 InvocationID string `json:"invocationId"` 25 Arguments []json.RawMessage `json:"arguments"` 26 StreamIds []string `json:"streamIds,omitempty"` 27 } 28 29 type jsonStreamItemMessage struct { 30 Type int `json:"type"` 31 InvocationID string `json:"invocationId"` 32 Item json.RawMessage `json:"item"` 33 } 34 35 type jsonCompletionMessage struct { 36 Type int `json:"type"` 37 InvocationID string `json:"invocationId"` 38 Result json.RawMessage `json:"result,omitempty"` 39 Error string `json:"error,omitempty"` 40 } 41 42 type jsonError struct { 43 raw string 44 err error 45 } 46 47 func (j *jsonError) Error() string { 48 return fmt.Sprintf("%v (source: %v)", j.err, j.raw) 49 } 50 51 // UnmarshalArgument unmarshals a json.RawMessage depending on the specified value type into value 52 func (j *jsonHubProtocol) UnmarshalArgument(src interface{}, dst interface{}) error { 53 rawSrc, ok := src.(json.RawMessage) 54 if !ok { 55 return fmt.Errorf("invalid source %#v for UnmarshalArgument", src) 56 } 57 if err := json.Unmarshal(rawSrc, dst); err != nil { 58 return &jsonError{string(rawSrc), err} 59 } 60 _ = j.dbg.Log(evt, "UnmarshalArgument", 61 "argument", string(rawSrc), 62 "value", fmt.Sprintf("%v", reflect.ValueOf(dst).Elem())) 63 return nil 64 } 65 66 // ParseMessages reads all messages from the reader and puts the remaining bytes into remainBuf 67 func (j *jsonHubProtocol) ParseMessages(reader io.Reader, remainBuf *bytes.Buffer) (messages []interface{}, err error) { 68 frames, err := readJSONFrames(reader, remainBuf) 69 if err != nil { 70 return nil, err 71 } 72 message := hubMessage{} 73 messages = make([]interface{}, 0) 74 for _, frame := range frames { 75 err = json.Unmarshal(frame, &message) 76 _ = j.dbg.Log(evt, "read", msg, string(frame)) 77 if err != nil { 78 return nil, &jsonError{string(frame), err} 79 } 80 typedMessage, err := j.parseMessage(message.Type, frame) 81 if err != nil { 82 return nil, err 83 } 84 // No specific type (aka Ping), use hubMessage 85 if typedMessage == nil { 86 typedMessage = message 87 } 88 messages = append(messages, typedMessage) 89 } 90 return messages, nil 91 } 92 93 func (j *jsonHubProtocol) parseMessage(messageType int, text []byte) (message interface{}, err error) { 94 switch messageType { 95 case 1, 4: 96 jsonInvocation := jsonInvocationMessage{} 97 if err = json.Unmarshal(text, &jsonInvocation); err != nil { 98 err = &jsonError{string(text), err} 99 } 100 arguments := make([]interface{}, len(jsonInvocation.Arguments)) 101 for i, a := range jsonInvocation.Arguments { 102 arguments[i] = a 103 } 104 return invocationMessage{ 105 Type: jsonInvocation.Type, 106 Target: jsonInvocation.Target, 107 InvocationID: jsonInvocation.InvocationID, 108 Arguments: arguments, 109 StreamIds: jsonInvocation.StreamIds, 110 }, err 111 case 2: 112 jsonStreamItem := jsonStreamItemMessage{} 113 if err = json.Unmarshal(text, &jsonStreamItem); err != nil { 114 err = &jsonError{string(text), err} 115 } 116 return streamItemMessage{ 117 Type: jsonStreamItem.Type, 118 InvocationID: jsonStreamItem.InvocationID, 119 Item: jsonStreamItem.Item, 120 }, err 121 case 3: 122 jsonCompletion := jsonCompletionMessage{} 123 if err = json.Unmarshal(text, &jsonCompletion); err != nil { 124 err = &jsonError{string(text), err} 125 } 126 completion := completionMessage{ 127 Type: jsonCompletion.Type, 128 InvocationID: jsonCompletion.InvocationID, 129 Error: jsonCompletion.Error, 130 } 131 // Only assign Result when non nil. setting interface{} Result to (json.RawMessage)(nil) 132 // will produce a value which can not compared to nil even if it is pointing towards nil! 133 // See https://www.calhoun.io/when-nil-isnt-equal-to-nil/ for explanation 134 if jsonCompletion.Result != nil { 135 completion.Result = jsonCompletion.Result 136 } 137 return completion, err 138 case 5: 139 invocation := cancelInvocationMessage{} 140 if err = json.Unmarshal(text, &invocation); err != nil { 141 err = &jsonError{string(text), err} 142 } 143 return invocation, err 144 case 7: 145 cm := closeMessage{} 146 if err = json.Unmarshal(text, &cm); err != nil { 147 err = &jsonError{string(text), err} 148 } 149 return cm, err 150 default: 151 return nil, nil 152 } 153 } 154 155 // readJSONFrames reads all complete frames (delimited by 0x1e) from the reader and puts the remaining bytes into remainBuf 156 func readJSONFrames(reader io.Reader, remainBuf *bytes.Buffer) ([][]byte, error) { 157 p := make([]byte, 1<<15) 158 buf := &bytes.Buffer{} 159 _, _ = buf.ReadFrom(remainBuf) 160 // Try getting data until at least one frame is available 161 for { 162 n, err := reader.Read(p) 163 // Some reader implementations return io.EOF additionally to n=0 if no data could be read 164 if err != nil && !errors.Is(err, io.EOF) { 165 return nil, err 166 } 167 if n > 0 { 168 _, _ = buf.Write(p[:n]) 169 frames, err := parseJSONFrames(buf) 170 if err != nil { 171 return nil, err 172 } 173 if len(frames) > 0 { 174 _, _ = remainBuf.ReadFrom(buf) 175 return frames, nil 176 } 177 } 178 } 179 } 180 181 func parseJSONFrames(buf *bytes.Buffer) ([][]byte, error) { 182 frames := make([][]byte, 0) 183 for { 184 frame, err := buf.ReadBytes(0x1e) 185 if errors.Is(err, io.EOF) { 186 // Restore incomplete frame in buffer 187 _, _ = buf.Write(frame) 188 break 189 } 190 if err != nil { 191 return nil, err 192 } 193 frames = append(frames, frame[:len(frame)-1]) 194 } 195 return frames, nil 196 } 197 198 // WriteMessage writes a message as JSON to the specified writer 199 func (j *jsonHubProtocol) WriteMessage(message interface{}, writer io.Writer) error { 200 var b []byte 201 var err error 202 if marshaler, ok := message.(json.Marshaler); ok { 203 b, err = marshaler.MarshalJSON() 204 } else { 205 b, err = json.Marshal(message) 206 } 207 if err != nil { 208 return err 209 } 210 b = append(b, 0x1e) 211 _ = j.dbg.Log(evt, "write", msg, string(b)) 212 _, err = writer.Write(b) 213 return err 214 } 215 216 func (j *jsonHubProtocol) transferMode() TransferMode { 217 return TextTransferMode 218 } 219 220 func (j *jsonHubProtocol) setDebugLogger(dbg StructuredLogger) { 221 j.dbg = log.WithPrefix(dbg, "ts", log.DefaultTimestampUTC, "protocol", "JSON") 222 }