Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ public class GetBurnTrxServlet extends RateLimiterServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
try {
long value = manager.getDynamicPropertiesStore().getBurnTrxAmount();
response.getWriter().println("{\"burnTrxAmount\": " + value + "}");
String out = JsonFormat.isInt64AsString()
? "{\"burnTrxAmount\": \"" + value + "\"}"
: "{\"burnTrxAmount\": " + value + "}";
response.getWriter().println(out);
} catch (Exception e) {
logger.error("", e);
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ public class GetPendingSizeServlet extends RateLimiterServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
try {
long value = manager.getPendingSize();
response.getWriter().println("{\"pendingSize\": " + value + "}");
String out = JsonFormat.isInt64AsString()
? "{\"pendingSize\": \"" + value + "\"}"
: "{\"pendingSize\": " + value + "}";
response.getWriter().println(out);
} catch (Exception e) {
logger.error("", e);
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) {
if (address != null) {
value = manager.getMortgageService().queryReward(address);
}
response.getWriter().println("{\"reward\": " + value + "}");
String out = JsonFormat.isInt64AsString()
? "{\"reward\": \"" + value + "\"}"
: "{\"reward\": " + value + "}";
response.getWriter().println(out);
} catch (DecoderException | IllegalArgumentException e) {
try {
response.getWriter()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)

private void fillResponse(long num, HttpServletResponse response) throws IOException {
long count = wallet.getTransactionCountByBlockNum(num);
response.getWriter().println("{\"count\": " + count + "}");
String out = JsonFormat.isInt64AsString()
? "{\"count\": \"" + count + "\"}"
: "{\"count\": " + count + "}";
response.getWriter().println(out);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,41 @@ public class JsonFormat {
BalanceContract.TransactionBalanceTrace.class
);

/**
* Thread-local flag controlling whether int64/uint64 fields are serialized as JSON strings.
* Set via {@link #setInt64AsString(boolean)} early in request handling and cleared via
* {@link #clearInt64AsString()} in a finally block. Centralized in
* {@code RateLimiterServlet.service} for GET requests. Does not support nested scopes.
*/
private static final ThreadLocal<Boolean> INT64_AS_STRING =
ThreadLocal.withInitial(() -> false);

/**
* Set whether int64/uint64 protobuf fields are serialized as quoted JSON strings to avoid
* precision loss in clients whose native number type cannot safely represent integers above
* 2^53 - 1 (e.g. JavaScript). Must be paired with {@link #clearInt64AsString()} in a
* finally block.
*/
public static void setInt64AsString(boolean enabled) {
INT64_AS_STRING.set(enabled);
}

/**
* Clear the int64-as-string thread-local. Always call from a finally block to avoid
* polluting subsequent requests on the same (reused) thread.
*/
public static void clearInt64AsString() {
INT64_AS_STRING.remove();
}

/**
* Whether the current thread is in int64-as-string mode. Used by servlets that build
* JSON literals manually (i.e. do not go through {@link #printToString}).
*/
public static boolean isInt64AsString() {
return INT64_AS_STRING.get();
}

/**
* Outputs a textual representation of the Protocol Message supplied into the parameter output.
* (This representation is the new version of the classic "ProtocolPrinter" output from the
Expand Down Expand Up @@ -340,26 +375,41 @@ private static void printFieldValue(FieldDescriptor field, Object value,
throws IOException {
switch (field.getType()) {
case INT32:
case INT64:
case SINT32:
case SINT64:
case SFIXED32:
case SFIXED64:
case FLOAT:
case DOUBLE:
case BOOL:
// Good old toString() does what we want for these types.
generator.print(value.toString());
break;

case INT64:
case SINT64:
case SFIXED64:
if (INT64_AS_STRING.get()) {
generator.print("\"");
generator.print(value.toString());
generator.print("\"");
} else {
generator.print(value.toString());
}
break;

case UINT32:
case FIXED32:
generator.print(unsignedToString((Integer) value));
break;

case UINT64:
case FIXED64:
generator.print(unsignedToString((Long) value));
if (INT64_AS_STRING.get()) {
generator.print("\"");
generator.print(unsignedToString((Long) value));
generator.print("\"");
} else {
generator.print(unsignedToString((Long) value));
}
break;

case STRING:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ protected void service(HttpServletRequest req, HttpServletResponse resp)
String contextPath = req.getContextPath();
String url = Strings.isNullOrEmpty(req.getServletPath())
? MetricLabels.UNDEFINED : contextPath + req.getServletPath();
// int64_as_string is honored only on GET requests (URL query). POST is intentionally
// unsupported because reading the body here would consume request.getReader() and
// break downstream servlets that read it themselves.
if ("GET".equalsIgnoreCase(req.getMethod())) {
JsonFormat.setInt64AsString(Util.getInt64AsString(req));
}
try {
resp.setContentType("application/json; charset=utf-8");

Expand All @@ -119,6 +125,10 @@ protected void service(HttpServletRequest req, HttpServletResponse resp)
} catch (Exception unexpected) {
logger.error("Http Api {}, Method:{}. Error:", url, req.getMethod(), unexpected);
} finally {
// CRITICAL: this clear pairs with the setInt64AsString call above. Removing it
// will leak int64_as_string state across requests on reused Tomcat threads,
// producing intermittent quoted/unquoted output that is very hard to debug.
JsonFormat.clearInt64AsString();
if (rateLimiter instanceof IPreemptibleRateLimiter && acquireResource) {
((IPreemptibleRateLimiter) rateLimiter).release();
}
Expand Down
15 changes: 15 additions & 0 deletions framework/src/main/java/org/tron/core/services/http/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public class Util {

public static final String PERMISSION_ID = "Permission_id";
public static final String VISIBLE = "visible";
public static final String INT64_AS_STRING = "int64_as_string";
public static final String TRANSACTION = "transaction";
public static final String TRANSACTION_EXTENSION = "transactionExtension";
public static final String VALUE = "value";
Expand Down Expand Up @@ -346,6 +347,20 @@ public static boolean existVisible(final HttpServletRequest request) {
return Objects.nonNull(request.getParameter(VISIBLE));
}

/**
* Read int64_as_string from URL query parameter. Mirrors
* {@link #getVisible(HttpServletRequest)}. The flag is honored only on GET requests
* (read by {@link RateLimiterServlet#service}); POST requests do not support it
* because that would require caching the request body to allow re-reading by
* downstream servlets.
*/
public static boolean getInt64AsString(final HttpServletRequest request) {
if (StringUtil.isNotBlank(request.getParameter(INT64_AS_STRING))) {
return Boolean.parseBoolean(request.getParameter(INT64_AS_STRING));
}
return false;
}

public static boolean getVisiblePost(final String input) {
boolean visible = false;
if (StringUtil.isNotBlank(input)) {
Expand Down
Loading
Loading