示例#1
0
        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
                });
            }
        }
示例#3
0
        /// <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.
        }