private async Task ProcessOne(int i) { using var dbc = _IksOutboundDbContextFactory(); var item = await dbc.Iks.SingleAsync(x => x.Id == i); var args = new IksSendCommandArgs { BatchTag = _BatchTagProvider.Create(item.Content), Content = item.Content, Signature = SignDks(item), }; await SendOne(args); var result = new IksSendResult { Exception = _LastResult.Exception, StatusCode = _LastResult?.HttpResponseCode }; _Results.Add(result); // Note: EFGS returns Created or OK on creation item.Sent = _LastResult?.HttpResponseCode == HttpStatusCode.Created; item.Error = !item.Sent; // TODO: Implement a state machine for batches; this is useful around error cases. // * Re-try for selected states. // * For data errors, end state with invalid (initially). // * Allow for manual fixing of data errors with a special retry state? // await dbc.SaveChangesAsync(); }
public async Task <HttpPostIksResult> ExecuteAsync(IksSendCommandArgs args) { var uri = new Uri($"{_EfgsConfig.BaseUrl}/diagnosiskeys/upload"); // Configure authentication certificate using var clientCert = _CertificateProvider.GetCertificate(); using var clientHandler = new HttpClientHandler { ClientCertificateOptions = ClientCertificateOption.Manual }; // Provide the authentication certificate manually clientHandler.ClientCertificates.Clear(); clientHandler.ClientCertificates.Add(clientCert); // Build request var request = new HttpRequestMessage(HttpMethod.Post, uri); request.Content = new ByteArrayContent(args.Content); request.Content.Headers.Add("Content-Type", "application/protobuf; version=1.0"); request.Headers.Add("BatchTag", args.BatchTag); request.Headers.Add("batchSignature", Convert.ToBase64String(args.Signature)); request.Headers.Add("Accept", "application/json;version=1.0"); if (_EfgsConfig.SendClientAuthenticationHeaders) { request.Headers.Add("X-SSL-Client-SHA256", clientCert.ComputeSha256Hash()); request.Headers.Add("X-SSL-Client-DN", clientCert.Subject.Replace(" ", string.Empty)); } _Logger.WriteRequest(request); _Logger.WriteRequestContent(args.Content); using var client = new HttpClient(clientHandler); client.Timeout = TimeSpan.FromSeconds(5); //TODO config try { var response = await client.SendAsync(request); return(new HttpPostIksResult { HttpResponseCode = response.StatusCode, Content = await response.Content.ReadAsStringAsync() //TODO How is this used? //TODO BatchTag = response.Headers.GetSingleValueOrDefault("batchTag") }); } catch (Exception e) { _Logger.WriteEfgsError(e); if (e.InnerException != null) { _Logger.WriteEfgsInnerException(e.InnerException); } return(new HttpPostIksResult { Exception = true }); } }
/// <summary> /// Pass/Fail /// </summary> /// <param name="args"></param> /// <returns></returns> private async Task SendOne(IksSendCommandArgs args) { // NOTE: no retry here var sender = _IksSendCommandFactory(); var result = await sender.ExecuteAsync(args); _LastResult = result; // TODO: handle the return types if (result != null) { switch (result.HttpResponseCode) { case HttpStatusCode.OK: case HttpStatusCode.Created: _Logger.WriteResponseSuccess(); return; case HttpStatusCode.MultiStatus: _Logger.WriteResponseWithWarnings(result.Content); return; case HttpStatusCode.BadRequest: _Logger.WriteResponseBadRequest(); break; case HttpStatusCode.Forbidden: _Logger.WriteResponseForbidden(); break; case HttpStatusCode.NotAcceptable: _Logger.WriteResponseNotAcceptable(); break; case HttpStatusCode.RequestEntityTooLarge: _Logger.WriteResponseRequestTooLarge(); break; case HttpStatusCode.InternalServerError: _Logger.WriteResponseInternalServerError(); break; default: _Logger.WriteResponseUnknownError(result.HttpResponseCode); break; } } // TODO for Production Quality Code: // // Handle the error codes like this: // // Auto-retry: InternalServerError, ANY undefined error // Fix config then retry: BadRequest, Forbidden // Fix file then retry: NotAcceptable // Skip: NotAcceptable // // Also: consider splitting this file up into a class which makes the calls, and a class // which handles the workflow described above. // // The table IksOut will gain the fields: State, RetryCount, Retry flag // // Code modified to include anything tagged with the Retry flag again. // // We must also define a State enumeration with a logical set of states as per the error handling. // State diagram is helpful here (TODO: Ryan) // // Basically we must be able to manually trigger retries for any data errors and configuration errors, have automatic retry for // transient errors. Ideally driven by some kind of portal, but at first it will be DB tinkering. // // For states: // // States: New, Failed, Sent (ended successfully), Skipped (ended unsuccessfully) // Failed states (combined Efgs and our own errors): // EfgsInvalidSignature, EfgsInvalidCertificate, EfgsInvalidContent, EfgsDuplicateContent, EfgsUnavailable, EfgsUndefined // UnableToConnect (when we can't connect to efgs), Unknown (catch-all for any other errors) // // I think that it's cleaner to split the states into State and FailedState; the latter being more detailed states for failures. }