Not able to set HTTP Status code for a custom ThingWorx service.
I need to send appropriate HTTP status code for error conditions in my Thingworx service. When I use GenericHTTPException, it sets the right code, but it prepends "Unable to Invoke Service" to my JSON response and that does not work for the AJAX client. I should be able to send pure JSON response along with the right HTTP status code
#ThingworxServiceDefinition(name = "GetServiceProviderHeirarchy", category = "PTC")
#ThingworxServiceResult(name = "result", baseType = "JSON")
public JSON GetServiceProviderHeirarchy(
#ThingworxServiceParameter(name = "seedURI", baseType = "STRING")String seedURI,
#ThingworxServiceParameter(name = "depth", baseType = "INTEGER")Integer depth,
#ThingworxServiceParameter(name = "resourceType", baseType = "STRING")String resourceType,
#ThingworxServiceParameter(name = "serverName", baseType = "STRING")String serverName)
throws Exception {
if(serverName == null || serverName.isEmpty()){
JSONObject jsonErrObject = new JSONObject();
jsonErrObject.put("message", "Values to input parameter serverName is missing");
logger.error("serverName is not provided");
throw new InvalidRequestException(jsonErrObject.toString(), RESTAPIConstants.StatusCode.STATUS_BAD_REQUEST);
}
}
Actual result:
Unable to Invoke Service GetServiceProviderHeirarchy on
UpstreamOslcDataServicesThing : {"message":"No Configuration found for
resource type RequirementResourc. Please contact your administrator."}
Expected result:
{"message":"No Configuration found for resource type
RequirementResourc. Please contact your administrator."}
One possible option would be to include the jax-rs libraries as a ThingWorx resource and throw those specific exceptions as required. Say for bad request you can throw javax.ws.rs.BadRequestException'.
if(serverName == null || serverName.isEmpty()){
String message = "No Configuration found for resource. Please contact your administrator.";
throw new BadRequestException(message);
}
Related
Assume following code written with Quarkus. But can as well be with micronaut.
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#APIResponses(
value = {
#APIResponse(
responseCode = "201",
description = "Customer Created"),
#APIResponse(
responseCode = "400",
description = "Customer already exists for customerId")
}
)
public Response post(#Valid Customer customer) {
final Customer saved = customerService.save(customer);
return Response.status(Response.Status.CREATED).entity(saved).build();
}
The Customer definition includes a field pictureUrl. CustomerService is responsible to validate the the URL is a valid URL and that the image really exists.
This means that following exception will be processed by the service: MalformedURLException and IOException. The CustomerService catches these errors and throws an application specific exception to report that the image does not exist or the path is not correct: ApplicationException.
How do you document this error case with microprofile?
My research suggests that I have to implement an exception mapper of the form:
public class ApplicationExceptionMapper implements ExceptionMapper<NotFoundException> {
#Override
#APIResponse(responseCode = "404", description = "Image not Found",
content = #Content(
schema = #Schema(implementation = Customer.class)
)
)
public Response toResponse(NotFoundException t) {
return Response.status(404, t.getMessage()).build();
}
}
And once I have a such mapper, the framework would know how to convert my exception into Response. Is my analysis correct? What is the best practice?
You are more or less pointed in the right direction, your question can be divided in two, let me answer separately both:
How to document an error using microprofile openapi: Using the api responses and the operation description is the correct way as you are doing, you can include a extended description of your errors and the specific Http error code associated with each if you want. This annotations should be present in the Rest Resource not in the ExceptionMapper.
Handling custom errors with micro profile or just the Jax-RS way of dealing with exceptions: A endpoint implemented with Jax-RS Apis knows how to hande WebApplicationExceptions automatically, By launching a custom exception which has this one us parent, the Jax-RS implementation automatically will know how to map your Exception to a Response.
Dealing with Unexpected exceptions or customizing responses for certain exceptions: By implementing the interface ExceptionMapper, you can further customize the response generation for specific exceptions if you want but this is not generally required.
A common good strategy for dealing with the scenario you have explained is to have a family of custom exceptions that extend the WebApplicationException where you can specify the response code and message, Or just using the ones provided by jax-rs. And if you need further customization with i18n support or its important to provide a response entity with details associated to the error, then implement the ExceptionMapper.
For the actual error handling and conversion of exceptions to corresponding HTTP responses you would use the JaxRS exception mapper as you already started. But the exception mapper itself, is not considered at all during the creation of the OpenAPI schema, as the OpenAPI extension has no way of obtaining the information about the actual error responses produced by your exception mapper. So you need to declare the possible error cases on the actual endpoint methods -which can be a bit tedious, especially if your endpoint methods can result in multiple error responses (400, 500, 409..). But you can reduce the amount of duplicated code by creating a shared error response definitions.
A common pattern for error handling on API layer is to explicitly define the models for your error responses e.g. I want all my endpoints to return error responses in a form of json document that looks sth like this:
{
"errorCode" : "MY_API_123",
"errorDescription" : "This operation is not allowed"
//anything else, details, links etc
}
So I create a POJO model for the response:
#Data
#AllArgsConstructor
public class ErrorResponse {
private String errorCode;
private String errorDescription;
}
And in my exception mapper I can convert my exception to the error response above:
public Response toResponse(Exception e) {
// you can define a mapper for a more general type of exception and then create specific error responses based on actual type, or you could define your own application level exception with custom error info, keys, maybe even i18n, that you catch here, you get the idea
if (e instanceOf NotFoundException) {
return Response.status(404, new ErrorResponse("MY_API_400", "Object not found")).build();
}
if (e instanceOf IllegalArgumentException) {
return Response.status(400, new ErrorResponse("MY_API_400", "Invalid request")).build();
}
// fallback if we dont have any better error info
return Response.status(500, new ErrorResponse("MY_API_100", "Internal server error")).build();
}
You can then document the possible error responses using the OpenAPi annotations:
#APIResponses({
#APIResponse(responseCode = "201", description = "Customer Created"),
#APIResponse(responseCode = "400", description = "Other object exists for customerId", content = #Content(schema = #Schema(implementation = ErrorResponse.class))),
#APIResponse(responseCode = "500", description = "Internal error", content = #Content(schema = #Schema(implementation = ErrorResponse.class)))
})
public Response yourMethod()
Or if you have some responses that are repeated often (such as generic internal server error, unathorized/unathenticated) you can document them on your class extending JaxRS Application and then reference them in your endpoints like this:
#Authenticated
#APIResponses({
#APIResponse(responseCode = "200", description = "Ok"),
#APIResponse(responseCode = "401", ref = "#/components/responses/Unauthorized"),
#APIResponse(responseCode = "500", ref = "#/components/responses/ServerError")
})
public Response someAPIMethod() {
Example JaxRS application class(useful for other common top level openapi schema attributes)
#OpenAPIDefinition(
info = #Info(title = "My cool API", version = "1.0.0"),
components = #Components(
schemas = {#Schema(name = "ErrorResponse", implementation = ErrorResponse.class)},
responses = {
#APIResponse(name = "ServerError", description = "Server side error", content = #Content(mediaType = MediaType.APPLICATION_JSON, schema = #Schema(ref = "#/components/schemas/ErrorResponse")), responseCode = "500"),
#APIResponse(name = "NotFound", description = "Requested object not found", content = #Content(schema = #Schema(ref = "#/components/schemas/ErrorResponse")), responseCode = "404"),
#APIResponse(name = "Forbidden", description = "Authorization error", responseCode = "403"),
#APIResponse(name = "BadRequest", description = "Bad Request", responseCode = "400"),
#APIResponse(name = "Unauthorized", description = "Authorization error", responseCode = "401")})
)
public class RESTApplication extends Application {
}
I have Spring Boot MVC application where exceptions are handled within a general #ControllerAdvice. Some of them do not include a response body, like for instance:
#ExceptionHandler(EntityNotFoundException.class)
#ResponseStatus(NOT_FOUND)
void handleEntityNotFound() {
}
Everything works fine, but I run into issues if I want to expose my endpoints with SpringDoc. The exception handler is picked up correctly, however, the Swagger-UI displays a random response for 404s:
Note, that this even isn't the response of the GET endpoint in question here, but a response from a method from a different RestController.
What do I have to do to hide wrong response? I already tried
#ApiResponse(responseCode = "404", description = "Not found", content = #Content)
#ExceptionHandler(EntityNotFoundException.class)
#ResponseStatus(NOT_FOUND)
void handleEntityNotFound() {
}
as suggested in the docs, but that does not work.
Your issue is resolved in v1.4.1:
https://github.com/springdoc/springdoc-openapi/issues/711
If you don't want to hide it and want to show the appropriate response, one way to do that is you can define a string as an example
public static final String exampleInternalError = "{\r\n"
+ " \"code\": 404,\r\n"
+ " \"message\": \"Resource not found\"\r\n" + "}";
same is used to show the example as
#ApiResponse(responseCode = "404",
description = "Resource not found",
//And different example for this error
content = #Content(schema = #Schema(implementation = ErrorResponse.class),
examples = #ExampleObject(description = "Internal Error", value = exampleInternalError)))
I have the following code in my web application:
#ExceptionHandler(InstanceNotFoundException.class)
#ResponseStatus(HttpStatus.NO_CONTENT)
public ModelAndView instanceNotFoundException(InstanceNotFoundException e) {
return returnErrorPage(message, e);
}
Is it possible to also append a status message to the response? I need to add some additional semantics for my errors, like in the case of the snippet I posted I would like to append which class was the element of which the instance was not found.
Is this even possible?
EDIT: I tried this:
#ResponseStatus(value=HttpStatus.NO_CONTENT, reason="My message")
But then when I try to get this message in the client, it's not set.
URL u = new URL ( url);
HttpURLConnection huc = (HttpURLConnection) u.openConnection();
huc.setRequestMethod("GET");
HttpURLConnection.setFollowRedirects(true);
huc.connect();
final int code = huc.getResponseCode();
String message = huc.getResponseMessage();
Turns out I needed to activate custom messages on Tomcat using this parameter:
-Dorg.apache.coyote.USE_CUSTOM_STATUS_MSG_IN_HEADER=true
The message can be in the body rather than in header. Similar to a successful method, set the response (text, json, xml..) to be returned, but set the http status to an error value. I have found that to be more useful than the custom message in header. The following example shows the response with a custom header and a message in body. A ModelAndView that take to another page will also be conceptually similar.
#ExceptionHandler(InstanceNotFoundException.class)
public ResponseEntity<String> handle() {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("ACustomHttpHeader", "The custom value");
return new ResponseEntity<String>("the error message", responseHeaders, HttpStatus.INTERNAL_SERVER_ERROR);
}
I created some web apis and when an error happens the api returns HttpResponseMessage that is created with CreateErrorResponse message. Something like this:
return Request.CreateErrorResponse(
HttpStatusCode.NotFound, "Failed to find customer.");
My problem is that I cannot figure out how to retrieve the message (in this case "Failed to find customer.") in consumer application.
Here's a sample of the consumer:
private static void GetCustomer()
{
var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
string data =
"{\"LastName\": \"Test\", \"FirstName\": \"Test\"";
var content = new StringContent(data, Encoding.UTF8, "application/json");
var httpResponseMessage =
client.PostAsync(
new Uri("http://localhost:55202/api/Customer/Find"),
content).Result;
if (httpResponseMessage.IsSuccessStatusCode)
{
var cust = httpResponseMessage.Content.
ReadAsAsync<IEnumerable<CustomerMobil>>().Result;
}
}
Any help is greatly appreciated.
Make sure you set the accept and or content type appropriately (possible source of 500 errors on parsing the request content):
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
content.Headers.ContentType = new MediaTypeWithQualityHeaderValue("application/json");
Then you could just do:
var errorMessage = response.Content.ReadAsStringAsync().Result;
That's all on the client of course. WebApi should handle the formatting of the content appropriately based on the accept and/or content type. Curious, you might also be able to throw new HttpResponseException("Failed to find customer.", HttpStatusCode.NotFound);
One way to get the message is to do:
((ObjectContent)httpResponseMessage.Content).Value
This will give you a dictionary that contains also the Message.
UPDATE
Refer to the official page:
http://msdn.microsoft.com/en-us/library/jj127065(v=vs.108).aspx
You have to vary the way you're reading the successful response and the error response as one is obviously in your case StreamContent, and the other should be ObjectContent.
UPDATE 2
Have you tried doing it this way ?
if (httpResponseMessage.IsSuccessStatusCode)
{
var cust = httpResponseMessage.Content.
ReadAsAsync<IEnumerable<CustomerMobil>>().Result;
}
else
{
var content = httpResponseMessage.Content as ObjectContent;
if (content != null)
{
// do something with the content
var error = content.Value;
}
else
{
Console.WriteLine("content was of type ", (httpResponseMessage.Content).GetType());
}
}
FINAL UPDATE (hopefully...)
OK, now I understand it - just try doing this instead:
httpResponseMessage.Content.ReadAsAsync<HttpError>().Result;
This is an option to get the message from the error response that avoids making an ...Async().Result() type of call.
((HttpError)((ObjectContent<HttpError>)response.Content).Value).Message
You should make sure that response.Content is of type ObjectContent<HttpError> first though.
It should be in HttpResponseMessage.ReasonPhrase. If that sounds like a bit of a strange name, it's just because that is the way it is named in the HTTP specification http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html
OK this is hilarious, but using QuickWatch I came up with this elegant solution:
(new System.Collections.Generic.Mscorlib_DictionaryDebugView(((System.Web.Http.HttpError)(((System.Net.Http.ObjectContent)(httpResponseMessage.Content)).Value)))).Items[0].Value
That is super readable!
I am using
I am logged into a remote server for accessing Visual studio as well as MS CRM. I have taken sample code from SDK and trying to run the code:
CrmAuthenticationToken token = new CrmAuthenticationToken();
token.AuthenticationType = 0;
token.OrganizationName = "AdventureWorksCycle";
CrmService service = new CrmService();
service.Url= "http://10.16.16.205:5555/mscrmservices/2007/crmservice.asmx";
service.CrmAuthenticationTokenValue = token;
service.Credentials = new System.Net.NetworkCredential"username", "password", "domain");
// Create the account object.
account account = new account();
// Set the properties of the account object.
account.name = "Fourth Coffee123";
account.address1_line1 = "29 Market St.";
account.address1_city = "Sam";
account.address1_stateorprovince = "MT1";
account.address1_postalcode = "9999";
account.donotbulkemail = new CrmBoolean();
account.donotbulkemail.Value = true;
// Create the target object for the request.
TargetCreateAccount target = new TargetCreateAccount();
// Set the properties of the target object.
target.Account = account;
// Create the request object.
CreateRequest create = new CreateRequest();
// Set the properties of the request object.
create.Target = target;
// Execute the request.
CreateResponse created = (CreateResponse)service.Execute(create);
I am using Crm Web Service for this, but Its throwing exception:
Exception Details:
System.Net.WebException: The request
failed with HTTP status 401:
Unauthorized.
Source Error:
Line 114: [return: System.Xml.Serialization.XmlElementAttribute("Response")]
Line 115: public Response Execute(Request Request) {
Line 116: ***object[] results = this.Invoke("Execute", new object[]* {**
Line 117: Request});
Line 118: return ((Response)(results[0]));
One thing you are missing is a real username and password. I am assuming that you have omitted this for the purposes of this question.
Have you checked the security role on the user that you are using for the web service call? Add this user to the System Administrator role if you haven't already.
With CRM often times, this error has nothing to do with security but something else altogether.
First turn on CRM tracing and look there. This will give you more error detail. Here's how:
http://support.microsoft.com/kb/907490
Also you can try to use my exception formatter to get more detail on the error. This is an extension class that will allow you to format the exception and print it to stdout or to the http response. Find it here:
http://paste.ly/5Y66
Use it this way:
try {
// do all your stuff
} catch (Exception ex) {
ex.Print();
}
Notice that in the formatted exception output, you can see the "Details" property deserialized such that you can see the text version. This is where CRM hides the real exception most of the time.