I'm creating an "SDK/Proxy" class using HttpClient and would like to pass exceptions thrown by the service the SDK is consuming to the application that is using this SDK. The original exception needs to be preserved unaltered and not wrapped. I've tried EnsureSuccessStatusCode() but it is no good because the original exception is lost to the consumer.
Below is SDK code that is attempting to catch exceptions from base service and pass it on to the consumer;
public async Task<string> GetValue(string effect)
{
using (var client = GetHttpClient())
{
HttpResponseMessage resp = await client.GetAsync("api/values?effect=" + effect).ConfigureAwait(false);
if (resp.IsSuccessStatusCode)
{
return await resp.Content.ReadAsStringAsync();
}
throw await resp.Content.ReadAsAsync<Exception>();
}
}
The "consumer" service is using the SDK like so;
public async Task<string> Get(string effect)
{
return await baseSvc.GetValue(effect);
}
When testing this I am getting the following response;
{
Message: "An error has occurred."
ExceptionMessage: "Member 'ClassName' was not found."
ExceptionType: "System.Runtime.Serialization.SerializationException"...
the base service code throwing the exception is this;
public async Task<string> Get(string effect)
{
switch (effect.ToLower())
{
case "string":
return "this is a string";
case "exception":
throw new ApplicationException("this is an application exception.");
default:
return "effect requested does not exist.";
}
}
Is it possible to "flow" the original unaltered exception from the consumed service through the SDK to the consumer?
You can use following.
string responseBody = response.Content.ReadAsStringAync().Result;
throw new HttpException((int)response.StatusCode, responseBody);
I have referred this - web API and MVC exception handling
Related
Working on a POC of transitioning from BinaryFormater to Protobuf for Serialization and deserialization inorder to reduce the deserialization time. While trying to deserialize using the protobuf library I get the following error "Invalid wire-type; this usually means you have over-written a file without truncating or setting the length" while deserializing a file in a rest web API project but the same code runs fine in another web job project with same .Net version.
protobuf-net Version: 3.1.26
.NET version: .NET framework 4.6.2
Seems to be maybe an internal package dependency version issue or issue if the deserialization happens in a w3 process.
Has anyone faced such issues with the protobuf-net package for a REST service.
Below is the code where the ProtoDeserialize function throws a exception Serializer.Deserialize<T>(stream) is called
[ProtoContract]
public class Temp
{
[ProtoMember(1)]
public string name;
[ProtoMember(2)]
public int no;
}
[HttpGet]
public HttpResponseMessage DerserializeProtoBuf()
{
try
{
var x1 = new Temp();
x1.name = "testData";
x1.no = 10;
var data1 = ProtoSerialize<Temp>(x1);
var y = ProtoDeserialize<Temp>(data1); // throws exception
}
catch
{
}
}
public static T ProtoDeserialize<T>(byte[] data) where T : class
{
if (null == data) return null;
try
{
using (var stream = new MemoryStream(data))
{
using (var decompressor = new GZipStream(stream, CompressionMode.Decompress))
{
return Serializer.Deserialize<T>(stream); // throws Invalid wire-type error here
}
}
}
catch(Exception ex)
{
throw new InvalidDataException(String.Format("Invalid data format when proto deserializing {0}", typeof(T).Name), ex);
}
}
public static byte[] ProtoSerialize<T>(T record) where T : class
{
if (null == record) return null;
try
{
using (var stream = new MemoryStream())
{
using (var gZipStream = new GZipStream(stream, CompressionMode.Compress))
{
Serializer.Serialize(gZipStream, record);
}
return stream.ToArray();
}
}
catch(Exception ex)
{
throw new InvalidDataException(String.Format("Invalid data format when proto serializing {0}", typeof(T).Name), ex);
}
}
I have tried adding the package dependencies versions explicilty by adding bindingRedirects.
Have tried updating and degrading the version of protobuf to 2.3.7 and other before versions
Pass decompressor instead of stream to Deserialize. You're passing it the compressed gzip data instead of the decompressed protobuf payload.
I am getting following exception when calling an Asp.NET Core 3.1 web api from a Blazor app.
But same code works great from visual studio debugging
Response status code does not indicate success: 405 (Method Not Allowed).
at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
at Microsoft.AspNetCore.Components.HttpClientJsonExtensions.SendJsonAsync[T](HttpClient httpClient, HttpMethod method, String requestUri, Object content)*
UI Code:
public async Task<bool> UpdateCOAValue(COALookUps dataItem)
{
bool result = false;
try
{
bool response = await _httpClient.SendJsonAsync<bool>(HttpMethod.Put, string.Format(#_webApi.WebAPIUrl, "update"), dataItem);
result = await Task.FromResult(response);
}
catch (Exception ex)
{
Log.Error("Error: {0}", ex);
}
return result;
}
Web API Controller Method:
[HttpPut("update")]
public bool UpdateCOAEntry([FromBody]COALookups value)
{
try
{
List<SqlParameter> lstSQLParams = new List<SqlParameter>();
SqlParameter paramCOALookUpID = new SqlParameter();
//other code
dbManager.Update("UpdateCOALookUp", CommandType.StoredProcedure, lstSQLParams.ToArray());
}
catch (Exception ex)
{
Log.Error("Error: {0}", ex);
return false;
}
return true;
}
Web API Controllers syntax:
[Route("api/[controller]")]
[ApiController]
public class COAController : ControllerBase
{
}
Here is what worked for me. (this is a workaround), will have to redo this after each release. Please post if anyone has a better solution.
Open WebDav Authoring Rules and then select Disable WebDAV option
present on the right bar.
Select Modules, find the WebDAV Module and remove it.
Select HandlerMapping, find the WebDAVHandler and remove it.
I found this solution working than changing any settings in IIS
In ConfigureServices method add following
var handler = new HttpClientHandler()
{
UseDefaultCredentials = false,
Credentials = System.Net.CredentialCache.DefaultCredentials,
AllowAutoRedirect = true
};
services.AddSingleton(sp =>
new HttpClient(handler)
{
BaseAddress = new Uri(Configuration["WebAPI:BaseUrl"])
});
I would like the following method to throw a custom exception if an error occurs:
#Service
public class MyClass {
private final WebClient webClient;
public MatcherClient(#Value("${my.url}") final String myUrl) {
this.webClient = WebClient.create(myUrl);
}
public void sendAsync(String request) {
Mono<MyCustomResponse> result = webClient.post()
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.body(BodyInserters.fromObject(request))
.retrieve()
.doOnError(throwable -> throw new CustomException(throwable.getMessage()))
.subscribe(response -> log.info(response));
}
}
I have also set up a unit test expecting the CustomException to be thrown. Unfortunately the test fails and the Exception is kind of wrapped into a Mono object. Here also the test code for reference:
#Test(expected = CustomException.class)
public void testSendAsyncRethrowingException() {
MockResponse mockResponse = new MockResponse()
.setHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.setResponseCode(500).setBody("Server error");
mockWebServer.enqueue(mockResponse);
matcherService.matchAsync(track);
}
I'm using the MockWebServer to mock an error in the test.
So, how should I implement the doOnError or onError part if the call in order to make my method really to throw an exception?
I'd advise to expose a reactive API that returns the Mono<Void> from the webclient, especially if you name your method "sendAsync". It's not async if you have to block for the call to return/fail. If you want to provide a sendSync() alternative, you can always make it call sendAsync().block().
For the conversion of exception, you can use the dedicated onErrorMap operator.
For the test, the thing is, you can't 100% test asynchronous code with purely imperative and synchronous constructs (like JUnit's Test(expected=?) annotation). (although some reactive operator don't induce parallelism so this kind of test can sometimes work).
You can also use .block() here (testing is one of the rare occurrences where this is unlikely to be problematic).
But if I were you I'd get in the habit of using StepVerifier from reactor-test. To give an example that sums up my recommendations:
#Service
public class MyClass {
private final WebClient webClient;
public MatcherClient(#Value("${my.url}") final String myUrl) {
this.webClient = WebClient.create(myUrl);
}
public Mono<MyCustomResponse> sendAsync(String request) {
return webClient.post()
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.body(BodyInserters.fromObject(request))
.retrieve()
.onErrorMap(throwable -> new CustomException(throwable.getMessage()))
//if you really need to hardcode that logging
//(can also be done by users who decide to subscribe or further add operators)
.doOnNext(response -> log.info(response));
}
}
and the test:
#Test(expected = CustomException.class)
public void testSendAsyncRethrowingException() {
MockResponse mockResponse = new MockResponse()
.setHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.setResponseCode(500).setBody("Server error");
mockWebServer.enqueue(mockResponse);
//Monos are generally lazy, so the code below doesn't trigger any HTTP request yet
Mono<MyCustomResponse> underTest = matcherService.matchAsync(track);
StepVerifier.create(underTest)
.expectErrorSatisfies(t -> assertThat(t).isInstanceOf(CustomException.class)
.hasMessage(throwable.getMessage())
)
.verify(); //this triggers the Mono, compares the
//signals to the expectations/assertions and wait for mono's completion
}
The retrieve() method in WebClient throws a WebClientResponseException
whenever a response with status code 4xx or 5xx is received.
1. You can customize the exception using the onStatus() method
public Mono<JSONObject> listGithubRepositories() {
return webClient.get()
.uri(URL)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, clientResponse ->
Mono.error(new MyCustomClientException())
)
.onStatus(HttpStatus::is5xxServerError, clientResponse ->
Mono.error(new MyCustomServerException())
)
.bodyToMono(JSONObject.class);
}
2. Throw the custom exception by checking the response status
Mono<Object> result = webClient.get().uri(URL).exchange().log().flatMap(entity -> {
HttpStatus statusCode = entity.statusCode();
if (statusCode.is4xxClientError() || statusCode.is5xxServerError())
{
return Mono.error(new Exception(statusCode.toString()));
}
return Mono.just(entity);
}).flatMap(clientResponse -> clientResponse.bodyToMono(JSONObject.class))
Reference: https://www.callicoder.com/spring-5-reactive-webclient-webtestclient-examples/
Instead of using doOnError I swiched to subscribe method accepting also an error consumer:
Mono<MyCustomResponse> result = webClient.post()
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.body(BodyInserters.fromObject(request))
.retrieve()
.subscribe(response -> log.info(response),
throwable -> throw new CustomException(throwable.getMessage()));
This documentation helps a lot: https://projectreactor.io/docs/core/release/reference/index.html#_error_handling_operators
I have the following:
The "400 Bad request" is converted to a ResourceAccessException in Spring.
Is there any way to retrieve the payload here? I want to send the "errorMessage" further up the call chain.
Code-wise the following is used to do the request:
public <T> T post(String url, Object request, Class<T> className) {
try {
return logEnhancedRestTemplate.postForObject(url, request, className);
} catch(RestClientException ex) {
throw handleErrors(ex, url);
}
}
It is in the "handleErrors" method I want to use the "errorMessage" from the body.
If you want to retrieve the message of an exception use the method getMessage().
In your specific example maybe is better if you catch a generic exception, since I suppose that every type of Runtime exception should call your method handleErrors(ex, url).
If this is the case, I suggest you to modify your code with:
public <T> T post(String url, Object request, Class<T> className) {
try {
return logEnhancedRestTemplate.postForObject(url, request, className);
} catch(Exception ex) {
throw handleErrors(ex, url);
}
}
I have created a very simple servlet that uses HTTP Post method. I have tested it on my local Apache Tomcat server using a simple HTML form that works. I want to integrate it with my GWT app. I am able to call it using FormPanel - in that case it downloads content and there is a flicker in my browser window.
I know I need to use RequestBuilder to access it. But my response.getStatusCode() in my overloaded public void onResponseReceived(Request request, Response response) method always return status as 0 and response.getText() return null
String url = "http://localhost:8080/servlets/servlet/ShapeColor";
builder = new RequestBuilder(RequestBuilder.POST, URL.encode(url));
try {
String json = getJSONString();
//builder.setTimeoutMillis(10000);
builder.setHeader("Access-Control-Allow-Origin", "*");
builder.setHeader("Content-type", "application/x-www-form-urlencoded");
builder.sendRequest(json, new RequestCallback() {
#Override
public void onError(Request request, Throwable exception) {
Window.alert("Couldn't retrieve JSON");
}
#Override
public void onResponseReceived(Request request, Response response) {
if (200 == response.getStatusCode()) {
System.out.println("res:"+response.getText());
} else {
System.out.println("err: " + response.getStatusCode()+","+response.getText());
}
}
});
//Request response = builder.send();
} catch (RequestException e) {
// TODO Auto-generated catch block
}
I have tried many thing including changing my servlet following CORS reference ( https://code.google.com/p/gwtquery/wiki/Ajax#CORS_%28Cross_Origin_Resource_Sharing%29 )
It always works on browser using my test.html, but not from my App. Although, onResponseReceived method always gets called
Thanks
KKM
Have you checked if your call in the app violates the Same-origin policy (http://en.wikipedia.org/wiki/Same-origin_policy) in some way? The GWT RequestBuilder uses XMLHttpRequest internally, so it does fall under the SOP.
Does your GWT app run inside the same domain (server + port) as the servlet? Does it use the same protocol (https or http)?