Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## Next Release

- Removes the legacy Google App Engine `urlfetch` fallback; all requests now use `HttpsURLConnection`

## v8.11.0 (2026-11-09)

- Adds `deliveryEvidence` property and `DeliveryEvidence` model to `Tracker` object
Expand Down
108 changes: 2 additions & 106 deletions src/main/java/com/easypost/http/Requestor.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import com.easypost.service.EasyPostClient;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import lombok.Generated;

import javax.net.ssl.HttpsURLConnection;
import java.io.ByteArrayOutputStream;
Expand All @@ -37,13 +36,10 @@
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.net.URLStreamHandler;
import java.nio.charset.Charset;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -196,7 +192,7 @@
try {
output = conn.getOutputStream();
String jsonString = body.toString();
output.write(jsonString.getBytes(Constants.Http.CHARSET));

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (19)

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (12)

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (21)

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (23)

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (11)

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (18)

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (9)

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (15)

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / coverage

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (22)

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (8)

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (24)

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (25)

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (13)

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (20)

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (17)

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (14)

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (10)

[StringCharset] StringCharset

Check warning on line 195 in src/main/java/com/easypost/http/Requestor.java

View workflow job for this annotation

GitHub Actions / build (16)

[StringCharset] StringCharset
} finally {
if (output != null) {
output.close();
Expand Down Expand Up @@ -649,19 +645,8 @@

client.getRequestHooks().executeEventHandler(requestResponse);

EasyPostResponse response;
try {
// HTTPSURLConnection verifies SSL cert by default
response = makeURLConnectionRequest(method, url, query, body, client);
} catch (ClassCastException ce) {
// appengine
String appEngineEnv = System.getProperty("com.google.appengine.runtime.environment", null);
if (appEngineEnv != null) {
response = makeAppEngineRequest(method, url, query, body, client);
} else {
throw ce;
}
}
// HTTPSURLConnection verifies SSL cert by default
EasyPostResponse response = makeURLConnectionRequest(method, url, query, body, client);
int rCode = response.getResponseCode();
String rBody = response.getResponseBody();

Expand Down Expand Up @@ -746,93 +731,4 @@
throw new UnknownApiError(errorMessage, errorCode, errors, rCode);
}
}

/**
* Make a request using Google's App Engine library.
*
* @param method The HTTP method.
* @param url The URL to make the request to.
* @param query The query string.
* @param body The request body.
* @param client The EasyPost client.
* @return The response from the API.
* @throws HttpError when the request process fails.
*/
@Generated // Exclude from the jacoco test coverage
private static EasyPostResponse makeAppEngineRequest(final RequestMethod method, String url, final String query,
final JsonObject body, final EasyPostClient client)
throws HttpError {
String unknownErrorMessage = String.format(
"Sorry, an unknown error occurred while trying to use the Google App Engine runtime." +
"Please email %s for assistance.",
Constants.EASYPOST_SUPPORT_EMAIL);
try {
if ((method == RequestMethod.GET || method == RequestMethod.DELETE) && query != null) {
url = String.format("%s?%s", url, query);
}
URL fetchURL = new URL(url);

Class<?> requestMethodClass = Class.forName("com.google.appengine.api.urlfetch.HTTPMethod");
Object httpMethod = requestMethodClass.getDeclaredField(method.name()).get(null);

Class<?> fetchOptionsBuilderClass = Class.forName("com.google.appengine.api.urlfetch.FetchOptions$Builder");
Object fetchOptions = null;
try {
fetchOptions = fetchOptionsBuilderClass.getDeclaredMethod("validateCertificate").invoke(null);
} catch (NoSuchMethodException e) {
System.err.printf(
"Warning: this App Engine SDK version does not allow verification of SSL certificates;" +
"this exposes you to a MITM attack. Please upgrade your App Engine SDK to >=1.5.0. " +
"If you have questions, email %s.%n",
Constants.EASYPOST_SUPPORT_EMAIL);
fetchOptions = fetchOptionsBuilderClass.getDeclaredMethod("withDefaults").invoke(null);
}

Class<?> fetchOptionsClass = Class.forName("com.google.appengine.api.urlfetch.FetchOptions");

// Heroku times out after 30s, so leave some time for the API to return a
// response
fetchOptionsClass.getDeclaredMethod("setDeadline", java.lang.Double.class)
.invoke(fetchOptions, client.getConnectionTimeoutMilliseconds());

Class<?> requestClass = Class.forName("com.google.appengine.api.urlfetch.HTTPRequest");

Object request = requestClass.getDeclaredConstructor(URL.class, requestMethodClass, fetchOptionsClass)
.newInstance(fetchURL, httpMethod, fetchOptions);

if ((method == RequestMethod.POST || method == RequestMethod.PUT) && body != null) {
String bodyString = body.toString();
requestClass.getDeclaredMethod("setPayload", byte[].class)
.invoke(request, bodyString.getBytes(Charset.defaultCharset()));
}

for (Map.Entry<String, String> header : generateHeaders(client.getApiKey()).entrySet()) {
Class<?> httpHeaderClass = Class.forName("com.google.appengine.api.urlfetch.HTTPHeader");
Object reqHeader = httpHeaderClass.getDeclaredConstructor(String.class, String.class)
.newInstance(header.getKey(), header.getValue());
requestClass.getDeclaredMethod("setHeader", httpHeaderClass).invoke(request, reqHeader);
}

Class<?> urlFetchFactoryClass = Class.forName("com.google.appengine.api.urlfetch.URLFetchServiceFactory");
Object urlFetchService = urlFetchFactoryClass.getDeclaredMethod("getURLFetchService").invoke(null);

Method fetchMethod = urlFetchService.getClass().getDeclaredMethod("fetch", requestClass);
fetchMethod.setAccessible(true);
Object response = fetchMethod.invoke(urlFetchService, request);

int responseCode = (Integer) response.getClass().getDeclaredMethod("getResponseCode").invoke(response);
byte[] responseContent = (byte[]) response.getClass().getDeclaredMethod("getContent").invoke(response);
if (responseContent != null && responseContent.length > Constants.Http.MAX_RESPONSE_BODY_BYTES) {
throw responseBodyTooLargeError();
}
String responseBody = new String(responseContent, Constants.Http.CHARSET);

return new EasyPostResponse(responseCode, responseBody);

} catch (InvocationTargetException | MalformedURLException | SecurityException | NoSuchFieldException
| NoSuchMethodException | ClassNotFoundException | IllegalArgumentException | IllegalAccessException
| InstantiationException | MissingParameterError | UnsupportedEncodingException e) {
throw new HttpError(unknownErrorMessage, e);
}
}
}
Loading