Skip to content

Commit

Permalink
Extract JsonRpcRequest then map parameters only (Consensys#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
rain-on authored Apr 3, 2019
1 parent c714795 commit f583eca
Show file tree
Hide file tree
Showing 13 changed files with 351 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.ext.web.RoutingContext;
import io.vertx.core.json.Json;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -69,10 +69,10 @@ private RequestMapper createRequestMapper(

final HttpClient downStreamConnection = vertx.createHttpClient(clientOptions);

// TODO use the json request
final PassThroughHandler passThroughHandler =
new PassThroughHandler(
downStreamConnection, (RoutingContext context) -> new JsonRpcBody(context.getBody()));
downStreamConnection,
(jsonRpcRequest) -> new JsonRpcBody(Json.encodeToBuffer(jsonRpcRequest)));

final RequestMapper requestMapper = new RequestMapper(passThroughHandler);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ public class JsonRpcRequest {

private JsonRpcRequestId id;
private final String method;
private final Object[] params;
private final Object params;
private final String version;

@JsonCreator
public JsonRpcRequest(
@JsonProperty("jsonrpc") final String version,
@JsonProperty("method") final String method,
@JsonProperty("params") final Object[] params) {
@JsonProperty("params") final Object params) {
this.version = version;
this.method = method;
this.params = params;
Expand All @@ -45,8 +45,8 @@ public JsonRpcRequest(
}

@JsonGetter("id")
public Object getId() {
return id == null ? null : id.getValue();
public JsonRpcRequestId getId() {
return id;
}

@JsonGetter("method")
Expand All @@ -61,7 +61,7 @@ public String getVersion() {

@JsonInclude(Include.NON_NULL)
@JsonGetter("params")
public Object[] getParams() {
public Object getParams() {
return params;
}

Expand All @@ -79,14 +79,36 @@ public boolean equals(final Object o) {
return false;
}
final JsonRpcRequest that = (JsonRpcRequest) o;
return Objects.equal(id, that.id)

return isParamsEqual(that.params)
&& Objects.equal(id, that.id)
&& Objects.equal(method, that.method)
&& Arrays.equals(params, that.params)
&& Objects.equal(version, that.version);
}

private boolean isParamsEqual(final Object otherParams) {
if (params.getClass().isArray()) {
if (!otherParams.getClass().isArray()) {
return false;
}
Object[] paramsArray = (Object[]) params;
Object[] thatParamsArray = (Object[]) otherParams;
return Arrays.equals(paramsArray, thatParamsArray);
} else if (otherParams.getClass().isArray()) {
return false;
}

return params.equals(otherParams);
}

@Override
public int hashCode() {
return Objects.hashCode(id, method, Arrays.hashCode(params), version);
final int paramsHashCode;
if (params.getClass().isArray()) {
paramsHashCode = Arrays.hashCode((Object[]) params);
} else {
paramsHashCode = params.hashCode();
}
return Objects.hashCode(id, method, paramsHashCode, version);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
package tech.pegasys.ethfirewall.jsonrpc;

import java.math.BigInteger;
import java.util.List;
import java.util.Optional;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import io.vertx.core.json.JsonObject;

@JsonIgnoreProperties(ignoreUnknown = true)
public class SendTransactionJsonParameters {
Expand Down Expand Up @@ -115,4 +117,26 @@ private void validatePrefix(final String value) {
String.format("Prefix of '0x' is expected in value: %s", value));
}
}

public static SendTransactionJsonParameters from(final JsonRpcRequest request) {

final Object sendTransactionObject;
final Object params = request.getParams();
if (params instanceof List) {
@SuppressWarnings("unchecked")
final List<Object> paramList = (List<Object>) params;
if (paramList.size() != 1) {
throw new IllegalArgumentException(
"SendTransaction Json Rpc requires a single parameter, request contained "
+ paramList.size());
}
sendTransactionObject = paramList.get(0);
} else {
sendTransactionObject = params;
}

final JsonObject receivedParams = JsonObject.mapFrom(sendTransactionObject);

return receivedParams.mapTo(SendTransactionJsonParameters.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
*/
package tech.pegasys.ethfirewall.jsonrpcproxy;

import io.vertx.ext.web.RoutingContext;
import tech.pegasys.ethfirewall.jsonrpc.JsonRpcRequest;

@FunctionalInterface
public interface BodyProvider {

JsonRpcBody getBody(RoutingContext context);
JsonRpcBody getBody(JsonRpcRequest request);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package tech.pegasys.ethfirewall.jsonrpcproxy;

import tech.pegasys.ethfirewall.jsonrpc.JsonRpcRequest;
import tech.pegasys.ethfirewall.jsonrpc.response.JsonRpcError;
import tech.pegasys.ethfirewall.jsonrpc.response.JsonRpcErrorResponse;

Expand All @@ -22,7 +23,6 @@
import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
Expand Down Expand Up @@ -108,11 +108,15 @@ private Router router() {

private void handleJsonRpc(final RoutingContext context) {
try {
final JsonObject json = context.getBodyAsJson();
final Handler<RoutingContext> handler = requestHandlerMapper.getMatchingHandler(json);
handler.handle(context);
} catch (final DecodeException e) {
final JsonObject requestJson = context.getBodyAsJson();
final JsonRpcRequest request = requestJson.mapTo(JsonRpcRequest.class);
final JsonRpcRequestHandler handler =
requestHandlerMapper.getMatchingHandler(request.getMethod());
handler.handle(context.request(), request);
} catch (final DecodeException | IllegalArgumentException e) {
sendParseErrorResponse(context, e);
} catch (Exception e) {
LOG.error("An unhandled error occurred while processing {}", context.getBodyAsString(), e);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2019 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package tech.pegasys.ethfirewall.jsonrpcproxy;

import tech.pegasys.ethfirewall.jsonrpc.JsonRpcRequest;

import io.vertx.core.http.HttpServerRequest;

@FunctionalInterface
public interface JsonRpcRequestHandler {

void handle(HttpServerRequest httpServerRequest, JsonRpcRequest rpcRequest);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,20 @@
*/
package tech.pegasys.ethfirewall.jsonrpcproxy;

import tech.pegasys.ethfirewall.jsonrpc.JsonRpcRequest;
import tech.pegasys.ethfirewall.jsonrpc.response.JsonRpcErrorResponse;

import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpClientResponse;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.json.Json;
import io.vertx.ext.web.RoutingContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PassThroughHandler implements Handler<RoutingContext> {
public class PassThroughHandler implements JsonRpcRequestHandler {

private static final Logger LOG = LoggerFactory.getLogger(PassThroughHandler.class);
private final HttpClient ethNodeClient;
Expand All @@ -38,62 +37,61 @@ public PassThroughHandler(final HttpClient ethNodeClient, final BodyProvider bod
}

@Override
public void handle(final RoutingContext context) {
final HttpServerRequest originalRequest = context.request();
public void handle(final HttpServerRequest httpServerRequest, final JsonRpcRequest request) {
final HttpClientRequest proxyRequest =
ethNodeClient.request(
originalRequest.method(),
originalRequest.uri(),
httpServerRequest.method(),
httpServerRequest.uri(),
proxiedResponse -> {
logResponse(proxiedResponse);

originalRequest.response().setStatusCode(proxiedResponse.statusCode());
originalRequest.response().headers().setAll(proxiedResponse.headers());
originalRequest.response().setChunked(false);
httpServerRequest.response().setStatusCode(proxiedResponse.statusCode());
httpServerRequest.response().headers().setAll(proxiedResponse.headers());
httpServerRequest.response().setChunked(false);

proxiedResponse.bodyHandler(
data -> {
logResponseBody(data);

// End the sendRequest, preventing any other handler from executing
originalRequest.response().end(data);
httpServerRequest.response().end(data);
});
});

proxyRequest.headers().setAll(originalRequest.headers());
proxyRequest.headers().setAll(httpServerRequest.headers());
proxyRequest.headers().remove("Content-Length"); // created during 'end'.
proxyRequest.setChunked(false);

final JsonRpcBody providedBody = bodyProvider.getBody(context);
final JsonRpcBody providedBody = bodyProvider.getBody(request);

if (providedBody.hasError()) {
sendErrorResponse(context, originalRequest, providedBody.error());
sendErrorResponse(request, httpServerRequest, providedBody.error());
} else {
// Data is only written to the wire on end()
final Buffer proxyRequestBody = providedBody.body();
proxyRequest.end(proxyRequestBody);
logRequest(context, proxyRequest, proxyRequestBody);
logRequest(request, httpServerRequest, proxyRequest, proxyRequestBody);
}
}

private void sendErrorResponse(
final RoutingContext context,
final HttpServerRequest originalRequest,
final JsonRpcRequest jsonRequest,
final HttpServerRequest httpRequest,
final JsonRpcErrorResponse error) {
LOG.info("Dropping request from {}", originalRequest.remoteAddress());
LOG.info("Dropping request from {}", httpRequest.remoteAddress());
LOG.debug(
"Dropping request method: {}, uri: {}, body: {}, Error body: {}",
originalRequest.method(),
originalRequest.absoluteURI(),
context.getBodyAsString(),
httpRequest.method(),
httpRequest.absoluteURI(),
Json.encodePrettily(jsonRequest),
Json.encode(error));

originalRequest.response().setStatusCode(HttpResponseStatus.BAD_REQUEST.code());
originalRequest.response().headers().setAll(originalRequest.headers());
originalRequest.response().headers().remove("Content-Length"); // created during 'end'.
originalRequest.response().setChunked(false);
httpRequest.response().setStatusCode(HttpResponseStatus.BAD_REQUEST.code());
httpRequest.response().headers().setAll(httpRequest.headers());
httpRequest.response().headers().remove("Content-Length"); // created during 'end'.
httpRequest.response().setChunked(false);

originalRequest.response().end(Json.encodeToBuffer(error));
httpRequest.response().end(Json.encodeToBuffer(error));
}

private void logResponse(final HttpClientResponse response) {
Expand All @@ -105,14 +103,15 @@ private void logResponseBody(final Buffer body) {
}

private void logRequest(
final RoutingContext context,
final JsonRpcRequest originalJsonRpcRequest,
final HttpServerRequest originalRequest,
final HttpClientRequest proxyRequest,
final Buffer proxyRequestBody) {
LOG.debug(
"Original method: {}, uri: {}, body: {}, Proxy: method: {}, uri: {}, body: {}",
context.request().method(),
context.request().absoluteURI(),
context.getBody(),
originalRequest.method(),
originalRequest.absoluteURI(),
Json.encodePrettily(originalJsonRpcRequest),
proxyRequest.method(),
proxyRequest.absoluteURI(),
proxyRequestBody);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,20 @@
import java.util.HashMap;
import java.util.Map;

import io.vertx.core.Handler;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;

public class RequestMapper {

private final Handler<RoutingContext> defaultHandler;
private final Map<String, Handler<RoutingContext>> handlers = new HashMap<>();
private final JsonRpcRequestHandler defaultHandler;
private final Map<String, JsonRpcRequestHandler> handlers = new HashMap<>();

public RequestMapper(final Handler<RoutingContext> defaultHandler) {
public RequestMapper(final JsonRpcRequestHandler defaultHandler) {
this.defaultHandler = defaultHandler;
}

public void addHandler(final String jsonMethod, final Handler<RoutingContext> requestHandler) {
public void addHandler(final String jsonMethod, final JsonRpcRequestHandler requestHandler) {
handlers.put(jsonMethod, requestHandler);
}

public Handler<RoutingContext> getMatchingHandler(final JsonObject bodyJson) {
final String method = bodyJson.getString("method");
public JsonRpcRequestHandler getMatchingHandler(final String method) {
return handlers.getOrDefault(method, defaultHandler);
}
}
Loading

0 comments on commit f583eca

Please sign in to comment.