/** * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG * 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 com.sixt.service.framework.rpc; import com.google.gson.JsonArray; import com.google.inject.Inject; import com.google.protobuf.Message; import com.sixt.service.framework.OrangeContext; import com.sixt.service.framework.json.JsonRpcRequest; import com.sixt.service.framework.json.JsonRpcResponse; import com.sixt.service.framework.protobuf.ProtobufRpcRequest; import com.sixt.service.framework.protobuf.ProtobufRpcResponse; import com.sixt.service.framework.protobuf.ProtobufUtil; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.util.BytesContentProvider; import org.eclipse.jetty.client.util.StringContentProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; import static com.sixt.service.framework.jetty.RpcServlet.TYPE_JSON; import static com.sixt.service.framework.jetty.RpcServlet.TYPE_OCTET; /** * Interface to call a method on a remote service * TODO: add asynchronous call support * To make multiple simultaneous calls to multiple services, utilize async calls and * https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html (allOf) */ public class RpcClient<RESPONSE extends Message> { private static final Logger logger = LoggerFactory.getLogger(RpcClient.class); private final LoadBalancer loadBalancer; private final String serviceName; private final String methodName; private Class<RESPONSE> responseClass; private int retries; private int timeout; @Inject public RpcClient(LoadBalancer loadBalancer, String serviceName, String methodName, int retries, int timeout, Class<RESPONSE> responseClass) { this.loadBalancer = loadBalancer; this.serviceName = serviceName; this.methodName = methodName; this.retries = retries; this.timeout = timeout; this.responseClass = responseClass; } /** * @deprecated use {@link #callSynchronous(JsonArray, OrangeContext)} instead and make sure to always pass the {@link OrangeContext} */ @Deprecated public String callSynchronous(JsonArray params) throws RpcCallException { return callSynchronous(params, null); } public String callSynchronous(JsonArray params, OrangeContext orangeContext) throws RpcCallException { HttpClientWrapper clientWrapper = loadBalancer.getHttpClientWrapper(); HttpRequestWrapper balancedPost = clientWrapper.createHttpPost(this); //set custom headers if (orangeContext != null) { orangeContext.getProperties().forEach(balancedPost::setHeader); } balancedPost.setHeader("Content-type", TYPE_JSON); //TODO: fix: Temporary workaround below until go services are more http compliant balancedPost.setHeader("Connection", "close"); JsonRpcRequest jsonRequest = new JsonRpcRequest(null, methodName, params); String json = jsonRequest.toString(); balancedPost.setContentProvider(new StringContentProvider(json)); logger.debug("Sending request of size {}", json.length()); ContentResponse rpcResponse = clientWrapper.execute(balancedPost, new JsonRpcCallExceptionDecoder(), orangeContext); String rawResponse = rpcResponse.getContentAsString(); logger.debug("Json response from the service: {}", rawResponse); return JsonRpcResponse.fromString(rawResponse).getResult().getAsString(); } /** * @deprecated use {@link #callSynchronous(Message, OrangeContext)} instead and make sure to always pass the {@link OrangeContext} */ @Deprecated public RESPONSE callSynchronous(Message request) throws RpcCallException { return callSynchronous(request, null); } public RESPONSE callSynchronous(Message request, OrangeContext orangeContext) throws RpcCallException { HttpClientWrapper clientWrapper = loadBalancer.getHttpClientWrapper(); HttpRequestWrapper balancedPost = clientWrapper.createHttpPost(this); //set custom headers if (orangeContext != null) { orangeContext.getProperties().forEach(balancedPost::setHeader); } balancedPost.setHeader("Content-type", TYPE_OCTET); //TODO: fix: Temporary workaround below until go services are more http compliant balancedPost.setHeader("Connection", "close"); ProtobufRpcRequest pbRequest = new ProtobufRpcRequest(methodName, request); byte[] protobufData = pbRequest.getProtobufData(); balancedPost.setContentProvider(new BytesContentProvider(protobufData)); logger.debug("Sending request of size {}", protobufData.length); ContentResponse rpcResponse = clientWrapper.execute(balancedPost, new ProtobufRpcCallExceptionDecoder(), orangeContext); byte[] data = rpcResponse.getContent(); logger.debug("Received a proto response of size: {}", data.length); return ProtobufUtil.byteArrayToProtobuf( new ProtobufRpcResponse(data).getPayloadData(), responseClass); } public LoadBalancer getLoadBalancer() { return loadBalancer; } public String getServiceName() { return serviceName; } public String getMethodName() { return methodName; } public String getServiceMethodName() { return serviceName + "." + methodName; } public int getRetries() { return retries; } public void setRetries(int retries) { this.retries = retries; } public int getTimeout() { return timeout; } public void setTimeout(int timeout) { this.timeout = timeout; } }