github.com/simpleiot/simpleiot@v0.18.3/respreader/response-reader.go (about) 1 package respreader 2 3 import ( 4 "errors" 5 "io" 6 "time" 7 ) 8 9 // ReadWriteCloser is a convenience type that implements io.ReadWriteCloser. 10 // Write calls flush reader before writing the prompt. 11 type ReadWriteCloser struct { 12 closer io.Closer 13 writer io.Writer 14 reader *Reader 15 } 16 17 // NewReadWriteCloser creates a new response reader 18 // 19 // timeout is used to specify an 20 // overall timeout. If this timeout is encountered, io.EOF is returned. 21 // 22 // chunkTimeout is used to specify the max timeout between chunks of data once 23 // the response is started. If a delay of chunkTimeout is encountered, the response 24 // is considered finished and the Read returns. 25 func NewReadWriteCloser(iorw io.ReadWriteCloser, timeout time.Duration, chunkTimeout time.Duration) *ReadWriteCloser { 26 return &ReadWriteCloser{ 27 closer: iorw, 28 writer: iorw, 29 reader: NewReader(iorw, timeout, chunkTimeout), 30 } 31 } 32 33 // Read response using chunkTimeout and timeout 34 func (rwc *ReadWriteCloser) Read(buffer []byte) (int, error) { 35 return rwc.reader.Read(buffer) 36 } 37 38 // Write flushes all data from reader, and then passes through write call. 39 func (rwc *ReadWriteCloser) Write(buffer []byte) (int, error) { 40 n, err := rwc.reader.Flush() 41 if err != nil { 42 return n, err 43 } 44 45 return rwc.writer.Write(buffer) 46 } 47 48 // SetTimeout can be used to update the reader timeout 49 func (rwc *ReadWriteCloser) SetTimeout(timeout, chunkTimeout time.Duration) { 50 rwc.reader.SetTimeout(timeout, chunkTimeout) 51 } 52 53 // Close is a passthrough call. 54 func (rwc *ReadWriteCloser) Close() error { 55 close(rwc.reader.stopChan) 56 return rwc.closer.Close() 57 } 58 59 // ReadCloser is a convenience type that implements io.ReadWriter. Write 60 // calls flush reader before writing the prompt. 61 type ReadCloser struct { 62 closer io.Closer 63 reader *Reader 64 } 65 66 // NewReadCloser creates a new response reader 67 // 68 // timeout is used to specify an 69 // overall timeout. If this timeout is encountered, io.EOF is returned. 70 // 71 // chunkTimeout is used to specify the max timeout between chunks of data once 72 // the response is started. If a delay of chunkTimeout is encountered, the response 73 // is considered finished and the Read returns. 74 func NewReadCloser(iorw io.ReadCloser, timeout time.Duration, chunkTimeout time.Duration) *ReadCloser { 75 return &ReadCloser{ 76 closer: iorw, 77 reader: NewReader(iorw, timeout, chunkTimeout), 78 } 79 } 80 81 // Read response using chunkTimeout and timeout 82 func (rc *ReadCloser) Read(buffer []byte) (int, error) { 83 return rc.reader.Read(buffer) 84 } 85 86 // Close is a passthrough call. 87 func (rc *ReadCloser) Close() error { 88 close(rc.reader.stopChan) 89 return rc.closer.Close() 90 } 91 92 // SetTimeout can be used to update the reader timeout 93 func (rc *ReadCloser) SetTimeout(timeout, chunkTimeout time.Duration) { 94 rc.reader.SetTimeout(timeout, chunkTimeout) 95 } 96 97 // ReadWriter is a convenience type that implements io.ReadWriter. Write 98 // calls flush reader before writing the prompt. 99 type ReadWriter struct { 100 writer io.Writer 101 reader *Reader 102 } 103 104 // NewReadWriter creates a new response reader 105 func NewReadWriter(iorw io.ReadWriter, timeout time.Duration, chunkTimeout time.Duration) *ReadWriter { 106 return &ReadWriter{ 107 writer: iorw, 108 reader: NewReader(iorw, timeout, chunkTimeout), 109 } 110 } 111 112 // Read response 113 func (rw *ReadWriter) Read(buffer []byte) (int, error) { 114 return rw.reader.Read(buffer) 115 } 116 117 // Write flushes all data from reader, and then passes through write call. 118 func (rw *ReadWriter) Write(buffer []byte) (int, error) { 119 n, err := rw.reader.Flush() 120 if err != nil { 121 return n, err 122 } 123 124 return rw.writer.Write(buffer) 125 } 126 127 // SetTimeout can be used to update the reader timeout 128 func (rw *ReadWriter) SetTimeout(timeout, chunkTimeout time.Duration) { 129 rw.reader.SetTimeout(timeout, chunkTimeout) 130 } 131 132 // Reader is used for prompt/response communication protocols where a prompt 133 // is sent, and some time later a response is received. Typically, the target takes 134 // some amount to formulate the response, and then streams it out. There are two delays: 135 // an overall timeout, and then an inter character timeout that is activated once the 136 // first byte is received. The thought is that once you received the 1st byte, all the 137 // data should stream out continuously and a short timeout can be used to determine the 138 // end of the packet. 139 type Reader struct { 140 reader io.Reader 141 timeout time.Duration 142 chunkTimeout time.Duration 143 size int 144 stopChan chan bool 145 dataChan <-chan []byte 146 } 147 148 // NewReader creates a new response reader. 149 // 150 // timeout is used to specify an 151 // overall timeout. If this timeout is encountered, io.EOF is returned. 152 // 153 // chunkTimeout is used to specify the max timeout between chunks of data once 154 // the response is started. If a delay of chunkTimeout is encountered, the response 155 // is considered finished and the Read returns. 156 func NewReader(reader io.Reader, timeout time.Duration, chunkTimeout time.Duration) *Reader { 157 r := Reader{ 158 reader: reader, 159 timeout: timeout, 160 chunkTimeout: chunkTimeout, 161 size: 128, 162 stopChan: make(chan bool), 163 } 164 // we have to start a reader goroutine here that lives for the life 165 // of the reader because there is no 166 // way to stop a blocked goroutine 167 r.dataChan = readInput(r.stopChan, r.reader, r.size) 168 return &r 169 } 170 171 // Read response 172 func (r *Reader) Read(buffer []byte) (int, error) { 173 if len(buffer) <= 0 { 174 return 0, errors.New("must supply non-zero length buffer") 175 } 176 177 timeout := time.NewTimer(r.timeout) 178 count := 0 179 180 for { 181 select { 182 case newData, ok := <-r.dataChan: 183 // copy data from chan buffer to Read() buf 184 for i := 0; count < len(buffer) && i < len(newData); i++ { 185 buffer[count] = newData[i] 186 count++ 187 } 188 189 if !ok { 190 return count, io.EOF 191 } 192 193 timeout.Reset(r.chunkTimeout) 194 195 case <-timeout.C: 196 if count > 0 { 197 return count, nil 198 } 199 200 return count, io.EOF 201 202 } 203 } 204 } 205 206 // Flush is used to flush any input data 207 func (r *Reader) Flush() (int, error) { 208 timeout := time.NewTimer(r.chunkTimeout) 209 count := 0 210 211 for { 212 select { 213 case newData, ok := <-r.dataChan: 214 count += len(newData) 215 if !ok { 216 return count, io.EOF 217 } 218 219 timeout.Reset(r.chunkTimeout) 220 221 case <-timeout.C: 222 return count, nil 223 } 224 } 225 } 226 227 // SetTimeout can be used to update the reader timeout 228 func (r *Reader) SetTimeout(timeout, chunkTimeout time.Duration) { 229 r.timeout = timeout 230 r.chunkTimeout = chunkTimeout 231 } 232 233 // readInput is started as a goroutine to read data from the underlying io.Reader 234 func readInput(done <-chan bool, port io.Reader, size int) <-chan []byte { 235 retData := make(chan []byte) 236 go func() { 237 defer close(retData) 238 for { 239 tmp := make([]byte, size) 240 select { 241 case <-done: 242 return 243 default: 244 } 245 246 length, _ := port.Read(tmp) 247 if length > 0 { 248 tmp = tmp[0:length] 249 retData <- tmp 250 } 251 } 252 }() 253 254 return retData 255 }