-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgoquestor.go
150 lines (129 loc) · 3.77 KB
/
goquestor.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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package goquestor
import (
"fmt"
http "github.com/bogdanfinn/fhttp"
tlsclient "github.com/bogdanfinn/tls-client"
"io"
"strings"
"sync"
)
// NewGoquestor Create initializes a Goquestor with a specified concurrency limit.
func NewGoquestor(concurrency int) *Goquestor {
return &Goquestor{
clients: make([]*clientData, 0),
responses: make([]*Response, 0),
concurrency: concurrency,
}
}
// Request adds a new request to the goquestor along with specific tlsclient options and an identifier/caller.
func (r *Goquestor) Request(method, url string, body io.Reader, headers http.Header, options []tlsclient.HttpClientOption, caller any) {
req, err := http.NewRequest(method, url, body)
if err != nil {
r.responses = append(r.responses, &Response{
Error: fmt.Sprintf("Failed to create HTTP request: %v", err),
Caller: &caller,
})
return
}
req.Header = headers
r.clients = append(r.clients, &clientData{
request: req,
options: &options,
caller: &caller,
})
}
// Execute processes all the requests stored in the goquestor up to the concurrency limit.
func (r *Goquestor) Execute() []*Response {
var wg sync.WaitGroup
responseChannel := make(chan *Response, len(r.clients))
semaphore := make(chan struct{}, r.concurrency) // Concurrency control semaphore
for _, client := range r.clients {
wg.Add(1)
semaphore <- struct{}{}
go func(c *clientData) {
defer wg.Done()
defer func() { <-semaphore }()
respData := doRequest(c.request, c.options, c.caller)
responseChannel <- respData
}(client)
}
go func() {
wg.Wait()
close(responseChannel)
}()
responses := make([]*Response, 0)
for response := range responseChannel {
responses = append(responses, response)
}
r.clients = make([]*clientData, 0)
r.responses = make([]*Response, 0)
return responses
}
// doRequest handles the HTTP request execution using a newly created tlsclient and returns the gathered response data.
func doRequest(req *http.Request, options *[]tlsclient.HttpClientOption, caller *any) *Response {
client, err := tlsclient.NewHttpClient(nil, *options...)
if err != nil {
return &Response{
Error: fmt.Sprintf("Failed to create HTTP client: %v", err),
Caller: caller,
}
}
resp, err := client.Do(req)
if err != nil {
return &Response{
Error: fmt.Sprintf("HTTP request failed: %v", err),
Caller: caller,
}
}
responseData := &Response{
Status: resp.StatusCode,
Headers: resp.Header,
Caller: caller,
}
body, err := io.ReadAll(resp.Body)
if err != nil {
responseData.Error = fmt.Sprintf("Failed to read response body: %v", err)
return responseData
}
responseData.Body = string(body)
responseData.Body = strings.TrimSpace(responseData.Body)
defer func() {
if err := resp.Body.Close(); err != nil {
errorMessage := fmt.Sprintf("Failed to close response body: %v", err)
if responseData.Error != "" {
responseData.Error += fmt.Sprintf(" | %s: %v", errorMessage, err)
} else {
responseData.Error = fmt.Sprintf("%s: %v", errorMessage, err)
}
}
}()
return responseData
}
// Result returns the value of responses pointer
func Result(responses []*Response) []Response {
array := make([]Response, len(responses))
for _, response := range responses {
array = append(array, *response)
}
return array
}
// Response struct holds all relevant data from a response for later use.
type Response struct {
Status int
Body string
Headers http.Header
Error string
Caller *any
}
// Goquestor struct manages a pool of clients and handles their asynchronous execution.
type Goquestor struct {
clients []*clientData
responses []*Response
concurrency int
}
// clientData struct defines the properties of a single request client.
type clientData struct {
request *http.Request
options *[]tlsclient.HttpClientOption
caller *any
}