Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add custom HttpStatusCodeProvider to adjust to JSON-RPC 2.0 conventions #1522

Merged
Merged
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
7 changes: 6 additions & 1 deletion rskj-core/src/main/java/co/rsk/rpc/netty/Web3HttpServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ protected void initChannel(SocketChannel ch) throws Exception {
p.addLast(jsonRpcWeb3FilterHandler);
p.addLast(new Web3HttpMethodFilterHandler());
p.addLast(jsonRpcWeb3ServerHandler);
p.addLast(new Web3ResultHttpResponseHandler());
p.addLast(buildWeb3ResultHttpResponseHandler());
}
});
try {
Expand All @@ -92,6 +92,11 @@ protected void initChannel(SocketChannel ch) throws Exception {
}
}

private Web3ResultHttpResponseHandler buildWeb3ResultHttpResponseHandler() {
Web3HttpStatusCodeProvider web3HttpStatusCodeProvider = new Web3HttpStatusCodeProvider();
return new Web3ResultHttpResponseHandler(web3HttpStatusCodeProvider);
}

@Override
public void stop() {
bossGroup.shutdownGracefully();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package co.rsk.rpc.netty;

import com.googlecode.jsonrpc4j.HttpStatusCodeProvider;
import io.netty.handler.codec.http.HttpResponseStatus;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.*;

public class Web3HttpStatusCodeProvider implements HttpStatusCodeProvider {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Based on this class (which we used prior to this change).
FYI: version 1.6 of jsonrpc4j doesn't adjust error handling to the JSON-RPC guidelines, that's why I decided not to update it this PR.


private static final List<Integer> BAD_REQUEST_JSON_ERRORS = Stream.of(METHOD_PARAMS_INVALID, INVALID_REQUEST, PARSE_ERROR)
.map(jsonError -> jsonError.code)
.collect(Collectors.toList());

/**
* Only invalid JSON-RPC requests (e.g. a malformed JSON) qualify for a Bad Request HTTP status.
* In any other case, the HTTP protocol remains entirely independent from JSON-RPC (since its 2.0 version).
* Reference: https://www.jsonrpc.org/
*/
@Override
public int getHttpStatusCode(int resultCode) {
return isBadRequestResultCode(resultCode) ?
HttpResponseStatus.BAD_REQUEST.code() :
HttpResponseStatus.OK.code();
}

private static boolean isBadRequestResultCode(int resultCode) {
return BAD_REQUEST_JSON_ERRORS.contains(resultCode);
}

/**
* The default implementation wrongly assumes that an HTTP status code is mapped to a single JSON RPC error - this
* is not always the case. Either way, this is not used in the current project.
*/
@Override
public Integer getJsonRpcCode(int httpStatusCode) {
throw new UnsupportedOperationException("Cannot possibly determine a single JSON RPC from an HTTP status code");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

jsonrpc4j maps each supported HTTP status code to a single JSON-RPC code - this is not entirely correct in my opinion, since a single HTTP code could map to N JSON-RPC codes (this even happens in jsonrpc4j).
Since we don't use this method in rskj, I decided to leave it like this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Take into account that a JSON RPC request could be an array of valid JSON RPC requests. Is this case covered in the process of responses/responses code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since I just swapped the DefaultHttpStatusCodeProvider (from jsonrpc4j library) for a custom implementation that adheres to the same signatures (but mapping HTTP codes differently), the actual processing of requests is not involved (this is only used in the response handling part of the pipeline).

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,30 @@
*/
package co.rsk.rpc.netty;

import com.googlecode.jsonrpc4j.DefaultHttpStatusCodeProvider;
import com.googlecode.jsonrpc4j.HttpStatusCodeProvider;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;

import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaders.Values.APPLICATION_JSON;

public class Web3ResultHttpResponseHandler extends SimpleChannelInboundHandler<Web3Result> {

private final HttpStatusCodeProvider httpStatusCodeProvider;

public Web3ResultHttpResponseHandler(HttpStatusCodeProvider httpStatusCodeProvider) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess using this 3rd-party interface is perfectly fine here, but as an option it could also be IntFunction<HttpResponseStatus> errorCodeToHttpStatusMapper

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's an interesting suggestion, but considering that we already depend on many classes of com.googlecode.jsonrpc4 for JSON-RPC handling, I think it'd be overkill (besides, I guess rskj shouldn't have the responsibility of defining stuff related to this). Is it ok if I leave it like this?

this.httpStatusCodeProvider = httpStatusCodeProvider;
}

@Override
protected void channelRead0(ChannelHandlerContext ctx, Web3Result msg) {
DefaultFullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.valueOf(DefaultHttpStatusCodeProvider.INSTANCE.getHttpStatusCode(msg.getCode())),
HttpResponseStatus.valueOf(httpStatusCodeProvider.getHttpStatusCode(msg.getCode())),
msg.getContent()
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package co.rsk.rpc.netty;

import com.googlecode.jsonrpc4j.HttpStatusCodeProvider;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.junit.Before;
import org.junit.Test;

import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.*;
import static org.junit.Assert.assertEquals;

public class Web3HttpStatusCodeProviderTest {

private HttpStatusCodeProvider httpStatusCodeProvider;

@Before
public void setup() throws Exception {
httpStatusCodeProvider = new Web3HttpStatusCodeProvider();
}

@Test
public void getHttpStatusCodeReturnsExpectedHttpStatusCodes() {
assertThatOkHttpStatusIsReturnedWhenExpected(httpStatusCodeProvider);
assertThatBadRequestHttpStatusIsReturnedWhenExpected(httpStatusCodeProvider);
}

private void assertThatBadRequestHttpStatusIsReturnedWhenExpected(HttpStatusCodeProvider httpStatusCodeProvider) {
assertReturnedHttpStatusCodeIsExpected(httpStatusCodeProvider, OK.code, HttpResponseStatus.OK.code());
assertReturnedHttpStatusCodeIsExpected(httpStatusCodeProvider, METHOD_NOT_FOUND.code, HttpResponseStatus.OK.code());
assertReturnedHttpStatusCodeIsExpected(httpStatusCodeProvider, INTERNAL_ERROR.code, HttpResponseStatus.OK.code());
assertReturnedHttpStatusCodeIsExpected(httpStatusCodeProvider, ERROR_NOT_HANDLED.code, HttpResponseStatus.OK.code());
assertReturnedHttpStatusCodeIsExpected(httpStatusCodeProvider, BULK_ERROR.code, HttpResponseStatus.OK.code());
}

private void assertThatOkHttpStatusIsReturnedWhenExpected(HttpStatusCodeProvider httpStatusCodeProvider) {
assertReturnedHttpStatusCodeIsExpected(httpStatusCodeProvider, METHOD_PARAMS_INVALID.code, HttpResponseStatus.BAD_REQUEST.code());
assertReturnedHttpStatusCodeIsExpected(httpStatusCodeProvider, INVALID_REQUEST.code, HttpResponseStatus.BAD_REQUEST.code());
assertReturnedHttpStatusCodeIsExpected(httpStatusCodeProvider, PARSE_ERROR.code, HttpResponseStatus.BAD_REQUEST.code());
}

private void assertReturnedHttpStatusCodeIsExpected(HttpStatusCodeProvider httpStatusCodeProvider,
int jsonRpcResultCode, int expectedHttpStatusCode) {
int httpStatusCode = httpStatusCodeProvider.getHttpStatusCode(jsonRpcResultCode);
assertEquals(expectedHttpStatusCode, httpStatusCode);
}

@Test(expected = UnsupportedOperationException.class)
public void getJsonRpcCodeThrowsUnsupportedOperationException() {
httpStatusCodeProvider.getJsonRpcCode(INVALID_REQUEST.code);
}
}