/* * Copyright (C) 2015 SoftIndex LLC. * * 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 io.datakernel.rpc.server; import io.datakernel.async.ResultCallback; import io.datakernel.eventloop.Eventloop; import io.datakernel.exception.ParseException; import io.datakernel.jmx.*; import io.datakernel.rpc.protocol.RpcMessage; import io.datakernel.rpc.protocol.RpcRemoteException; import io.datakernel.rpc.protocol.RpcStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.InetSocketAddress; import java.util.Map; public final class RpcServerConnection implements RpcStream.Listener, JmxRefreshable { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final Eventloop eventloop; private final RpcServer rpcServer; private final RpcStream stream; private final Map<Class<?>, RpcRequestHandler<?, ?>> handlers; private int activeRequests; private boolean readEndOfStream; // jmx private final InetSocketAddress remoteAddress; private final ExceptionStats lastRequestHandlingException = ExceptionStats.create(); private final ValueStats requestHandlingTime = ValueStats.create(RpcServer.SMOOTHING_WINDOW); private EventStats successfulRequests = EventStats.create(RpcServer.SMOOTHING_WINDOW); private EventStats failedRequests = EventStats.create(RpcServer.SMOOTHING_WINDOW); private boolean monitoring = false; protected RpcServerConnection(Eventloop eventloop, RpcServer rpcServer, InetSocketAddress remoteAddress, Map<Class<?>, RpcRequestHandler<?, ?>> handlers, RpcStream stream) { this.eventloop = eventloop; this.rpcServer = rpcServer; this.stream = stream; this.handlers = handlers; this.readEndOfStream = false; // jmx this.remoteAddress = remoteAddress; } @SuppressWarnings("unchecked") private void apply(Object request, ResultCallback<Object> callback) { RpcRequestHandler requestHandler = handlers.get(request.getClass()); if (requestHandler == null) { callback.setException(new ParseException("Failed to process request " + request)); return; } requestHandler.run(request, callback); } @Override public void onData(final RpcMessage message) { incrementActiveRequests(); final int cookie = message.getCookie(); final long startTime = monitoring ? System.currentTimeMillis() : 0; final Object messageData = message.getData(); apply(messageData, new ResultCallback<Object>() { @Override public void onResult(Object result) { // jmx updateProcessTime(); successfulRequests.recordEvent(); rpcServer.getSuccessfulRequests().recordEvent(); stream.sendMessage(RpcMessage.of(cookie, result)); decrementActiveRequest(); } @Override public void onException(Exception exception) { // jmx updateProcessTime(); lastRequestHandlingException.recordException(exception, messageData); rpcServer.getLastRequestHandlingException().recordException(exception, messageData); failedRequests.recordEvent(); rpcServer.getFailedRequests().recordEvent(); stream.sendMessage(RpcMessage.of(cookie, new RpcRemoteException(exception))); decrementActiveRequest(); logger.warn("Exception while process request ID {}", cookie, exception); } private void updateProcessTime() { if (startTime == 0) return; int value = (int) (System.currentTimeMillis() - startTime); requestHandlingTime.recordValue(value); rpcServer.getRequestHandlingTime().recordValue(value); } }); } private void incrementActiveRequests() { activeRequests++; } private void decrementActiveRequest() { activeRequests--; if (readEndOfStream && activeRequests == 0) { stream.sendEndOfStream(); onClosed(); } } public void onClosed() { rpcServer.remove(this); } @Override public void onClosedWithError(Throwable exception) { onClosed(); // jmx String causedAddress = "Remote address: " + remoteAddress.getAddress().toString(); logger.error("Protocol error. " + causedAddress, exception); rpcServer.getLastProtocolError().recordException(exception, causedAddress); } @Override public void onReadEndOfStream() { readEndOfStream = true; if (activeRequests == 0) { stream.sendEndOfStream(); onClosed(); } } public void close() { stream.sendCloseMessage(); } // jmx public void startMonitoring() { monitoring = true; } public void stopMonitoring() { monitoring = false; } @JmxAttribute public boolean isOverloaded() { return stream.isOverloaded(); } @JmxAttribute public EventStats getSuccessfulRequests() { return successfulRequests; } @JmxAttribute public EventStats getFailedRequests() { return failedRequests; } @JmxAttribute public ValueStats getRequestHandlingTime() { return requestHandlingTime; } @JmxAttribute public ExceptionStats getLastRequestHandlingException() { return lastRequestHandlingException; } @JmxAttribute public String getRemoteAddress() { return remoteAddress.toString(); } @Override public void refresh(long timestamp) { successfulRequests.refresh(timestamp); failedRequests.refresh(timestamp); requestHandlingTime.refresh(timestamp); } }