-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
250 lines (198 loc) · 6.72 KB
/
main.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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
/*
AUTHOR: MELVIN GEORGE
ASSIGNMENT: REQCLIER
*/
package main
/*
------------ CLI USAGES -----------
===================================
--url <valid_url>
--profile <no_of_requests_to_the_URL>
--help to get all the usages in the app
*/
import (
"bufio"
"bytes"
"crypto/tls"
"flag"
"fmt"
"io"
"log"
"net/url"
"sort"
"time"
)
// Structure for connection information
type connectionInfo struct {
conn *tls.Conn
elapsedTime int64
}
// Structure for Single Request Profile
type requestsProfile struct {
statusCode string
details *connectionInfo
}
func main() {
// set flags for CLI
host := flag.String("url", "https://linktree.melvingeorge10.workers.dev/links", "Provide the full URL") // --url
profile := flag.Int("profile", 0, "Provide the number of requests to make to the defined URL") // --profile
flag.Parse()
// Print details about request to console
fmt.Printf("\nInformation\n")
fmt.Printf("-----------\n\n")
fmt.Printf("Using URL: %s\n", *host)
if *profile != 0 {
fmt.Printf("Requests Count: %d\n", *profile)
} else {
fmt.Printf("Requests Count: %d\n", (*profile)+1)
}
// Requests Profile Array
// for storing details about
// all the requests
var RequestsProfileArray []requestsProfile
// if the profile is greater than 0
// then we will measure
// the mean, median, slow, fast time of the URL
if *profile > 0 {
fmt.Printf("\nSending %d requests...🔥\n", *profile)
for i := 0; i < *profile; i++ {
details := makeRequest(host)
profile := composeConnectionDetails(details)
RequestsProfileArray = append(RequestsProfileArray, profile)
}
fmt.Printf("\nAnalysis Complete...⚡️\n")
profileAndWriteToConsole(RequestsProfileArray)
return
}
// else if the profile is 0
// we will output the raw tcp responce
details := makeRequest(host)
writeResponseToConsole(details.conn)
}
// function to profile all the requests
// and write measurements to console
func profileAndWriteToConsole(requestsProfileArray []requestsProfile) {
requestsProfileArrayLength := len(requestsProfileArray)
if requestsProfileArrayLength > 0 {
var slowestTime, fastestTime requestsProfile
var sum, median float64
var noOfSuccessRequests, noOfFailureRequests int
var errorStatusCodeArray []string
// Sort the Single Profile Array
// Because, Need to calculate the median
// and also the fastest and smallest time for request
sort.Slice(requestsProfileArray, func(i, j int) bool {
return requestsProfileArray[i].details.elapsedTime < requestsProfileArray[j].details.elapsedTime
})
// get the fastest and slowest time
// easy since it is sorted already
fastestTime = requestsProfileArray[0]
slowestTime = requestsProfileArray[(requestsProfileArrayLength - 1)]
// loop through all the requests
for i := 0; i < requestsProfileArrayLength; i++ {
// and check if the request is success or a failure
// just a simple check
// for more agreesive check we can use a switch statement
// with all the 2xx Status codes
if requestsProfileArray[i].statusCode == "200" {
noOfSuccessRequests++
} else {
noOfFailureRequests++
errorStatusCodeArray = append(errorStatusCodeArray, requestsProfileArray[i].statusCode)
}
// calculate total sum for mean time
sum = sum + float64(requestsProfileArray[i].details.elapsedTime)
}
// calculate median time
if requestsProfileArrayLength%2 == 0 {
if requestsProfileArrayLength > 2 {
// for even number of requests greater than 2
median1 := requestsProfileArray[int(requestsProfileArrayLength/2)]
median2 := requestsProfileArray[int((requestsProfileArrayLength/2)+1)]
median = float64((median1.details.elapsedTime + median2.details.elapsedTime) / 2)
} else {
median = float64((requestsProfileArray[0].details.elapsedTime + requestsProfileArray[1].details.elapsedTime) / 2)
}
} else {
// if array length is 1
if requestsProfileArrayLength == 1 {
median = float64(requestsProfileArray[0].details.elapsedTime)
} else {
// for odd number of requests greater than 1
median = float64(requestsProfileArray[int((requestsProfileArrayLength+1)/2)].details.elapsedTime)
}
}
// show time
fmt.Printf("\nFastest Time: %d ms 🏃♀️", fastestTime.details.elapsedTime)
fmt.Printf("\nSlowest Time: %d ms 🐌", slowestTime.details.elapsedTime)
fmt.Printf("\nMean Time: %.2f ms 🚀", (sum / float64(requestsProfileArrayLength)))
fmt.Printf("\nMedian Time: %.2f ms 🌟", median)
fmt.Printf("\nSuccess rate: %d %% ✅", (noOfSuccessRequests/requestsProfileArrayLength)*100)
fmt.Printf("\nFailure rate: %d %% ❌\n", (noOfFailureRequests/requestsProfileArrayLength)*100)
// Show Error codes if any
if len(errorStatusCodeArray) > 0 {
fmt.Printf("\nError Code: %s 💀\n", errorStatusCodeArray[0])
}
}
}
// function to compose details about a single request
func composeConnectionDetails(details *connectionInfo) requestsProfile {
// close connection at the end
defer details.conn.Close()
// get status code
statusCode := getStatusCode(details.conn)
req := requestsProfile{
statusCode: statusCode,
details: details,
}
return req
}
// make request to a specific URL
func makeRequest(host *string) *connectionInfo {
// parse URL
u, err := url.Parse(*host)
if err != nil {
log.Fatal("--> Cannot Parse RAW URL\nURL should start with: https://\n-->eg: https://google.com\n--> Provide absolute path", err)
}
// start the timer
startTime := time.Now()
// Dial to the website using the TLS protocol
conn, err := tls.Dial("tcp", u.Host+":443", &tls.Config{})
if err != nil {
log.Fatal("\n--> URL should start with: https://\n--> eg: https://google.com\n--> Provide absolute path")
}
// populate connection header
connectionHeader := "GET " + u.Scheme + "://" + u.Host + u.Path + " HTTP/1.1\r\nHost: " + u.Host + ":443" + "\r\nConnection: close\r\nContent-type: text/html; charset=UTF-8\r\n\r\n"
// Write to connection
fmt.Fprintf(conn, connectionHeader)
// end the timer
endTime := time.Now()
// substract endTime from startTime
// and convert it to milliseconds
totalTime := endTime.Sub(startTime).Milliseconds()
// return the connection details
return &connectionInfo{
conn: conn,
elapsedTime: totalTime,
}
}
// write raw tcp responce to console
func writeResponseToConsole(conn *tls.Conn) {
fmt.Printf("\nResponse\n")
fmt.Printf("-----------\n\n")
// Intialise a byte buffer
var buf bytes.Buffer
// copy the connection raw repsonce to buffer
io.Copy(&buf, conn)
// print the buffer
fmt.Println(buf.String())
}
// get the status code for a single request
func getStatusCode(conn *tls.Conn) string {
status, err := bufio.NewReader(conn).ReadString('\n')
if err != nil {
fmt.Println(err)
}
statusCode := string(status[9:12])
return statusCode
}