@@ -18,32 +18,77 @@ public static class ApiGatewayResponseExtensions
18
18
/// Converts an <see cref="APIGatewayProxyResponse"/> to an <see cref="HttpResponse"/>.
19
19
/// </summary>
20
20
/// <param name="apiResponse">The API Gateway proxy response to convert.</param>
21
- /// <param name="context ">The <see cref="HttpContext"/> to use for the conversion.</param>
21
+ /// <param name="httpContext ">The <see cref="HttpContext"/> to use for the conversion.</param>
22
22
/// <param name="emulatorMode">The <see cref="ApiGatewayEmulatorMode"/> to use for the conversion.</param>
23
23
/// <returns>An <see cref="HttpResponse"/> representing the API Gateway response.</returns>
24
- public static void ToHttpResponse ( this APIGatewayProxyResponse apiResponse , HttpContext httpContext , ApiGatewayEmulatorMode emulatorMode )
24
+ public static async Task ToHttpResponseAsync ( this APIGatewayProxyResponse apiResponse , HttpContext httpContext , ApiGatewayEmulatorMode emulatorMode )
25
25
{
26
26
var response = httpContext . Response ;
27
27
response . Clear ( ) ;
28
28
29
+ if ( apiResponse . StatusCode == 0 )
30
+ {
31
+ await SetErrorResponse ( response , emulatorMode ) ;
32
+ return ;
33
+ }
34
+
29
35
SetResponseHeaders ( response , apiResponse . Headers , emulatorMode , apiResponse . MultiValueHeaders ) ;
30
- SetResponseBody ( response , apiResponse . Body , apiResponse . IsBase64Encoded ) ;
31
- SetContentTypeAndStatusCodeV1 ( response , apiResponse . Headers , apiResponse . MultiValueHeaders , apiResponse . StatusCode , emulatorMode ) ;
36
+ response . StatusCode = apiResponse . StatusCode ;
37
+ await SetResponseBodyAsync ( response , apiResponse . Body , apiResponse . IsBase64Encoded ) ;
32
38
}
33
39
34
40
/// <summary>
35
41
/// Converts an <see cref="APIGatewayHttpApiV2ProxyResponse"/> to an <see cref="HttpResponse"/>.
36
42
/// </summary>
37
43
/// <param name="apiResponse">The API Gateway HTTP API v2 proxy response to convert.</param>
38
- /// <param name="context ">The <see cref="HttpContext"/> to use for the conversion.</param>
39
- public static void ToHttpResponse ( this APIGatewayHttpApiV2ProxyResponse apiResponse , HttpContext httpContext )
44
+ /// <param name="httpContext ">The <see cref="HttpContext"/> to use for the conversion.</param>
45
+ public static async Task ToHttpResponseAsync ( this APIGatewayHttpApiV2ProxyResponse apiResponse , HttpContext httpContext )
40
46
{
41
47
var response = httpContext . Response ;
42
48
response . Clear ( ) ;
43
49
50
+ if ( apiResponse . StatusCode == 0 )
51
+ {
52
+ await SetErrorResponse ( response , ApiGatewayEmulatorMode . HttpV2 ) ;
53
+ return ;
54
+ }
55
+
44
56
SetResponseHeaders ( response , apiResponse . Headers , ApiGatewayEmulatorMode . HttpV2 ) ;
45
- SetResponseBody ( response , apiResponse . Body , apiResponse . IsBase64Encoded ) ;
46
- SetContentTypeAndStatusCodeV2 ( response , apiResponse . Headers , apiResponse . StatusCode ) ;
57
+ response . StatusCode = apiResponse . StatusCode ;
58
+ await SetResponseBodyAsync ( response , apiResponse . Body , apiResponse . IsBase64Encoded ) ;
59
+ }
60
+
61
+ /// <summary>
62
+ /// Sets the error response when the status code is 0 or an error occurs.
63
+ /// </summary>
64
+ /// <param name="response">The <see cref="HttpResponse"/> to set the error on.</param>
65
+ /// <param name="emulatorMode">The <see cref="ApiGatewayEmulatorMode"/> determining the error format.</param>
66
+ private static async Task SetErrorResponse ( HttpResponse response , ApiGatewayEmulatorMode emulatorMode )
67
+ {
68
+ // Set default headers first
69
+ var defaultHeaders = GetDefaultApiGatewayHeaders ( emulatorMode ) ;
70
+ foreach ( var header in defaultHeaders )
71
+ {
72
+ response . Headers [ header . Key ] = header . Value ;
73
+ }
74
+
75
+ response . ContentType = "application/json" ;
76
+
77
+ if ( emulatorMode == ApiGatewayEmulatorMode . Rest )
78
+ {
79
+ response . StatusCode = 502 ;
80
+ response . Headers [ "x-amzn-ErrorType" ] = "InternalServerErrorException" ;
81
+ var errorBytes = Encoding . UTF8 . GetBytes ( "{\" message\" : \" Internal server error\" }" ) ;
82
+ response . ContentLength = errorBytes . Length ;
83
+ await response . Body . WriteAsync ( errorBytes , CancellationToken . None ) ;
84
+ }
85
+ else
86
+ {
87
+ response . StatusCode = 500 ;
88
+ var errorBytes = Encoding . UTF8 . GetBytes ( "{\" message\" :\" Internal Server Error\" }" ) ;
89
+ response . ContentLength = errorBytes . Length ;
90
+ await response . Body . WriteAsync ( errorBytes , CancellationToken . None ) ;
91
+ }
47
92
}
48
93
49
94
/// <summary>
@@ -55,6 +100,9 @@ public static void ToHttpResponse(this APIGatewayHttpApiV2ProxyResponse apiRespo
55
100
/// <param name="multiValueHeaders">The multi-value headers to set.</param>
56
101
private static void SetResponseHeaders ( HttpResponse response , IDictionary < string , string > ? headers , ApiGatewayEmulatorMode emulatorMode , IDictionary < string , IList < string > > ? multiValueHeaders = null )
57
102
{
103
+ // Set content type first based on headers
104
+ SetContentType ( response , headers , multiValueHeaders , emulatorMode ) ;
105
+
58
106
// Add default API Gateway headers
59
107
var defaultHeaders = GetDefaultApiGatewayHeaders ( emulatorMode ) ;
60
108
foreach ( var header in defaultHeaders )
@@ -66,26 +114,71 @@ private static void SetResponseHeaders(HttpResponse response, IDictionary<string
66
114
{
67
115
foreach ( var header in multiValueHeaders )
68
116
{
69
- response . Headers [ header . Key ] = new StringValues ( header . Value . ToArray ( ) ) ;
117
+ if ( header . Key != "Content-Type" ) // Skip Content-Type as it's already handled
118
+ {
119
+ response . Headers [ header . Key ] = new StringValues ( header . Value . ToArray ( ) ) ;
120
+ }
70
121
}
71
122
}
72
123
73
124
if ( headers != null )
74
125
{
75
126
foreach ( var header in headers )
76
127
{
77
- if ( ! response . Headers . ContainsKey ( header . Key ) )
128
+ if ( header . Key != "Content-Type" ) // Skip Content-Type as it's already handled
78
129
{
79
- response . Headers [ header . Key ] = header . Value ;
80
- }
81
- else
82
- {
83
- response . Headers . Append ( header . Key , header . Value ) ;
130
+ if ( ! response . Headers . ContainsKey ( header . Key ) )
131
+ {
132
+ response . Headers [ header . Key ] = header . Value ;
133
+ }
134
+ else
135
+ {
136
+ response . Headers . Append ( header . Key , header . Value ) ;
137
+ }
84
138
}
85
139
}
86
140
}
87
141
}
88
142
143
+ /// <summary>
144
+ /// Sets the content type for the response based on headers and emulator mode.
145
+ /// </summary>
146
+ /// <param name="response">The <see cref="HttpResponse"/> to set the content type on.</param>
147
+ /// <param name="headers">The single-value headers.</param>
148
+ /// <param name="multiValueHeaders">The multi-value headers.</param>
149
+ /// <param name="emulatorMode">The <see cref="ApiGatewayEmulatorMode"/> determining the default content type.</param>
150
+ private static void SetContentType ( HttpResponse response , IDictionary < string , string > ? headers , IDictionary < string , IList < string > > ? multiValueHeaders , ApiGatewayEmulatorMode emulatorMode )
151
+ {
152
+ string ? contentType = null ;
153
+
154
+ if ( headers != null && headers . TryGetValue ( "Content-Type" , out var headerContentType ) )
155
+ {
156
+ contentType = headerContentType ;
157
+ }
158
+ else if ( multiValueHeaders != null && multiValueHeaders . TryGetValue ( "Content-Type" , out var multiValueContentType ) )
159
+ {
160
+ contentType = multiValueContentType . FirstOrDefault ( ) ;
161
+ }
162
+
163
+ response . ContentType = contentType ?? GetDefaultContentType ( emulatorMode ) ;
164
+ }
165
+
166
+ /// <summary>
167
+ /// Gets the default content type for the specified emulator mode.
168
+ /// </summary>
169
+ /// <param name="emulatorMode">The <see cref="ApiGatewayEmulatorMode"/> determining the default content type.</param>
170
+ /// <returns>The default content type string.</returns>
171
+ private static string GetDefaultContentType ( ApiGatewayEmulatorMode emulatorMode )
172
+ {
173
+ return emulatorMode switch
174
+ {
175
+ ApiGatewayEmulatorMode . Rest => "application/json" ,
176
+ ApiGatewayEmulatorMode . HttpV1 => "text/plain; charset=utf-8" ,
177
+ ApiGatewayEmulatorMode . HttpV2 => "text/plain; charset=utf-8" ,
178
+ _ => throw new ArgumentException ( $ "Unsupported emulator mode: { emulatorMode } ")
179
+ } ;
180
+ }
181
+
89
182
/// <summary>
90
183
/// Generates default API Gateway headers based on the specified emulator mode.
91
184
/// </summary>
@@ -121,120 +214,18 @@ private static Dictionary<string, string> GetDefaultApiGatewayHeaders(ApiGateway
121
214
/// <param name="response">The <see cref="HttpResponse"/> to set the body on.</param>
122
215
/// <param name="body">The body content.</param>
123
216
/// <param name="isBase64Encoded">Whether the body is Base64 encoded.</param>
124
- private static void SetResponseBody ( HttpResponse response , string ? body , bool isBase64Encoded )
125
- {
126
- if ( ! string . IsNullOrEmpty ( body ) )
127
- {
128
- byte [ ] bodyBytes ;
129
- if ( isBase64Encoded )
130
- {
131
- bodyBytes = Convert . FromBase64String ( body ) ;
132
- }
133
- else
134
- {
135
- bodyBytes = Encoding . UTF8 . GetBytes ( body ) ;
136
- }
137
-
138
- response . Body = new MemoryStream ( bodyBytes ) ;
139
- response . ContentLength = bodyBytes . Length ;
140
- }
141
- }
142
-
143
- /// <summary>
144
- /// Sets the content type and status code for API Gateway v1 responses.
145
- /// </summary>
146
- /// <param name="response">The <see cref="HttpResponse"/> to set the content type and status code on.</param>
147
- /// <param name="headers">The single-value headers.</param>
148
- /// <param name="multiValueHeaders">The multi-value headers.</param>
149
- /// <param name="statusCode">The status code to set.</param>
150
- /// <param name="emulatorMode">The <see cref="ApiGatewayEmulatorMode"/> being used.</param>
151
- private static void SetContentTypeAndStatusCodeV1 ( HttpResponse response , IDictionary < string , string > ? headers , IDictionary < string , IList < string > > ? multiValueHeaders , int statusCode , ApiGatewayEmulatorMode emulatorMode )
217
+ private static async Task SetResponseBodyAsync ( HttpResponse response , string ? body , bool isBase64Encoded )
152
218
{
153
- string ? contentType = null ;
154
-
155
- if ( headers != null && headers . TryGetValue ( "Content-Type" , out var headerContentType ) )
219
+ if ( body == null )
156
220
{
157
- contentType = headerContentType ;
158
- }
159
- else if ( multiValueHeaders != null && multiValueHeaders . TryGetValue ( "Content-Type" , out var multiValueContentType ) )
160
- {
161
- contentType = multiValueContentType . FirstOrDefault ( ) ;
221
+ return ;
162
222
}
163
223
164
- if ( contentType != null )
165
- {
166
- response . ContentType = contentType ;
167
- }
168
- else
169
- {
170
- if ( emulatorMode == ApiGatewayEmulatorMode . HttpV1 )
171
- {
172
- response . ContentType = "text/plain; charset=utf-8" ;
173
- }
174
- else if ( emulatorMode == ApiGatewayEmulatorMode . Rest )
175
- {
176
- response . ContentType = "application/json" ;
177
- }
178
- else
179
- {
180
- throw new ArgumentException ( "This function should only be called for ApiGatewayEmulatorMode.HttpV1 or ApiGatewayEmulatorMode.Rest" ) ;
181
- }
182
- }
224
+ byte [ ] bodyBytes = isBase64Encoded
225
+ ? Convert . FromBase64String ( body )
226
+ : Encoding . UTF8 . GetBytes ( body ) ;
183
227
184
- if ( statusCode != 0 )
185
- {
186
- response . StatusCode = statusCode ;
187
- }
188
- else
189
- {
190
- if ( emulatorMode == ApiGatewayEmulatorMode . Rest ) // rest api text for this message/error code is slightly different
191
- {
192
- response . StatusCode = 502 ;
193
- response . ContentType = "application/json" ;
194
- var errorBytes = Encoding . UTF8 . GetBytes ( "{\" message\" : \" Internal server error\" }" ) ;
195
- response . Body = new MemoryStream ( errorBytes ) ;
196
- response . ContentLength = errorBytes . Length ;
197
- response . Headers [ "x-amzn-ErrorType" ] = "InternalServerErrorException" ;
198
- }
199
- else
200
- {
201
- response . StatusCode = 500 ;
202
- response . ContentType = "application/json" ;
203
- var errorBytes = Encoding . UTF8 . GetBytes ( "{\" message\" :\" Internal Server Error\" }" ) ;
204
- response . Body = new MemoryStream ( errorBytes ) ;
205
- response . ContentLength = errorBytes . Length ;
206
- }
207
- }
208
- }
209
-
210
- /// <summary>
211
- /// Sets the content type and status code for API Gateway v2 responses.
212
- /// </summary>
213
- /// <param name="response">The <see cref="HttpResponse"/> to set the content type and status code on.</param>
214
- /// <param name="headers">The headers.</param>
215
- /// <param name="statusCode">The status code to set.</param>
216
- private static void SetContentTypeAndStatusCodeV2 ( HttpResponse response , IDictionary < string , string > ? headers , int statusCode )
217
- {
218
- if ( headers != null && headers . TryGetValue ( "Content-Type" , out var contentType ) )
219
- {
220
- response . ContentType = contentType ;
221
- }
222
- else
223
- {
224
- response . ContentType = "text/plain; charset=utf-8" ; // api gateway v2 defaults to this content type if none is provided
225
- }
226
-
227
- if ( statusCode != 0 )
228
- {
229
- response . StatusCode = statusCode ;
230
- }
231
- else
232
- {
233
- response . StatusCode = 500 ;
234
- response . ContentType = "application/json" ;
235
- var errorBytes = Encoding . UTF8 . GetBytes ( "{\" message\" :\" Internal Server Error\" }" ) ;
236
- response . Body = new MemoryStream ( errorBytes ) ;
237
- response . ContentLength = errorBytes . Length ;
238
- }
228
+ response . ContentLength = bodyBytes . Length ;
229
+ await response . Body . WriteAsync ( bodyBytes , CancellationToken . None ) ;
239
230
}
240
231
}
0 commit comments