I am sending API requests to a backend API using Spring in Android (Java). My question is how to receive validation errors to the error handler at ex 400 bad request response. Here is my code:
class RestTask extends AsyncTask<String,Void,ResponseEntity<ExpectedReturn>>
{
protected ResponseEntity<ExpectedReturn> doInBackground(String... uri)
{
try{
final String url = uri[0];
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(subscriber.getErrorHandler());
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
// set authentication tokens:
ResponseEntity<ExpectedReturn> response = restTemplate.exchange(url,callMethod,httpEntity, expectedReturnClass);
return response;
}catch(Exception e)
{
System.out.println(e.getMessage());
}
return null;
}
#Override
protected void onPostExecute(ResponseEntity<ExpectedReturn> result) {
if(result !=null && result.getBody() !=null)
{
subscriber.getSubscriber().onComplete(result.getBody(),result.getStatusCode());
}
}
}
My question is, if the post data fails validation (is incorrect), the API will return as JSON error object with errors, ex:
In case of a validation error, the error handler is called with a ClientHttpResponse object as a parameter. Calling the response.getBody() returns an InputStream. My question is, is there any way of receiving an object mapped from the JSON error response (as shown above) to the error handler, or perhaps converting the input stream to something readable (like a hashmap) so I can display the errors returned by the API (ex: "Name is required" etc...)?
I've tested your code and in case of a 400 bad request the catch block receives an instance of HttpClientErrorException which has a method to get the error body as String:
private class HttpRequestTask extends AsyncTask<Void, Void, String> {
#Override
protected String doInBackground(Void... params) {
try {
final String url = "https://reqres.in/api/login";
RestTemplate restTemplate = new RestTemplate();
//Same result with restTemplate.exchange() too
return restTemplate.postForObject(url, "{\n" +
" \"email\": \"peter#klaven\"\n" +
"}", String.class);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
if (e instanceof HttpClientErrorException) {
String responseBodyAsString = ((HttpClientErrorException) e).getResponseBodyAsString();
Log.e(TAG, "Validation error" + responseBodyAsString);
//You can parse this with gson or jackson here
return responseBodyAsString;
}
}
return null;
}
#Override
protected void onPostExecute(String result) {
Log.d(TAG, "onPostExecute() called with: result = [" + result + "]");
}
}
Which prints in:
W/RestTemplate: POST request for "https://reqres.in/api/login" resulted in
400 (Bad Request); invoking error handler
E/MainActivity: 400 Bad Request
E/MainActivity: Validation error{"error":"Missing email or username"}
D/MainActivity: onPostExecute() called with: result = [{"error":"Missing email or username"}]
If you want to use the none default error handler and set your custom error handler you can get the error message as string this way:
restTemplate.setErrorHandler(new ResponseErrorHandler() {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return response.getStatusCode().is4xxClientError();
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
String errorResponse = new String(getResponseBody(response), getCharset(response).name());
Log.e(TAG, "handleError: called with: " + errorResponse);
}
});
private byte[] getResponseBody(ClientHttpResponse response) {
try {
InputStream responseBody = response.getBody();
if (responseBody != null) {
return FileCopyUtils.copyToByteArray(responseBody);
}
} catch (IOException ex) {
// ignore
}
return new byte[0];
}
private Charset getCharset(ClientHttpResponse response) {
HttpHeaders headers = response.getHeaders();
MediaType contentType = headers.getContentType();
return contentType != null ? contentType.getCharSet() : Charset.defaultCharset();
}
Then you can use Jackson or Gson to parse the error response as below:
new Gson().fromJson(responseBodyAsString, ExpectedResponse.class);
Note I've just did the same thing as implemented in DefaultResponseErrorHandler
Edit:
The whole AsyncTask and Spring Android APIs are so outdated, Here is the same example with Retrofit:
api.login(new BodyModel("peter#klaven"))
.enqueue(new Callback<ExpectedModel>() {
#Override
public void onResponse(#NonNull Call<ExpectedModel> call, #NonNull Response<ExpectedModel> response) {
if (response.isSuccessful()) {
//Do what you got to do
} else {
Converter<ResponseBody, ErrorModel> converter = MainActivity.this.retrofit.responseBodyConverter(ErrorModel.class, new Annotation[0]);
ErrorModel errorModel = null;
try {
errorModel = converter.convert(response.errorBody());
Toast.makeText(MainActivity.this, errorModel.toString(), Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(#NonNull Call<ExpectedModel> call, #NonNull Throwable t) {
t.printStackTrace();
}
})
You can find the full gist in my github repo
Related
I have an Android application with a NodejS-Backend. The backend provides an private API-endpoint, which I have protected with Auth0.
This is my NodeJS-Code:
app.get('/api/private', jwtCheck, function(req, res) {
res.json({
message: 'Hello World from private API-Endpoint!'
});
});
To call my private API from the Android application I used the code-samples from Auth0.
To print my message from the endpoint I wanna use this function:
private void print_private_api_message() {
RequestQueue queue = Volley.newRequestQueue(MainActivity.this);
String url = "http://10.0.2.2:3000/api/private";
JsonArrayRequest request = new JsonArrayRequest(com.android.volley.Request.Method.GET, url, null, new com.android.volley.Response.Listener<JSONArray>() {
public void onResponse(JSONArray response) {
try {
JSONObject message_private = response.getJSONObject(0);
String message_from_backend = message_private.getString("message");
Toast.makeText(MainActivity.this, message_from_backend, Toast.LENGTH_SHORT).show();
} catch (JSONException e) {
e.printStackTrace();
}
}
}, new com.android.volley.Response.ErrorListener() {
public void onErrorResponse(VolleyError error) {
error.printStackTrace();
}
});
queue.add(request);
}
I call this function here:
public void onResponse(#NotNull Call call, #NotNull final Response response) {
runOnUiThread(new Runnable() {
#Override
public void run() {
if (response.isSuccessful()) {
Toast.makeText(MainActivity.this, "API call success!", Toast.LENGTH_SHORT).show();
print_private_api_message();
} else {
Toast.makeText(MainActivity.this, "API call failed.", Toast.LENGTH_SHORT).show();
}
}
});
}
When I'm logged in with my Auth0-account and call the API I get the Toast-Message: API call success and when I call print_private_api_message() my NodeJS-console logged UnauthorizedError: No authorization token was found.
So, my question is: How can I use the accessToken and print the message from my protected API-endpoint?
You need to send the authorization header. You can see how in the following question: How to send Authorization header in Android using Volley library?
The part are you need to add is the generation header:
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headerMap = new HashMap<String, String>();
headerMap.put("Content-Type", "application/json");
headerMap.put("Authorization", "Bearer " + ACCESS_TOKEN);
return headerMap;
}
I am not able to cover below HttpClientErrorException catch block in Junit code coverage. How can I do this?
code
#Autowired
private ExceptionHandlerService errorHandler;
public CartResponse callUpdateCart(AddToCartRequest request) {
String url = Utils.formatHttpUrl(cartbaseUrl, CART_UPDATE_CART);
try {
HttpEntity<?> entity = new HttpEntity<>(request);
JsonNode jsonNode = restTemplate.postForObject(url, entity, JsonNode.class);
if (jsonNode.has(Constants.CONTENT) && !jsonNode.path(Constants.CONTENT).path(Constants.PAYLOAD).isMissingNode()) {
jsonNode = jsonNode.path(Constants.PAYLOAD).get(Constants.PAYLOAD);
} else {
errorHandler.error(ErrorMessages.EPO_VALIDATEQ_ERROR_08, jsonNode);
}
return JsonService.getObjectFromJson(jsonNode, CartResponse.class);
} catch (HttpClientErrorException e) {
errorHandler.error(ErrorMessages.EPO_VALIDATEQ_ERROR_08, e.getResponseBodyAsString());
return null;
} catch (HttpServerErrorException e) {
throw new ServiceException(ErrorMessages.EPO_SYSTEM_ERROR, e.getMessage(), url);
}
}
ExceptionHandlerService
#Override
public void error(ResolvableErrorEnum error, String responseBody) {
JsonNode response = JsonService.getObjectFromJson(responseBody, JsonNode.class);
if (null != response && null != response.get(Constants.ERROR)) {
ServiceError serviceError = JsonService.getObjectFromJsonNode(response.get(Constants.ERROR), ServiceError.class);
error(error, serviceError.getErrorId(), serviceError.getMessage());
}
throw new ServiceException(error);
}
junit
#Test(expected = ServiceException.class)
public void test_callUpdateCart_Exception() throws IOException {
AddToCartRequest req = createAddToCartRequest();
String responseBodyStr = "{\"error\":{\"errorId\":\"Service-I-1003\",\"message\":\"Error returned from downstream system.\",\"traceCode\":\"CART;400\",\"details\":[{\"code\":\"400\",\"message\":\"400 Bad Request\"},{\"code\":\"DTV_CAT_ERR_002\",\"message\":\"Error in getting response from catalog.\",\"traceCode\":\"CART;400\"}]}}\r\n";
byte[] body = responseBodyStr.getBytes(StandardCharsets.UTF_8);
HttpClientErrorException e = new HttpClientErrorException(HttpStatus.BAD_REQUEST, "BAD REQUEST", body,
StandardCharsets.UTF_8);
when(restTemplate.postForObject(Mockito.anyString(), Mockito.<HttpEntity<?>>any(), Mockito.eq(JsonNode.class)))
.thenThrow(e);
client.callUpdateCart(req);
}
error
It seems like the mockito.when() is not working properly, since it looks like it is not throwing the exception you are asking it to throw. I had similar issues and tinkering with mockito matchers usually fixes them.
You can check this page for a bit more information regarding "expanding" or restricting your argument matchers.
I think the solution bellow will work:
when(restTemplate.postForObject(Mockito.anyString(), Mockito.any(HttpEntity.class), Mockito.any(JsonNode.class)))
.thenThrow(e);
I have this bit of code:
public static void main(String[] args) throws UnirestException {
ArrayList<Stock> listStock
= getAllAvailableStocks("https://api.iextrading.com/1.0/ref-data/symbols");
//doing more actions after the one before, using the data from the listStock etc.
}
private static ArrayList<Stock> getAllAvailableStocks(String url) {
ArrayList<Stock> stocks = new ArrayList<Stock>();
Future<HttpResponse<JsonNode>> future = Unirest.get(url)
.header("accept", "application/json")
.asJsonAsync(new Callback<JsonNode>() {
public void failed(UnirestException e) {
System.out.println("The request has failed");
}
public void completed(HttpResponse<JsonNode> response) {
ObjectMapper objectMapper = new ObjectMapper();
try {
listStock = objectMapper.readValue(response.getRawBody(), new TypeReference<List<Stock>>(){});
} catch (Exception e) {
System.out.println("all is fucked");
}
return listStock;
}
public void cancelled() {
System.out.println("The request has been cancelled");
}
});
}
I am a newbie in java, i want to do the following:
1) I want to do async call to get and extract a list of stocks, only after the request completed i want to do the next things in the main method.
2) How do i extract data from the method i built so i can use the data outside of the method?
3) If i need to do the following:
getAllAvailableStocks("https://api.iextrading.com/1.0/ref-data/symbols",new Callback<JsonNode>() {
public void failed(UnirestException e) {
System.out.println("The request has failed");
}
public void completed(HttpResponse<JsonNode> response) {
ArrayList<Stock> listStock = new ArrayList<Stock>();
ObjectMapper objectMapper = new ObjectMapper();
int code = response.getStatus();
System.out.println(code);
try {
listStock = objectMapper.readValue(response.getRawBody(), new TypeReference<List<Stock>>(){});
} catch (Exception e) {
}
System.out.println(listStock);
}
public void cancelled() {
System.out.println("The request has been cancelled");
}
});
}
private static Future<HttpResponse<JsonNode>> getAllAvailableStocks(String url,Callback<JsonNode> cb) {
return Unirest.get(url)
.header("accept", "application/json")
.asJsonAsync(cb);
}
Or something of that sort, it makes the code horrible, and when i want to do much more async requests after, i have a callback hell here, is there any way to avoid it? what are my options here?
I think your are mixing up asnychronous and synchronous.
If you
want to do async call to get and extract a list of stocks, only after the request completed I want to do the next things in the main method
then you actually want to perform a synchronous call.
An asynchronous call would be to perform the request, then doing other things (not related to the request) and at some point in the future you get the result of the request and handle it.
To perform a synchronous call, which is probably what you want, try to adapt your code like this:
private static ArrayList<Stock> getAllAvailableStocks(String url) {
ArrayList<Stock> stocks = new ArrayList<Stock>();
Future<HttpResponse<JsonNode>> future = Unirest.get(url)
.header("accept", "application/json")
.asJsonAsync(new Callback<JsonNode>() {
public void failed(UnirestException e) {
System.out.println("The request has failed");
}
public void completed(HttpResponse<JsonNode> response) {
System.out.println("The request succeeded");
}
public void cancelled() {
System.out.println("The request has been cancelled");
}
});
HttpResponse<JsonNode> response = future.get(); // NOTE: This call is blocking until the request is finished
if (response != null && response.getStatus() == 200) {
JsonNode body = response.getBody();
// TODO Parse body and add items to `stocks`
}
return stocks;
}
This method can be used like this:
ArrayList<Stock> stocks = getAllAvailableStocks(...);
stocks.forEach(x -> System.out.println(x));
Edit
If you want to handle the result asynchronously without providing callbacks, you could use a CompletableFuture. Consider the following snippet as a starting point which does not handle unsuccessful calls.
private static CompletableFuture<ArrayList<Stock>> getAllAvailableStocks(String url) {
CompletableFuture<ArrayList<Stock>> result = new CompletableFuture<>();
Future<HttpResponse<JsonNode>> future = Unirest.get(url)
.header("accept", "application/json")
.asJsonAsync(new Callback<JsonNode>() {
public void failed(UnirestException e) {
System.out.println("The request has failed");
}
public void completed(HttpResponse<JsonNode> response) {
System.out.println("The request succeeded");
ArrayList<Stock> stocks = new ArrayList<Stock>();
if (response != null && response.getStatus() == 200) {
JsonNode body = response.getBody();
// TODO Parse body and add items to `stocks`
}
result.complete(stocks);
}
public void cancelled() {
System.out.println("The request has been cancelled");
}
});
return result;
}
The method can be used as follows:
CompletableFuture<ArrayList<Stock>> stocksFuture = getAllAvailableStocks(...);
stocksFuture.thenAccept((stocks) -> {
// NOTE: This will be called after and only if the request succeeded
stocks.forEach(x -> System.out.println(x));
});
System.out.println("This is probably executed before the above request finished.");
Thread.sleep(10000); // If you are calling from your `main` method: Prevent JVM exit
I am trying to send a POST request with a JSON BODY in CodeName one.
It reaches the server with an empty Json String.
Here is the code that makes the connection and sends the message:
JSONObject json = new JSONObject();
class MyConnection extends ConnectionRequest {
public Map<String, Object> results;
#Override
protected void readResponse(InputStream input) throws IOException {
JSONParser jp = new JSONParser();
results = jp.parseJSON(new InputStreamReader(input, "UTF-8"));
}
#Override
protected void handleErrorResponseCode(int code, String message) {
showError("The server returned the error code: " + code);
}
#Override
protected void handleException(Exception err) {
showError("There was a connection error: " + err);
}
#Override
protected void postResponse() {
try {
json.put("AAA", "AAA");
json.put("BBB", "BBB");
json.put("CCC", "CCC");
} catch (JSONException ex) {
ex.printStackTrace();
}
}
#Override
protected void buildRequestBody(OutputStream os) throws IOException {
os.write(json.toString().getBytes("UTF-8"));
}
}
---
MyConnection connec = new MyConnection ();
connec.setUrl("http://testServoce/addUser");
connec.setPost(true);
connec.setContentType("application/json");
InfiniteProgress prog = new InfiniteProgress();
Dialog dlg = prog.showInifiniteBlocking();
connec.setDisposeOnCompletion(dlg);
NetworkManager.getInstance().addToQueueAndWait(connec);
I'm not sure what your intention was but it looks like you misunderstood the goal of the postResponse method. It's unrelated to the POST web method and is just called after the response completed on the EDT. So changing the JSON value there is irrelevant.
Also it looks like you are using two separate JSON parsers for some reason. The builtin one and the org.json one from one of the cn1libs.
Since I got some bad reviews I am rewriting this question...
I have an HTTP REST server and a client (Android app). I have programmed several APIs that work just fine, however there is one that is giving me a 400 error, and if I put a breakpoint in the server, it does not even triggers it. So, I would like to understand why it fails :( ...
It is very simple, I have a value object called Alarm with a few attributes, that I want to POST to the server for registration of object in the database.
This is the output:
Callback failure for call to http://10.0.0.3:8080/...
java.io.IOException: Unexpected code Response{protocol=http/1.1, code=400, message=, url=http://10.0.0.3:8080/BiTrack_API/api/assets/registerAlarm}
at it.bitrack.fabio.bitrack.AlarmView$2$1.onResponse(AlarmView.java:438)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:135)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
This is my client side Android button listener:
View.OnClickListener addAlarmAction = new View.OnClickListener() {
#Override
public void onClick(View v) {
try {
alarm.setThreshold(Float.parseFloat(thresholdEditText.getText().toString()));
String alarmJson = j.makeJsonBodyForAlarmRegistration(alarm);
tagLinearLayout.setVisibility(view.GONE);
operatorLinearLayout.setVisibility(view.GONE);
thresholdLinearLayout.setVisibility(view.GONE);
assetSpinner.setSelection(0);
r.attemptAddNewAlarm(alarmJson,
new Callback() {
#Override public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
#Override public void onResponse(Call call, Response response) throws IOException {
try (final ResponseBody responseBody = response.body()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
final String responseBodyString = responseBody.string();
final int resultCode = response.code();
try {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
Log.i("BiTrack", "attemptAddNewAlarm RESULT: " + resultCode);
executeAlarmRegistration(resultCode);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
};
This is the code where I generate the Json body for the POST in the client:
public String makeJsonBodyForAlarmRegistration (Alarm alarm) {
Gson gson = new Gson();
String jsonAlarm = gson.toJson(alarm);
return jsonAlarm;
}
This is the actual POST code in the client (Android) side:
public void attemptAddNewAlarm(String json, Callback callback) throws Exception {
final OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(WEB_SERVER + "BiTrack_API/api/assets/registerAlarm")
.post(body)
.build();
client.newCall(request).enqueue(callback);
}
This is my server side code:
#POST
#Path("/registerAlarm")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response registerAlarm(Alarm alarm) {
System.out.println("Received API Call: registerAlarm for alarm tagId: " + alarm.getTagId() + " operatorId: " + alarm.getOperatorId() + " treshold: " + alarm.getThreshold());
DataProcessor dp = new DataProcessor();
AssetUpdateDAO aDAO = new AssetUpdateDAO();
ArrayList<Alarm> customerAlarms = aDAO.getUserAlarmsForAsset(alarm.getUserId(), alarm.getAssetId());
if (dp.isNewAlarmDuplicate(customerAlarms, alarm)) {
return Response.status(480).build(); // duplicated error
} else {
int response = aDAO.insertAssetUserAlarm(alarm.getUserId(), alarm.getAssetId(), alarm.getTagId(), alarm.getOperatorId(), alarm.getThreshold());
if (response == -5) {
return Response.status(484).build(); // something went wrong while inserting alarm into db
} else {
return Response.status(200).build();
}
}
}
This is my Alarm value object (identical class in client and server):
public class Alarm {
public Alarm() {
}
protected int id;
protected int userId;
protected int assetId;
protected int tagId;
protected int operatorId;
protected float threshold;
protected String networkAssetCode;
public String getNetworkAssetCode() {
return networkAssetCode;
}
public void setNetworkAssetCode(String networkAssetCode) {
this.networkAssetCode = networkAssetCode;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getAssetId() {
return assetId;
}
public void setAssetId(int assetId) {
this.assetId = assetId;
}
public int getTagId() {
return tagId;
}
public void setTagId(int tagId) {
this.tagId = tagId;
}
public int getOperatorId() {
return operatorId;
}
public void setOperatorId(int operatorId) {
this.operatorId = operatorId;
}
public float getThreshold() {
return threshold;
}
public void setThreshold(float threshold) {
this.threshold = threshold;
}
}
I really appreciate any help...
In order to help you, the endpoint code is required. Now it is even unclear what technology stack is used for your API.
But from the information that is present... The endpoint considers your json as invalid.
400 Bad Request
The request could not be understood by the server due to malformed
syntax. The client SHOULD NOT repeat the request without
modifications.
In jax-rs the payload is first deserialized before it reaches the method that is bound to the url en http method.
Possibly the deserializing is failing and it never reaches the breakpoint you set.
What would be interesting is the following:
logs or exception from the server. The client exception is not that helpful, since the server returns this response.
the actual (json) payload that is send over the wire.
what deserialization mechanism is used at the server end? Reflection or did you make your own deserializer?
I found the issue! After 48 hours looking for the impossible, discovered that I had done a small update at the object attribute at the server side that had not been replicated in the client side...