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  }