Skip to content

Commit 9eb44ce

Browse files
committed
optimize JsonRpcServlet
1 parent bf9a5a1 commit 9eb44ce

3 files changed

Lines changed: 48 additions & 25 deletions

File tree

framework/src/main/java/org/tron/core/services/filter/BufferedResponseWrapper.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
* the handler returns and write its own error response when true.
1919
*
2020
* <p>Header-mutating methods ({@code setStatus}, {@code setContentType}) are buffered here and
21-
* only forwarded to the real response via {@link #commitToResponse()}, preventing a timed-out
22-
* handler thread from racing with the timeout error writer.
21+
* only forwarded to the real response via {@link #commitToResponse()}.
2322
*/
2423
public class BufferedResponseWrapper extends HttpServletResponseWrapper {
2524

@@ -28,8 +27,9 @@ public class BufferedResponseWrapper extends HttpServletResponseWrapper {
2827
private final int maxBytes;
2928
private int status = HttpServletResponse.SC_OK;
3029
private String contentType;
30+
private boolean committed = false;
3131
@Getter
32-
private boolean overflow = false;
32+
private volatile boolean overflow = false;
3333

3434
private final ServletOutputStream outputStream = new ServletOutputStream() {
3535
@Override
@@ -122,6 +122,10 @@ public PrintWriter getWriter() {
122122
}
123123

124124
public void commitToResponse() throws IOException {
125+
if (committed) {
126+
throw new IllegalStateException("commitToResponse() already called");
127+
}
128+
committed = true;
125129
if (contentType != null) {
126130
actual.setContentType(contentType);
127131
}

framework/src/main/java/org/tron/core/services/jsonrpc/JsonRpcServlet.java

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package org.tron.core.services.jsonrpc;
22

3+
import com.fasterxml.jackson.core.JsonProcessingException;
34
import com.fasterxml.jackson.databind.JsonNode;
45
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import com.fasterxml.jackson.databind.node.ArrayNode;
57
import com.fasterxml.jackson.databind.node.ObjectNode;
68
import com.googlecode.jsonrpc4j.HttpStatusCodeProvider;
79
import com.googlecode.jsonrpc4j.JsonRpcInterceptor;
@@ -29,12 +31,13 @@ public class JsonRpcServlet extends RateLimiterServlet {
2931

3032
private static final ObjectMapper MAPPER = new ObjectMapper();
3133

32-
enum JsonRpcError {
34+
private enum JsonRpcError {
3335
PARSE_ERROR(-32700),
36+
INTERNAL_ERROR(-32603),
3437
EXCEED_LIMIT(-32005),
3538
RESPONSE_TOO_LARGE(-32003);
3639

37-
final int code;
40+
private final int code;
3841

3942
JsonRpcError(int code) {
4043
this.code = code;
@@ -86,19 +89,25 @@ public Integer getJsonRpcCode(int httpStatusCode) {
8689
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
8790
CommonParameter parameter = CommonParameter.getInstance();
8891

89-
byte[] body;
92+
// Transport IOException from readBody propagates as HTTP 500 (genuine IO failure).
93+
byte[] body = readBody(req.getInputStream());
9094
JsonNode rootNode;
9195
try {
92-
body = readBody(req.getInputStream());
9396
rootNode = MAPPER.readTree(body);
94-
} catch (IOException e) {
95-
writeJsonRpcError(resp, JsonRpcError.PARSE_ERROR, "Parse error", null);
97+
if (rootNode == null || rootNode.isMissingNode()) {
98+
writeJsonRpcError(resp, JsonRpcError.PARSE_ERROR, "Parse error", null, false);
99+
return;
100+
}
101+
} catch (JsonProcessingException e) {
102+
writeJsonRpcError(resp, JsonRpcError.PARSE_ERROR, "Parse error", null, false);
96103
return;
97104
}
105+
106+
boolean isBatch = rootNode.isArray();
98107
int batchSize = parameter.getJsonRpcMaxBatchSize();
99-
if (rootNode.isArray() && batchSize > 0 && rootNode.size() > batchSize) {
108+
if (isBatch && batchSize > 0 && rootNode.size() > batchSize) {
100109
writeJsonRpcError(resp, JsonRpcError.EXCEED_LIMIT,
101-
"Batch size " + rootNode.size() + " exceeds the limit of " + batchSize, null);
110+
"Batch size " + rootNode.size() + " exceeds the limit of " + batchSize, null, true);
102111
return;
103112
}
104113

@@ -108,15 +117,18 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
108117

109118
try {
110119
rpcServer.handle(cachedReq, bufferedResp);
111-
} catch (Exception e) {
112-
throw new IOException("RPC execution failed", e);
120+
} catch (RuntimeException e) {
121+
logger.error("RPC execution failed", e);
122+
JsonNode idNode = isBatch ? null : rootNode.get("id");
123+
writeJsonRpcError(resp, JsonRpcError.INTERNAL_ERROR, "Internal error", idNode, isBatch);
124+
return;
113125
}
114126

115127
if (bufferedResp.isOverflow()) {
116-
JsonNode idNode = !rootNode.isArray() ? rootNode.get("id") : null;
128+
JsonNode idNode = isBatch ? null : rootNode.get("id");
117129
writeJsonRpcError(resp, JsonRpcError.RESPONSE_TOO_LARGE,
118130
"Response exceeds the limit of " + parameter.getJsonRpcMaxResponseSize() + " bytes",
119-
idNode);
131+
idNode, isBatch);
120132
return;
121133
}
122134
bufferedResp.commitToResponse();
@@ -133,18 +145,25 @@ private byte[] readBody(InputStream in) throws IOException {
133145
}
134146

135147
private void writeJsonRpcError(HttpServletResponse resp, JsonRpcError error, String message,
136-
JsonNode id) throws IOException {
137-
ObjectNode root = MAPPER.createObjectNode();
138-
root.put("jsonrpc", "2.0");
139-
ObjectNode errNode = root.putObject("error");
148+
JsonNode id, boolean isBatch) throws IOException {
149+
ObjectNode errorObj = MAPPER.createObjectNode();
150+
errorObj.put("jsonrpc", "2.0");
151+
ObjectNode errNode = errorObj.putObject("error");
140152
errNode.put("code", error.code);
141153
errNode.put("message", message);
142154
if (id != null && !id.isNull() && !id.isMissingNode()) {
143-
root.set("id", id);
155+
errorObj.set("id", id);
156+
} else {
157+
errorObj.putNull("id");
158+
}
159+
byte[] bytes;
160+
if (isBatch) {
161+
ArrayNode arr = MAPPER.createArrayNode();
162+
arr.add(errorObj);
163+
bytes = MAPPER.writeValueAsBytes(arr);
144164
} else {
145-
root.putNull("id");
165+
bytes = MAPPER.writeValueAsBytes(errorObj);
146166
}
147-
byte[] bytes = MAPPER.writeValueAsBytes(root);
148167
resp.setContentType("application/json; charset=utf-8");
149168
resp.setStatus(HttpServletResponse.SC_OK);
150169
resp.setContentLength(bytes.length);

framework/src/main/resources/config.conf

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,10 +374,10 @@ node {
374374
maxSubTopics = 1000
375375
# Allowed maximum number for blockFilter, default: 50000, >0 otherwise no limit
376376
maxBlockFilterNum = 50000
377-
# Allowed batch size, default: 100, default: 100, >0 otherwise no limit
377+
# Allowed batch size, default: 100, >0 otherwise no limit
378378
maxBatchSize = 100
379-
# Allowed max response byte size, default: 26214400, >0 otherwise no limit
380-
maxResponseSize = 26214400 // 25 MB = 25 * 1024 * 1024 B
379+
# Allowed max response byte size, default: 26214400 (25 MB), >0 otherwise no limit
380+
maxResponseSize = 26214400
381381
}
382382

383383
# Disabled api list, it will work for http, rpc and pbft, both FullNode and SolidityNode,

0 commit comments

Comments
 (0)