|
7 | 7 | using System.CommandLine.Invocation;
|
8 | 8 | using System.Net;
|
9 | 9 | using System.Text.Json;
|
| 10 | +using System.Text.Json.Serialization; |
10 | 11 | using System.Text.RegularExpressions;
|
11 | 12 | using Titanium.Web.Proxy.EventArguments;
|
12 | 13 | using Titanium.Web.Proxy.Http;
|
13 | 14 | using Titanium.Web.Proxy.Models;
|
14 | 15 |
|
15 | 16 | namespace Microsoft365.DeveloperProxy.Plugins.RandomErrors;
|
16 | 17 | internal enum GraphRandomErrorFailMode {
|
17 |
| - Throttled, |
18 | 18 | Random,
|
19 | 19 | PassThru
|
20 | 20 | }
|
@@ -96,17 +96,57 @@ public GraphRandomErrorPlugin() {
|
96 | 96 | // uses config to determine if a request should be failed
|
97 | 97 | private GraphRandomErrorFailMode ShouldFail(ProxyRequestArgs e) => _random.Next(1, 100) <= _proxyConfiguration?.Rate ? GraphRandomErrorFailMode.Random : GraphRandomErrorFailMode.PassThru;
|
98 | 98 |
|
99 |
| - private void FailResponse(ProxyRequestArgs e, GraphRandomErrorFailMode failMode) { |
100 |
| - HttpStatusCode errorStatus; |
101 |
| - if (failMode == GraphRandomErrorFailMode.Throttled) { |
102 |
| - errorStatus = HttpStatusCode.TooManyRequests; |
| 99 | + private void FailResponse(ProxyRequestArgs e) { |
| 100 | + // pick a random error response for the current request method |
| 101 | + var methodStatusCodes = _methodStatusCode[e.Session.HttpClient.Request.Method]; |
| 102 | + var errorStatus = methodStatusCodes[_random.Next(0, methodStatusCodes.Length)]; |
| 103 | + UpdateProxyResponse(e, errorStatus); |
| 104 | + } |
| 105 | + |
| 106 | + private void FailBatch(ProxyRequestArgs e) { |
| 107 | + var batchResponse = new GraphBatchResponsePayload(); |
| 108 | + |
| 109 | + var batch = JsonSerializer.Deserialize<GraphBatchRequestPayload>(e.Session.HttpClient.Request.BodyString); |
| 110 | + if (batch == null) { |
| 111 | + UpdateProxyBatchResponse(e, batchResponse); |
| 112 | + return; |
103 | 113 | }
|
104 |
| - else { |
105 |
| - // pick a random error response for the current request method |
106 |
| - var methodStatusCodes = _methodStatusCode[e.Session.HttpClient.Request.Method]; |
107 |
| - errorStatus = methodStatusCodes[_random.Next(0, methodStatusCodes.Length)]; |
| 114 | + |
| 115 | + var responses = new List<GraphBatchResponsePayloadResponse>(); |
| 116 | + foreach (var request in batch.Requests) |
| 117 | + { |
| 118 | + try { |
| 119 | + // pick a random error response for the current request method |
| 120 | + var methodStatusCodes = _methodStatusCode[request.Method]; |
| 121 | + var errorStatus = methodStatusCodes[_random.Next(0, methodStatusCodes.Length)]; |
| 122 | + |
| 123 | + var response = new GraphBatchResponsePayloadResponse { |
| 124 | + Id = request.Id, |
| 125 | + Status = (int)errorStatus, |
| 126 | + Body = new GraphBatchResponsePayloadResponseBody { |
| 127 | + Error = new GraphBatchResponsePayloadResponseBodyError { |
| 128 | + Code = new Regex("([A-Z])").Replace(errorStatus.ToString(), m => { return $" {m.Groups[1]}"; }).Trim(), |
| 129 | + Message = "Some error was generated by the proxy.", |
| 130 | + } |
| 131 | + } |
| 132 | + }; |
| 133 | + |
| 134 | + if (errorStatus == HttpStatusCode.TooManyRequests) { |
| 135 | + var retryAfterDate = DateTime.Now.AddSeconds(retryAfterInSeconds); |
| 136 | + var requestUrl = ProxyUtils.GetAbsoluteRequestUrlFromBatch(e.Session.HttpClient.Request.RequestUri, request.Url); |
| 137 | + e.ThrottledRequests.Add(new ThrottlerInfo(GraphUtils.BuildThrottleKey(requestUrl), ShouldThrottle, retryAfterDate)); |
| 138 | + response.Headers = new Dictionary<string, string>{ |
| 139 | + { "Retry-After", retryAfterInSeconds.ToString() } |
| 140 | + }; |
| 141 | + } |
| 142 | + |
| 143 | + responses.Add(response); |
| 144 | + } |
| 145 | + catch {} |
108 | 146 | }
|
109 |
| - UpdateProxyResponse(e, errorStatus); |
| 147 | + batchResponse.Responses = responses.ToArray(); |
| 148 | + |
| 149 | + UpdateProxyBatchResponse(e, batchResponse); |
110 | 150 | }
|
111 | 151 |
|
112 | 152 | private ThrottlingInfo ShouldThrottle(Request request, string throttlingKey) {
|
@@ -139,6 +179,25 @@ private void UpdateProxyResponse(ProxyRequestArgs ev, HttpStatusCode errorStatus
|
139 | 179 | _logger?.LogRequest(new[] { $"{(int)errorStatus} {errorStatus.ToString()}" }, MessageType.Chaos, new LoggingContext(ev.Session));
|
140 | 180 | session.GenericResponse(body ?? string.Empty, errorStatus, headers);
|
141 | 181 | }
|
| 182 | + |
| 183 | + private void UpdateProxyBatchResponse(ProxyRequestArgs ev, GraphBatchResponsePayload response) { |
| 184 | + // failed batch uses a fixed 424 error status code |
| 185 | + var errorStatus = HttpStatusCode.FailedDependency; |
| 186 | + |
| 187 | + SessionEventArgs session = ev.Session; |
| 188 | + string requestId = Guid.NewGuid().ToString(); |
| 189 | + string requestDate = DateTime.Now.ToString(); |
| 190 | + Request request = session.HttpClient.Request; |
| 191 | + var headers = ProxyUtils.BuildGraphResponseHeaders(request, requestId, requestDate); |
| 192 | + |
| 193 | + var options = new JsonSerializerOptions { |
| 194 | + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull |
| 195 | + }; |
| 196 | + string body = JsonSerializer.Serialize(response, options); |
| 197 | + _logger?.LogRequest(new[] { $"{(int)errorStatus} {errorStatus.ToString()}" }, MessageType.Chaos, new LoggingContext(ev.Session)); |
| 198 | + session.GenericResponse(body, errorStatus, headers); |
| 199 | + } |
| 200 | + |
142 | 201 | private static string BuildApiErrorMessage(Request r) => $"Some error was generated by the proxy. {(ProxyUtils.IsGraphRequest(r) ? ProxyUtils.IsSdkRequest(r) ? "" : String.Join(' ', MessageUtils.BuildUseSdkForErrorsMessage(r)) : "")}";
|
143 | 202 |
|
144 | 203 | public override void Register(IPluginEvents pluginEvents,
|
@@ -189,7 +248,12 @@ private async Task OnRequest(object? sender, ProxyRequestArgs e) {
|
189 | 248 | if (failMode == GraphRandomErrorFailMode.PassThru && _proxyConfiguration?.Rate != 100) {
|
190 | 249 | return;
|
191 | 250 | }
|
192 |
| - FailResponse(e, failMode); |
| 251 | + if (ProxyUtils.IsGraphBatchUrl(e.Session.HttpClient.Request.RequestUri)) { |
| 252 | + FailBatch(e); |
| 253 | + } |
| 254 | + else { |
| 255 | + FailResponse(e); |
| 256 | + } |
193 | 257 | state.HasBeenSet = true;
|
194 | 258 | }
|
195 | 259 | }
|
|
0 commit comments