Closed galacticgumshoe closed 2 years ago
I believe the NPE is being thrown and not handled properly due to the APIRequest.executeInternal
call, which lines up to what I'm seeing in my logs:
protected ResponseWrapper executeInternal(Map<String, Object> extraParams) throws APIException {
// extraParams are one-time params for this call,
// so that the APIRequest can be reused later on.
ResponseWrapper response = null;
try {
context.log("========Start of API Call========");
response = executor.execute(method, getApiUrl(), getAllParams(extraParams), context);
context.log("Response:");
context.log(response.getBody());
context.log("========End of API Call========");
} catch(IOException e) {
throw new APIException.FailedRequestException(e);
}
return response;
}
As pointed out in the logs I can see the "========Start of API Call========"
getting logged, followed by the METHOD and URL (assuming coming from the executor.execute...
line), but nothing after that point. Since this function is only catching IOExceptions, it's causing NPE to pass unchecked. I'm a bit dubious about why it's initializing also the response as null here too, which could further complicate callers to this method in situations where maybe they usually get responses, but could have a network hiccup in between that could cause no response from their call and theoretically another NPE thrown.
In reading through this: https://docs.oracle.com/javase/7/docs/api/java/net/URLConnection.html and lining up the logic happening in the stack trace between these different methods:
at com.facebook.ads.sdk.APIRequest.readResponse(APIRequest.java:298)
at com.facebook.ads.sdk.APIRequest.access$100(APIRequest.java:53)
at com.facebook.ads.sdk.APIRequest$DefaultRequestExecutor.sendGet(APIRequest.java:533)
at com.facebook.ads.sdk.APIRequest$DefaultRequestExecutor.execute(APIRequest.java:517)
at com.facebook.ads.sdk.APIRequest.executeInternal(APIRequest.java:198)
I don't see where the connect()
method on the HttpsURLConnection object is being invoked. In the APIRequest.sendGet()
method, I can see the openConnection()
method is being invoked. Shortly after this is where in my logs I get the next two lines showing "Request:"
and "Get: " + url.toString()
. But then from there after decorating the HttpsURLConnection object with its method and a couple of headers, it gets passed to the APIRequest.readResponse()
method:
public ResponseWrapper sendGet(String apiUrl, Map<String, Object> allParams, APIContext context) throws APIException, IOException {
URL url = new URL(RequestHelper.constructUrlString(apiUrl, allParams));
context.log("Request:");
context.log("GET: " + url.toString());
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("User-Agent", USER_AGENT);
con.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
return readResponse(con);
}
The first step in the try block of readResponse
does a getResponseCode()
on the HttpsURLConnection
object. If the API docs are to be believed, there needs to be a connect()
call made first before we can get the headers, but I'm not seeing it here. I guess that could cause a NPE to get thrown, but in here that would be caught (since it's catching all Exceptions).
private static ResponseWrapper readResponse(HttpsURLConnection con) throws APIException, IOException {
try {
int responseCode = con.getResponseCode();
String header = convertToString(con.getHeaderFields());
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
return new ResponseWrapper(response.toString(), header);
} catch(Exception e) {
BufferedReader in = new BufferedReader(new InputStreamReader(con.getErrorStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
throw new APIException.FailedRequestException(
convertToString(con.getHeaderFields()), response.toString(), e
);
}
}
So that would mean the connection is never actually being made from this sendGet() method, always resulting in the getResponseCode() failing and forcing it to treat the call as a failure.
I can see now the NPE is not being thrown in the try block, it is actually coming from the catch block in the readResponse
method. The line in particular that's causing the NPE to get thrown is this:
BufferedReader in = new BufferedReader(new InputStreamReader(con.getErrorStream()));
Which means the NPE has swallowed up whatever the original Exception that was thrown in the try block and isn't throwing a new catchable exception since it's over before the throw new APIException.FailedRequestException...
can be thrown. So I tried commenting out everything in the catch block:
catch(Exception e) {
//BufferedReader in = new BufferedReader(new InputStreamReader(con.getErrorStream()));
//String inputLine;
//StringBuilder response = new StringBuilder();
//while ((inputLine = in.readLine()) != null) {
//response.append(inputLine);
//}
//in.close();
throw new APIException.FailedRequestException(
convertToString(con.getHeaderFields()), con.getResponseMessage(), e
);
}
That only throws a new APIException, and that successfully showed me what the real problem was:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
I fixed my certificates in my local and now it's working. So the original problem is resolved, but this brings up a new issue, which is that this catch block needs to be fixed so in situations where there is no response from the network resource (and the failure happens locally) that it properly handles an NPE. Possibly embed another try/catch block around the lines I have commented above to make sure that a secondary exception doesn't prevent the proper throw.
Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. Thank you for your contributions.
Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please feel free to create a new issue with up-to-date information.
Which SDK version are you using?
9.0.0
What's the issue?
A NullPointerException is thrown when parsing the results while invoking the Business.APIRequestGetOwnedAdAccounts() method. This is in a Spring Boot application running on JVM 1.8.
Steps/Sample code to reproduce the issue
Service class:
Controller:
Sample curl:
With debug on I was able to see the URL it had constructed and was fetching data from:
With the proper secrets (scrubbed here) I was able to fetch a valid response from the same URL via curl and browser (responded back with the first 25 Ad Accounts page of results with all the pagination details expected):
Instead of returning these results, however, an NPE is thrown from the APIRequest.readResponse call in the stack trace (which is also buggy since it's supposed to be throwing APIExceptions, not NullPointerExceptions, hence I have to catch those in my service layer since they appear to be unhandled by the SDK):
Expected Results:
This should have resulted in initializing the
result
variable as aAPINodeList<AdAccount>
object with the fully paginated list of Ad Accounts (we have 82 Ad Accounts last I checked, so 4 pagination trips) and not thrown an NPE. At a minimum, it should be throwing an APIException from the sdk layer, but is not.