/* Stream manufactures a `pkg/stream.Stream`, starts a worker pumping events out of decoding, and returns that. The 'outputCh' parameter must be a sendable channel. The "zero"-values of channel's content type will be created and used in the deserialization, then sent. The return values from `httpclient.RawReq` are probably a useful starting point for the 'res' parameter. Closing the returned `stream.Stream` shuts down the worker. */ func Stream(res *http.Response, outputCh interface{}) stream.Stream { stream := stream.New() var chanValue reflect.Value if v, ok := outputCh.(reflect.Value); ok { chanValue = v } else { chanValue = reflect.ValueOf(outputCh) } stopChanValue := reflect.ValueOf(stream.StopCh) msgType := chanValue.Type().Elem().Elem() go func() { done := make(chan struct{}) defer func() { chanValue.Close() close(done) }() go func() { select { case <-stream.StopCh: case <-done: } res.Body.Close() }() r := bufio.NewReader(res.Body) dec := sse.NewDecoder(r) for { msg := reflect.New(msgType) if err := dec.Decode(msg.Interface()); err != nil { if err != io.EOF { stream.Error = err } break } chosen, _, _ := reflect.Select([]reflect.SelectCase{ { Dir: reflect.SelectRecv, Chan: stopChanValue, }, { Dir: reflect.SelectSend, Chan: chanValue, Send: msg, }, }) switch chosen { case 0: return default: } } }() return stream }
// StreamJobEvents returns a JobEventStream for an app. func (c *Client) StreamJobEvents(appID string, lastID int64) (*JobEventStream, error) { header := http.Header{ "Accept": []string{"text/event-stream"}, "Last-Event-Id": []string{strconv.FormatInt(lastID, 10)}, } res, err := c.RawReq("GET", fmt.Sprintf("/apps/%s/jobs", appID), header, nil, nil) if err != nil { return nil, err } stream := &JobEventStream{Events: make(chan *ct.JobEvent), body: res.Body} go func() { defer close(stream.Events) dec := sse.NewDecoder(bufio.NewReader(stream.body)) for { event := &ct.JobEvent{} if err := dec.Decode(event); err != nil { return } stream.Events <- event } }() return stream, nil }