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 @@ -216,6 +216,12 @@ public class CommonParameter {
public int maxMessageSize;
@Getter
@Setter
public int httpMaxMessageSize;
@Getter
@Setter
public int jsonRpcMaxMessageSize;
@Getter
@Setter
public int maxHeaderListSize;
@Getter
@Setter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jetty.server.ConnectionLimit;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.SizeLimitHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.tron.core.config.args.Args;

Expand All @@ -29,6 +30,8 @@ public abstract class HttpService extends AbstractService {

protected String contextPath;

protected int maxRequestSize;

@Override
public void innerStart() throws Exception {
if (this.apiServer != null) {
Expand Down Expand Up @@ -63,7 +66,9 @@ protected void initServer() {
protected ServletContextHandler initContextHandler() {
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath(this.contextPath);
this.apiServer.setHandler(context);
SizeLimitHandler sizeLimitHandler = new SizeLimitHandler(this.maxRequestSize, -1);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This moves oversized-request handling in front of every servlet, so those requests never reach RateLimiterServlet.service() / Util.processError(). Today the HTTP APIs consistently set application/json and serialize failures through Util.printErrorMsg(...); after this change an over-limit body gets Jetty's default 413 response instead. That is a client-visible behavior change for existing callers, and the new test only checks status codes so it would not catch the response-format regression.

sizeLimitHandler.setHandler(context);
this.apiServer.setHandler(sizeLimitHandler);
return context;
}

Expand Down
14 changes: 14 additions & 0 deletions framework/src/main/java/org/tron/core/config/args/Args.java
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,20 @@ public static void applyConfigParams(
PARAMETER.maxMessageSize = config.hasPath(ConfigKey.NODE_RPC_MAX_MESSAGE_SIZE)
? config.getInt(ConfigKey.NODE_RPC_MAX_MESSAGE_SIZE) : GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;

int defaultHttpMaxMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
PARAMETER.httpMaxMessageSize = config.hasPath(ConfigKey.NODE_HTTP_MAX_MESSAGE_SIZE)
? config.getInt(ConfigKey.NODE_HTTP_MAX_MESSAGE_SIZE) : defaultHttpMaxMessageSize;
if (PARAMETER.httpMaxMessageSize <= 0) {
throw new TronError("node.http.maxMessageSize must be positive, got: "
+ PARAMETER.httpMaxMessageSize, PARAMETER_INIT);
}
PARAMETER.jsonRpcMaxMessageSize = config.hasPath(ConfigKey.NODE_JSONRPC_MAX_MESSAGE_SIZE)
? config.getInt(ConfigKey.NODE_JSONRPC_MAX_MESSAGE_SIZE) : defaultHttpMaxMessageSize;
if (PARAMETER.jsonRpcMaxMessageSize <= 0) {
throw new TronError("node.jsonrpc.maxMessageSize must be positive, got: "
+ PARAMETER.jsonRpcMaxMessageSize, PARAMETER_INIT);
}

PARAMETER.maxHeaderListSize = config.hasPath(ConfigKey.NODE_RPC_MAX_HEADER_LIST_SIZE)
? config.getInt(ConfigKey.NODE_RPC_MAX_HEADER_LIST_SIZE)
: GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ private ConfigKey() {
public static final String NODE_HTTP_SOLIDITY_ENABLE = "node.http.solidityEnable";
public static final String NODE_HTTP_PBFT_ENABLE = "node.http.PBFTEnable";
public static final String NODE_HTTP_PBFT_PORT = "node.http.PBFTPort";
public static final String NODE_HTTP_MAX_MESSAGE_SIZE = "node.http.maxMessageSize";

// node - jsonrpc
public static final String NODE_JSONRPC_HTTP_FULLNODE_ENABLE =
Expand All @@ -150,6 +151,7 @@ private ConfigKey() {
public static final String NODE_JSONRPC_MAX_SUB_TOPICS = "node.jsonrpc.maxSubTopics";
public static final String NODE_JSONRPC_MAX_BLOCK_FILTER_NUM =
"node.jsonrpc.maxBlockFilterNum";
public static final String NODE_JSONRPC_MAX_MESSAGE_SIZE = "node.jsonrpc.maxMessageSize";

// node - dns
public static final String NODE_DNS_TREE_URLS = "node.dns.treeUrls";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ public FullNodeHttpApiService() {
port = Args.getInstance().getFullNodeHttpPort();
enable = isFullNode() && Args.getInstance().isFullNodeHttpEnable();
contextPath = "/";
maxRequestSize = Args.getInstance().getHttpMaxMessageSize();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ public static Transaction packTransaction(String strTransaction, boolean selfTyp
}
}

@Deprecated
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marking this helper as deprecated does not make the new HTTP limit effective yet. PostParams.getPostParams() and many servlets still call Util.checkBodySize(), and that method is still enforcing parameter.getMaxMessageSize() (the gRPC limit), not httpMaxMessageSize. So any request whose body is > node.rpc.maxMessageSize but <= node.http.maxMessageSize will pass Jetty and then still be rejected in the servlet layer, which means the new independent HTTP setting is not actually honored for a large part of the API surface.

public static void checkBodySize(String body) throws Exception {
CommonParameter parameter = Args.getInstance();
if (body.getBytes().length > parameter.getMaxMessageSize()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ public SolidityNodeHttpApiService() {
port = Args.getInstance().getSolidityHttpPort();
enable = !isFullNode() && Args.getInstance().isSolidityNodeHttpEnable();
contextPath = "/";
maxRequestSize = Args.getInstance().getHttpMaxMessageSize();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public JsonRpcServiceOnPBFT() {
port = Args.getInstance().getJsonRpcHttpPBFTPort();
enable = isFullNode() && Args.getInstance().isJsonRpcHttpPBFTNodeEnable();
contextPath = "/";
maxRequestSize = Args.getInstance().getJsonRpcMaxMessageSize();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public JsonRpcServiceOnSolidity() {
port = Args.getInstance().getJsonRpcHttpSolidityPort();
enable = isFullNode() && Args.getInstance().isJsonRpcHttpSolidityNodeEnable();
contextPath = "/";
maxRequestSize = Args.getInstance().getJsonRpcMaxMessageSize();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ public HttpApiOnPBFTService() {
port = Args.getInstance().getPBFTHttpPort();
enable = isFullNode() && Args.getInstance().isPBFTHttpEnable();
contextPath = "/walletpbft";
maxRequestSize = Args.getInstance().getHttpMaxMessageSize();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ public HttpApiOnSolidityService() {
port = Args.getInstance().getSolidityHttpPort();
enable = isFullNode() && Args.getInstance().isSolidityNodeHttpEnable();
contextPath = "/";
maxRequestSize = Args.getInstance().getHttpMaxMessageSize();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public FullNodeJsonRpcHttpService() {
port = Args.getInstance().getJsonRpcHttpFullNodePort();
enable = isFullNode() && Args.getInstance().isJsonRpcHttpFullNodeEnable();
contextPath = "/";
maxRequestSize = Args.getInstance().getJsonRpcMaxMessageSize();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package org.tron.common.jetty;

import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.tron.common.TestConstants;
import org.tron.common.application.HttpService;
import org.tron.common.utils.PublicMethod;
import org.tron.core.config.args.Args;

/**
* Tests the {@link org.eclipse.jetty.server.handler.SizeLimitHandler} body-size
* enforcement configured in {@link HttpService initContextHandler()}.
*
* <p>Covers:</p>
* <ul>
* <li>Bodies within the limit are accepted ({@code 200}).</li>
* <li>Bodies exceeding the limit are rejected ({@code 413}).</li>
* <li>The limit counts raw UTF-8 <em>bytes</em>, not Java {@code char}s.</li>
* <li>HTTP and JSON-RPC services use independent size limits.</li>
* <li>Default values are 4x {@code GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE}.</li>
* </ul>
*/
@Slf4j
public class SizeLimitHandlerTest {

private static final int HTTP_MAX_BODY_SIZE = 1024;
private static final int JSONRPC_MAX_BODY_SIZE = 512;

@ClassRule
public static final TemporaryFolder temporaryFolder = new TemporaryFolder();

private static TestHttpService httpService;
private static TestJsonRpcService jsonRpcService;
private static URI httpServerUri;
private static URI jsonRpcServerUri;
private static CloseableHttpClient client;

/** Echoes the raw request-body bytes back so tests can inspect what arrived. */
public static class EchoServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
byte[] body = ByteStreams.toByteArray(req.getInputStream());
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType("application/octet-stream");
resp.getOutputStream().write(body);
}
}

/** Minimal concrete {@link HttpService} wired with a given size limit. */
static class TestHttpService extends HttpService {
TestHttpService(int port, int maxRequestSize) {
this.port = port;
this.contextPath = "/";
this.maxRequestSize = maxRequestSize;
}

@Override
protected void addServlet(ServletContextHandler context) {
context.addServlet(new ServletHolder(new EchoServlet()), "/*");
}
}

/** Minimal concrete {@link HttpService} simulating a JSON-RPC service. */
static class TestJsonRpcService extends HttpService {
TestJsonRpcService(int port, int maxRequestSize) {
this.port = port;
this.contextPath = "/";
this.maxRequestSize = maxRequestSize;
}

@Override
protected void addServlet(ServletContextHandler context) {
context.addServlet(new ServletHolder(new EchoServlet()), "/jsonrpc");
}
}

@BeforeClass
public static void setup() throws Exception {
Args.setParam(new String[]{"-d", temporaryFolder.newFolder().toString()},
TestConstants.TEST_CONF);
Args.getInstance().setHttpMaxMessageSize(HTTP_MAX_BODY_SIZE);
Args.getInstance().setJsonRpcMaxMessageSize(JSONRPC_MAX_BODY_SIZE);

int httpPort = PublicMethod.chooseRandomPort();
httpService = new TestHttpService(httpPort, HTTP_MAX_BODY_SIZE);
httpService.start().get(10, TimeUnit.SECONDS);
httpServerUri = new URI(String.format("http://localhost:%d/", httpPort));

int jsonRpcPort = PublicMethod.chooseRandomPort();
jsonRpcService = new TestJsonRpcService(jsonRpcPort, JSONRPC_MAX_BODY_SIZE);
jsonRpcService.start().get(10, TimeUnit.SECONDS);
jsonRpcServerUri = new URI(String.format("http://localhost:%d/jsonrpc", jsonRpcPort));

client = HttpClients.createDefault();
}

@AfterClass
public static void teardown() throws Exception {
try {
if (client != null) {
client.close();
}
} finally {
try {
if (httpService != null) {
httpService.stop();
}
} finally {
if (jsonRpcService != null) {
jsonRpcService.stop();
}
}
Args.clearParam();
}
}

// -- HTTP service body-size tests -------------------------------------------

@Test
public void testHttpBodyWithinLimit() throws Exception {
Assert.assertEquals(200, post(httpServerUri, new StringEntity("small body")));
}

@Test
public void testHttpBodyExceedsLimit() throws Exception {
Assert.assertEquals(413,
post(httpServerUri, new StringEntity(repeat('a', HTTP_MAX_BODY_SIZE + 1))));
}

@Test
public void testHttpBodyAtExactLimit() throws Exception {
Assert.assertEquals(200,
post(httpServerUri, new StringEntity(repeat('b', HTTP_MAX_BODY_SIZE))));
}

// -- JSON-RPC service body-size tests ---------------------------------------

@Test
public void testJsonRpcBodyWithinLimit() throws Exception {
Assert.assertEquals(200,
post(jsonRpcServerUri, new StringEntity("{\"method\":\"eth_blockNumber\"}")));
}

@Test
public void testJsonRpcBodyExceedsLimit() throws Exception {
Assert.assertEquals(413,
post(jsonRpcServerUri, new StringEntity(repeat('x', JSONRPC_MAX_BODY_SIZE + 1))));
}

@Test
public void testJsonRpcBodyAtExactLimit() throws Exception {
Assert.assertEquals(200,
post(jsonRpcServerUri, new StringEntity(repeat('c', JSONRPC_MAX_BODY_SIZE))));
}

// -- Independent limit tests ------------------------------------------------

@Test
public void testHttpAndJsonRpcHaveIndependentLimits() throws Exception {
// A body that exceeds JSON-RPC limit but is within HTTP limit
String body = repeat('d', JSONRPC_MAX_BODY_SIZE + 100);
Assert.assertTrue(body.length() < HTTP_MAX_BODY_SIZE);

Assert.assertEquals(200, post(httpServerUri, new StringEntity(body)));
Assert.assertEquals(413, post(jsonRpcServerUri, new StringEntity(body)));
}

// -- UTF-8 byte counting test -----------------------------------------------

@Test
public void testLimitIsBasedOnBytesNotCharacters() throws Exception {
// Each CJK character is 3 UTF-8 bytes; 342 chars x 3 = 1026 bytes > 1024
String cjk = repeat('一', 342);
Assert.assertEquals(342, cjk.length());
Assert.assertEquals(1026, cjk.getBytes("UTF-8").length);
Assert.assertEquals(413, post(httpServerUri, new StringEntity(cjk, "UTF-8")));
}

// -- helpers ----------------------------------------------------------------

/** POSTs with the given entity and returns the HTTP status code. */
private int post(URI uri, HttpEntity entity) throws Exception {
HttpPost req = new HttpPost(uri);
req.setEntity(entity);
HttpResponse resp = client.execute(req);
EntityUtils.consume(resp.getEntity());
return resp.getStatusLine().getStatusCode();
}

/** Returns a string of {@code n} repetitions of {@code c}. */
private static String repeat(char c, int n) {
return new String(new char[n]).replace('\0', c);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ public void get() {
Assert.assertEquals(60000L, parameter.getMaxConnectionIdleInMillis());
Assert.assertEquals(Long.MAX_VALUE, parameter.getMaxConnectionAgeInMillis());
Assert.assertEquals(GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE, parameter.getMaxMessageSize());
Assert.assertEquals(GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE, parameter.getHttpMaxMessageSize());
Assert.assertEquals(GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE, parameter.getJsonRpcMaxMessageSize());
Assert.assertEquals(GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE, parameter.getMaxHeaderListSize());
Assert.assertEquals(1L, parameter.getAllowCreationOfContracts());
Assert.assertEquals(0, parameter.getConsensusLogicOptimization());
Expand Down Expand Up @@ -342,4 +344,3 @@ public void testConfigStorageDefaults() {
Args.clearParam();
}
}

Loading