internal virtual async Task <Exception> ReadErrorResponseAsync(RestCallData callData) { try { var response = callData.Response; var hasBody = response.Content != null && response.Content.Headers.ContentLength > 0; if (!hasBody) { return(new RestException($"Web API call failed, no details returned. HTTP Status: {response.StatusCode}", response.StatusCode)); } callData.ResponseBodyString = await response.Content.ReadAsStringAsync(); switch (response.StatusCode) { case HttpStatusCode.BadRequest: if (Settings.BadRequestContentType == typeof(string)) { return(await ReadErrorResponseUntypedAsync(response)); } var errObj = Settings.Serializer.Deserialize(Settings.BadRequestContentType, callData.ResponseBodyString); return(new BadRequestException(errObj)); default: if (Settings.ServerErrorContentType == typeof(string)) { return(await ReadErrorResponseUntypedAsync(response)); } //deserialize custom object try { var serverErr = Settings.Serializer.Deserialize(Settings.ServerErrorContentType, callData.ResponseBodyString); return(new RestException("Server error: " + callData.ResponseBodyString, response.StatusCode, serverErr)); } catch (Exception ex) { var remoteErr = callData.ResponseBodyString; var msg = $"Server error: {remoteErr}. RestClient: failed to deserialize response into error object, exc: {ex}"; return(new RestException(msg, response.StatusCode, remoteErr)); } }//switch } catch (Exception exc) { Type errorType = callData.Response.StatusCode == HttpStatusCode.BadRequest ? Settings.BadRequestContentType : Settings.ServerErrorContentType; var explain = $@"Failed to read error response returned from the service. Expected content type: {errorType}. Consider changing it to match the error response for remote service. Deserializer error: {exc.Message}"; throw new Exception(explain, exc); } }
}//method private async Task ReadResponseBodyAsync(RestCallData callData) { var content = callData.Response.Content; // check response body kind var returnValueKind = GetReturnValueKind(callData.ResponseBodyType); callData.ResponseBodyString = "(not set)"; switch (returnValueKind) { case ReturnValueKind.None: return; case ReturnValueKind.HttpResponseMessage: callData.ResponseBodyObject = callData.Response; return; case ReturnValueKind.HttpContent: callData.ResponseBodyObject = content; return; case ReturnValueKind.Stream: callData.ResponseBodyObject = await content.ReadAsStreamAsync(); return; case ReturnValueKind.HttpStatusCode: var status = callData.Response.StatusCode; callData.ResponseBodyObject = status; return; case ReturnValueKind.Object: // read as string and then deserialize callData.ResponseBodyString = await content.ReadAsStringAsync(); if (!string.IsNullOrEmpty(callData.ResponseBodyString)) { callData.ResponseBodyObject = Settings.Serializer.Deserialize(callData.ResponseBodyType, callData.ResponseBodyString); } return; }// switch }
private void BuildHttpRequestContent(RestCallData request) { var body = request.RequestBodyObject; if (body == null) { return; } // = ApiClientUtil.GetRequestBodyKind(request.HttpMethod, request.RequestBodyType); if (typeof(HttpContent).IsAssignableFrom(request.RequestBodyType)) { request.Request.Content = (HttpContent)body; } else if (typeof(Stream).IsAssignableFrom(request.RequestBodyType)) { var stream = (Stream)body; request.Request.Content = new StreamContent(stream); } else { var strContent = Settings.Serializer.Serialize(body); request.Request.Content = new StringContent(strContent, this.Settings.Encoding, GetRequestMediaType()); } }
private async Task <TResult> SendAsyncImpl <TBody, TResult>(HttpMethod method, string urlTemplate, object[] urlParameters, TBody body, string acceptMediaType = null) { var start = GetTimestamp(); var callData = new RestCallData() { StartedAtUtc = RestClientHelper.GetUtc(), HttpMethod = method, UrlTemplate = urlTemplate, UrlParameters = urlParameters, Url = FormatUrl(urlTemplate, urlParameters), RequestBodyType = typeof(TBody), ResponseBodyType = typeof(TResult), RequestBodyObject = body, AcceptMediaType = acceptMediaType ?? Settings.ExplicitAcceptList ?? Settings.Serializer.ContentTypes, }; // Create RequestMessage, setup headers, serialize body callData.Request = new HttpRequestMessage(callData.HttpMethod, callData.Url); var headers = callData.Request.Headers; headers.Add("accept", callData.AcceptMediaType); foreach (var kv in this.DefaultRequestHeaders) { headers.Add(kv.Key, kv.Value); } BuildHttpRequestContent(callData); Settings.Events.OnSendingRequest(this, callData); //actually make a call callData.Response = await HttpClient.SendAsync(callData.Request, this.CancellationToken); callData.TimeElapsed = GetTimeSince(start); //measure time in case we are about to cancel and throw //check error if (callData.Response.IsSuccessStatusCode) { Settings.Events.OnReceivedResponse(this, callData); await ReadResponseBodyAsync(callData).ConfigureAwait(false); } else { callData.Exception = await this.ReadErrorResponseAsync(callData); Settings.Events.OnReceivedError(this, callData); } // get time again to include deserialization time callData.TimeElapsed = GetTimeSince(start); // Log // args: operationContext, clientName, urlTemplate, urlArgs, request, response, requestBody, responseBody, timeMs, exc var timeMs = (int)callData.TimeElapsed.TotalMilliseconds; Settings.LogAction?.Invoke(this.AppContext, this.ClientName, callData.UrlTemplate, callData.UrlParameters, callData.Request, callData.Response, callData.RequestBodyString, callData.ResponseBodyString, timeMs, callData.Exception); Settings.Events.OnCompleted(this, callData); if (callData.Exception != null) { throw callData.Exception; } return((TResult)callData.ResponseBodyObject); }//method