I'm writing my first JAVA servlet and I have a question.
May be it's important to say that my servlet will be called from Google Web Toolkit (AJAX)
First thing that I do is to create PrintWriter and start to write in it my JSON output
PrintWriter out = response.getWriter();
...
out.println("[");
out.println(" {");
out.println(" \"validation\" : {");
...
but what will happened if meanwhile I get an error condition?
What is the right way to return an error to client? (AJAX client)
Do I have to buffer my output (HOW?) and return error as JSON (instead of output)
or
I have to throw ServletException?
Just build the string in memory using for example StringBuilder. Do not write any character to the response yet until you're finished building the string. That's "buffering".
StringBuilder builder= new StringBuilder();
builder.append("[");
builder.append(" {");
builder.append(" \"validation\" : {");
// ...
// When finished:
response.getWriter().write(builder.toString());
When something fails in meanwhile, either throw ServletException (which will end up in a server default error page with status code 500), or use HttpServletResponse#sendError() to send a more specific error status. But generally, status code 500 is enough sign for XMLHttpRequest client that something failed at the server side.
try {
// Build and write JSON.
} catch (Exception e) {
throw new ServletException(e);
}
As #McDowell says, the correct way to deal with an error during request handling in a servlet is to set an HTTP status code in the response object.
But there is a catch.
The HTTP status code is actually passed in the first line of the HTTP response. And that gets written when the response is "committed", which typically happens when you call response.getOutputStream() or response.getWriter(). After that, you cannot change the status code.
The way to deal with it is to do one of the following:
Code your application so that errors don't happen during the generation of the response body.
Generate the response body to a buffer of some kind, and only open the response output stream / reader when you've built it completely. If there are errors during the body generation, you can set the HTTP status code and (if appropriate) send an alternative body containing an error message.
See the HTTP status codes. You can use HttpServletResponse.setStatus to set the response state (note also the constants defined by that class).
Related
I'm trying to send a startOptimization request to https://api.myptv.com/routeoptimization. I generated my java client as described in the tutorial: https://developer.myptv.com/Tutorials/General/clientGeneration.htm
Creating a plan works but when I want to optimize it I get the following error:
HTTP Error 411. The request must be chunked or have a content length.
I noticed that this error is also returned when I send a startEvaluation request. All other requests of the route optimization api and all other APIs seems to work.
Unfortunately this is a bug in the API Managment of Microsoft. We created a ticket for Microsoft but we don't know when it is fixed.
The problem is that the API Managment rejects POST requests with an empty body where no Content-Length header of 0 is passed. The java client does not pass this header for an empty body, therefore either the empty body must be avoided or the header must be added until the bug is fixed.
There are two possible solution to solve this problem:
Just pass a dummy body which is ignored anyway. For this you have to adapt the java client after generation:
replace all occurencies of
localVarRequestBuilder.method("POST", HttpRequest.BodyPublishers.noBody());
with
try {
byte[] localVarPostBody = memberVarObjectMapper.writeValueAsBytes("dummybody");
localVarRequestBuilder.method("POST", HttpRequest.BodyPublishers.ofByteArray(localVarPostBody));
} catch (IOException e) {
throw new ApiException(e);
}
Explicitly send a Content-Length header of 0 by adding
System.setProperty("jdk.httpclient.allowRestrictedHeaders", "Content-Length");
to a setup method of your main and adding
localVarRequestBuilder.header("Content-Length", "0");
to the startEvaluationRequestBuilder and startOptimizationRequestBuilder methods.
I use httpResp.sendError(400, "You are missing customer id") to send the response back
When I try to retrieve the message on the client side (using Rest template to call the endpoint).
However, printing the HttpClientErrorException always produces the following result for me:
HttpClientErrorException: 400 null
I see that I have HttpClientErrorException.getResponseBody has all the information about time stamp, message etc. But HttpClientErrorException.getStatusText is always empty.
My question is : How do you design your ResponseEntity on the server-side such that the HTTP client finds the server-side exception message in response.getStatusText() instead of null?
here is the code I have
try{
ResponseEntity<String> responseEntity = restTemplate.exchange(uri, HttpMethod.POST, requestEntity, String.class );
System.out.println(responseEntity.getBody());
}
catch (HttpClientErrorException | HttpServerErrorException e) {
if (e.getStatusCode().equals(HttpStatus.UNAUTHORIZED) || e.getStatusCode().equals(HttpStatus.FORBIDDEN)) { System.out.println("Response Body returned:");
System.out.println(e.getResponseBodyAsString());
System.out.println("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
System.out.println("Status text is:");
System.out.println(e.getStatusText());
} else if (e.getStatusCode().equals(HttpStatus.BAD_REQUEST)) {
System.out.println("Response Body returned:");
System.out.println(e.getResponseBodyAsString());
System.out.println("-------------------------------");
System.out.println("Status text is:");
System.out.println(e.getStatusText());
} else {
throw e;
}
}
Sprint Boot Version: 2.1.0.RELEASE
I traced the code for how RestTemplate actually makes the calls. Basically what happens is the result of HttpClientErrorException.getStatusText() is populated by the HTTP status code's text and not your custom error message. For example, instead of just returning back error code 400, a server might return back error 400 Bad Request. Instead of status code 200, a server might return back 200 OK. If the server responds back with that optional text, that's what you'll see when you call getStatusText(). Note that that text like OK and Bad Request can't be customized by you on the server side.
This is happening because, internally, Spring is making use of SimpleClientHttpResponse.getStatusText() which is internally relying on HttpUrlConnection.getResponseMessage(). As you can see from getResponseMessage's Javadoc, the possible values returned aren't meant to be custom error messages. Note that in your case, getStatusText() is returning null because your server is just sending back a status line like 400 and not 400 Bad Request. You can also see this from the Javadoc. You can probably configure your server to send back status code text messages, but doing so still won't help you use the getStatusText() method to get your custom error message.
Consequently, the HttpClientErrorException.getStatusText() just isn't what you need. Instead, you need to continue calling getResponseBodyAsString(). However, if you know the format of the response body that is sent back from the server (since this will likely be wrapped in HTML and other stuff) you can use a regex to filter out the non-useful parts of the response message.
I am referring to this project on Github. So this is supposed to be a RESTful API for managing a movie rental service. I mean it technically "works" right now, but one of the things it will do is deliver the error messages directly to the client from the internal methods.
Take this code for example:
/*
GET films
*/
get("/films", (req, res) -> {
try {
String json_output = get_film_list();
return json_output;
} catch (Exception e) {
return "{\"error\":\"There was an error: " + e.toString().replace("\"","") + "\"}";
}
});
And we have the get_film_list() method:
public static String get_film_list() throws SQLException, URISyntaxException{
Connection connection = ConnectionPool.getDBCP2Connection();
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM films");
String output = "{\"films\":[";
int got_result = 0;
while (rs.next()) {
output += "{\"id\":\""+rs.getInt(1)+"\",\"name\":\""+rs.getString(2)+"\",\"type\":\""+rs.getInt(3)+"\"},";
got_result = 1;
}
rs.close();
stmt.close();
output = output.substring(0, output.length()-1) + "]}";
if (got_result == 1){
return output;
}else{
throw new SQLException("No films.");
}
}
So the errors are delivered to the user via the API. I found this pretty convenient for development. I knew that if the server response contains the error property then there was an error, and it supplies the error message. I have heard through code review that this is not the way to do this at all. I also suspect that perhaps you're supposed to give formal HTTP errors or something. But at the same time I figured I would want my api to always return nice JSON formatted strings.
When a user hasn't authenticated, they will see this:
{"error":"Please Authenticate."}
I created an error in the DB connection class, and the user would see this:
{"error":"There was an error: java.sql.SQLException: Cannot load JDBC
driver class 'org.postgresql.Drive'"}
So, my question comes down to, what is the proper way to return error messages to the users with a RESTful API. One of this sort which uses returns JSON data.
Thanks!
RESTful services are based on 2 things, the response code and the actual response itself.
Well, basically it boils down to what error you want to handle. This particular scenario means no data being found and you would find different ways of handling this scenario. Any other error conditions would be handled differently
The 2 approaches to handling this error are
Scenario 1:
Response Code: 200 OK
Response: {}
which means that there was no data for the request specified(more so the parameters supplied with the request)
Scenario 2:
Response Code: 404 Not Found
Response: {"error":"Error Message"}
but this could potentially be confusing to indicate that the service was not found. But this depends on how you've defined your RESTful API.
From what I understand, the above scenario is a mix of both, where it sends out a 200 OK, but at the same time an error message too which is not the way to do it.
Its best to read through the rules of REST and then come up with your API.
Also it might be worth documenting your API through SWAGGER or RAML which makes it meaningful to someone using the service without going through tons of code to understand it.
Since you're using http you should use the http status codes properly, for example the SQL exception would probably result in a response code of 500 Internal Server Error, but you shouldn't expose the actual stack trace or exception at least for two reasons
The api-user has no use of that error message, he can't act upon it or take any reasonable actions to fix it.
You're exposing the applications internals, this could provide someone with malicious intent with valuable information.
When it comes to actually displaying an error. Hopefully something that the user can have some sort of use of. You can pretty much do it in any manner you feel fits your api. The important thing is that the api is consistent.
I'd say that the body of the response you're giving now is okay, except for the fact that the actual message probably doesn't mean anything to the intended user when you just call toString() on an Exception, that information is intended for the developers and should probably be logged.
What you need to do is, translate the exceptions to usable error messages and use http status codes.
When creating a REST API on the top of the HTTP protocol, the HTTP status codes are the proper way to indicate the result of the operation. Along with the status code, you can return a message with more details about the error.
Find below the most common status codes of errors for situations when the client seems to have erred:
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
409 Conflict
422 Unprocessable Entity
Don't define your own status codes. Stick to the standards.
When returning a message, your HTTP response can be like:
HTTP/1.1 404 Not Found
Content-Type: application/json
{ "error" : "Resource not found with the requested identifier" }
I am writing a REST application using Tomcat and Spring WebMVC.
I want to signal errors to my client using HTTP status codes along with some XML payload that contains more information about what went wrong.
To catch all errors regardless of where they occur, I have written a Filter which wraps the response and overrides the sendError() method:
private static final class GenericErrorResponseWrapper
extends HttpServletResponseWrapper
{
#Override
public void sendError(int sc, String msg) throws IOException {
final HttpServletResponse wrappedResponse = (HttpServletResponse) getResponse();
wrappedResponse.setStatus(sc, msg);
wrappedResponse.setContentType("application/xml");
PrintWriter writer = wrappedResponse.getWriter();
try {
SimpleXmlWriter xmlWriter = SimpleXmlWriterWrapper.newInstance(writer);
xmlWriter.writeStartElement("ns2", "genericError")
.writeAttribute("xmlns:ns2", "http://mynamespace")
.writeCharacters(msg)
.writeEndDocument().flush();
writer.flush();
wrappedResponse.flushBuffer();
} finally {
writer.close();
}
}
}
This implementation has two problems:
It generates a deprecation warning in Eclipse, since HttpServletResponse.setStatus(sc, msg) is deprecated.
The HTTP response header generated by Tomcat is not correct, it starts with the first line "HTTP/1.1 500 OK". 500 is correct, but instead of OK the reason phrase should be "Internal Server Error".
How can I implement my filter so that it does the right thing and is free of deprecation warnings? Both alternatives named in the Javadoc are not usable for me:
sendError(sc, msg) is not usable, since it commits the response body and I can't write XML payload any more
setStatus(sc) with just the error code is theoretically usable, but it also creates the hardcoded "OK" string in the first line of the response header.
There is unfortunately no way to avoid the deprecation warning. As you already mention yourself, the two alternatives which are referred to in the API documentation do not cover the same functionality. You may of course annotate your method with #SuppressWarnings("deprecation") to indicate that the usage of the deprecated method is intended.
The other thing, that Tomcat does not use your message string, even if one is provided, is a configuration issue. For some strange reason, Tomcat will by default ignore the provided message string and use a default error message based on the passed return code. You must set the system property org.apache.coyote.USE_CUSTOM_STATUS_MSG_IN_HEADER to true to force Tomcat to use your provided error message instead. More details on this can be found in the Tomcat documentation.
As an alternative answer - you could first write the XML payload, without calling flush/flushBuffer, and only after that do sendError(int, String), which would flush the buffer.
I'm an experienced Java programmer but a newbie web developer. I'm trying to put together a simple web service using the HttpServer class that ships with JDK 1.6. From the examples I've viewed, some typical code from an HttpHandler's handle method would look something like this:
Headers responseHeaders = exchange.getResponseHeaders();
responseHeaders.set("Content-Type", "text/plain");
exchange.sendResponseHeaders(200, 0);
OutputStream responseBody = exchange.getResponseBody();
responseBody.write(createMyResponseAsBytes());
responseBody.close();
My question: What happens if I send a response header to indicate success (i.e. response code 200) and perhaps begin to stream back data and then encounter an exception, which would necessitate sending an "internal server error" response code along with some error content? In other words, what action should I take given that I've already sent a partial "success" response back to the client at the point where I encounter the exception?
200 is not sent until you either flush the stream or close it.
But once it is sent, there is nothing you can do about it.
Usually it may happen only when you have a really large amount of data and you use chunking.