forked from coaidev/coai
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscanner.go
112 lines (92 loc) · 2.19 KB
/
scanner.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package utils
import (
"bufio"
"bytes"
"chat/globals"
"fmt"
"io"
"net/http"
"runtime/debug"
"strings"
)
type EventScannerProps struct {
Method string
Uri string
Headers map[string]string
Body interface{}
Callback func(string) error
}
type EventScannerError struct {
Error error
Body string
}
func getErrorBody(resp *http.Response) string {
if resp == nil {
return ""
}
if content, err := io.ReadAll(resp.Body); err == nil {
return string(content)
}
return ""
}
func EventScanner(props *EventScannerProps) *EventScannerError {
// panic recovery
defer func() {
if r := recover(); r != nil {
stack := debug.Stack()
globals.Warn(fmt.Sprintf("event source panic: %s (uri: %s, method: %s)\n%s", r, props.Uri, props.Method, stack))
}
}()
client := newClient()
req, err := http.NewRequest(props.Method, props.Uri, ConvertBody(props.Body))
if err != nil {
return &EventScannerError{Error: err}
}
fillHeaders(req, props.Headers)
resp, err := client.Do(req)
if err != nil {
return &EventScannerError{Error: err}
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
// for error response
return &EventScannerError{
Error: fmt.Errorf("request failed with status code: %d", resp.StatusCode),
Body: getErrorBody(resp),
}
}
scanner := bufio.NewScanner(resp.Body)
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
// when EOF and empty data
return 0, nil, nil
}
if idx := bytes.Index(data, []byte("\n")); idx >= 0 {
// when found new line
return idx + 1, data[:idx], nil
}
if atEOF {
// when EOF and no new line
return len(data), data, nil
}
// when need more data
return 0, nil, nil
})
for scanner.Scan() {
raw := scanner.Text()
if len(raw) <= 5 || !strings.HasPrefix(raw, "data:") {
// for only `data:` partial raw or unexpected chunk
continue
}
chunk := strings.TrimSpace(strings.TrimPrefix(raw, "data:"))
if chunk == "[DONE]" || strings.HasPrefix(chunk, "[DONE]") {
// for done signal
continue
}
// callback chunk
if err := props.Callback(chunk); err != nil {
return &EventScannerError{Error: err}
}
}
return nil
}