-
Notifications
You must be signed in to change notification settings - Fork 29
/
client.ts
159 lines (146 loc) · 5.86 KB
/
client.ts
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
import { RefinedResponse, ResponseType, Params } from 'k6/http'
import { AWSConfig } from './config'
import { Endpoint } from './endpoint'
import { HTTPHeaders } from './http'
import {
GeneralErrorKind,
DNSErrorKind,
TCPErrorKind,
TLSErrorKind,
HTTP2ErrorKind,
GeneralError,
DNSError,
TCPError,
TLSError,
HTTP2Error,
} from './error'
/**
* Class allowing to build requests targeting AWS APIs
*
* This class is meant to be used as a base class for specific
* services clients. See S3Client or SecretsManagerClient for
* usage examples.
*/
export class AWSClient {
readonly awsConfig: AWSConfig
readonly serviceName: string
// Because jslib-aws is mostly used as a way to setup or feed k6 tests, and
// we want the jslib-aws to be able to disregard k6's discardResponseBodies: meaning
// that for instance, even when setting discardResponseBodies to true in the k6 options, using
// s3.getObject still receives the underlying response body and returns data to the user.
//
// To achieve this, we set the responseType to 'text' in the baseRequestParams, as it
// will lead the http module to ignore the discardResponseBodies option.
//
// AWS Client classes can override this value if they want to receive the response body
// as a different type ('binary' for instance, e.g. S3Client.getObject).
//
// See #45: https://github.com/grafana/k6-jslib-aws/issues/45
readonly baseRequestParams: Params = {
responseType: 'text',
}
private _endpoint?: Endpoint
/**
* @param {AWSConfig} awsConfig - configuration attributes to use when interacting with AWS' APIs
* @param {string} serviceName - name of the service to target.
* @param {URIEncodingConfig} URIencodingConfig - configures how requests URIs should be encoded.
*/
constructor(awsConfig: AWSConfig, serviceName: string) {
this.awsConfig = awsConfig
this.serviceName = serviceName
// If an endpoint is provided in the config, set it
// to ensure the default endpoint is not used.
if (awsConfig.endpoint != undefined) {
this._endpoint = awsConfig.endpoint
}
}
/**
* Represents the endpoint URL of the AWS service.
*
* If no custom endpoint is set, a default endpoint will be constructed
* using the service name and region provided in the AWS config.
*
* @type {Endpoint}
* @public
*/
public get endpoint() {
if (this._endpoint == undefined) {
this._endpoint = new Endpoint(
`https://${this.serviceName}.${this.awsConfig.region}.amazonaws.com`
)
}
return this._endpoint
}
/**
* Updates the endpoint URL of the AWS service.
*
* This can be used to override the default AWS service endpoint or set a custom endpoint.
*
* @param {Endpoint} endpoint - The new endpoint to set for the AWS service.
* @public
*/
public set endpoint(endpoint: Endpoint) {
this._endpoint = endpoint
}
/**
* Handles the k6 http response potential errors produced when making a
* request to an AWS service.
*
* Importantly, this method only handles errors that emerge from the k6 http client itself, and
* won't handle AWS specific errors. To handle AWS specific errors, client classes are
* expected to implement their own error handling logic by overriding this method.
*
* @param response {RefinedResponse<ResponseType | undefined>} the response received by the k6 http client
* @param operation {string | undefined } the name of the operation that was attempted when the error occurred
* @param {boolean} returns true if an error was handled, false otherwise
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected handleError(response: RefinedResponse<ResponseType | undefined>, operation?: string): boolean {
const status: number = response.status
const errorCode: number = response.error_code
const errorMessage: string = response.error
// We consider codes 200-299 as success.
//
// We do not consider 3xx as success as some services such as S3 can use
// 301 to indicate a bucket not found
if (status >= 200 && status < 300 && errorMessage == '' && errorCode === 0) {
return false
}
switch (errorCode) {
case GeneralErrorKind.GenericError:
case GeneralErrorKind.NonTCPNetworkError:
case GeneralErrorKind.InvalidURL:
case GeneralErrorKind.HTTPRequestTimeout:
throw new GeneralError(errorCode);
case DNSErrorKind.GenericDNSError:
case DNSErrorKind.NoIPFound:
case DNSErrorKind.BlacklistedIP:
case DNSErrorKind.BlacklistedHostname:
throw new DNSError(errorCode);
case TCPErrorKind.GenericTCPError:
case TCPErrorKind.BrokenPipeOnWrite:
case TCPErrorKind.UnknownTCPError:
case TCPErrorKind.GeneralTCPDialError:
case TCPErrorKind.DialTimeoutError:
case TCPErrorKind.DialConnectionRefused:
case TCPErrorKind.DialUnknownError:
case TCPErrorKind.ResetByPeer:
throw new TCPError(errorCode);
case TLSErrorKind.GeneralTLSError:
case TLSErrorKind.UnknownAuthority:
case TLSErrorKind.CertificateHostnameMismatch:
throw new TLSError(errorCode);
case HTTP2ErrorKind.GenericHTTP2Error:
case HTTP2ErrorKind.GeneralHTTP2GoAwayError:
throw new HTTP2Error(errorCode);
}
return true
}
}
/**
* Type alias representing the result of an AWSClient.buildRequest call
*/
export interface AWSRequest {
readonly url: string
readonly headers: HTTPHeaders
}