MSPF-16: Client and server part

This commit is contained in:
Vladimir Pankrashkin 2016-05-12 00:58:03 +03:00
parent d3bb9f1f73
commit d97847f4e4
89 changed files with 3106 additions and 295 deletions

View File

@ -1,3 +1,3 @@
# rpc
# com.rbkmoney.woody.rpc
Java реализация [Библиотеки RPC вызовов для общения между микросервисами](http://52.29.202.218/scrapyard/rpc-lib/).
Java реализация [Библиотеки RPC вызовов для общения между микросервисами](http://52.29.202.218/scrapyard/com.rbkmoney.woody.rpc-lib/).

View File

@ -8,6 +8,9 @@
<artifactId>woody</artifactId>
<version>1.0.0</version>
<description>Java implementation for Woody spec</description>
<properties>
<api-version>1.0.0</api-version>
</properties>
<modules>
<module>woody-api</module>

View File

@ -10,6 +10,8 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>woody-api</artifactId>
<version>${api-version}</version>
<packaging>jar</packaging>
<dependencies>
<dependency>

View File

@ -2,13 +2,9 @@ package com.rbkmoney.woody.api;
import com.rbkmoney.woody.api.event.ClientEventListener;
import com.rbkmoney.woody.api.generator.IdGenerator;
import com.rbkmoney.woody.api.provider.ClientProviderControl;
import com.rbkmoney.woody.api.proxy.MethodCallTracer;
import com.rbkmoney.woody.api.proxy.ProxyFactory;
import com.rbkmoney.woody.api.trace.context.ContextTracer;
import com.rbkmoney.woody.api.trace.context.EventListenerTracer;
import com.rbkmoney.woody.api.trace.context.MetadataTracer;
import com.rbkmoney.woody.api.trace.context.TraceContext;
import com.rbkmoney.woody.api.trace.context.*;
import java.net.URI;
@ -38,53 +34,78 @@ public abstract class AbstractClientBuilder implements ClientBuilder {
return this;
}
@Override
public <T> T build(Class<T> clientInterface) {
return createProxyClient(clientInterface, null);
protected URI getAddress() {
return address;
}
protected ClientEventListener getEventListener() {
return eventListener;
}
protected IdGenerator getIdGenerator() {
return idGenerator;
}
@Override
public <T> T build(Class<T> clientInterface, ClientProviderControl providerControl) {
T target = null;
return createProxyClient(clientInterface, target);
public <T> T build(Class<T> clientInterface) {
try {
T target = createProviderClient(clientInterface);
return createProxyClient(clientInterface, target);
} catch (Exception e) {
throw new WoodyInstantiationException(e);
}
}
protected <T> T createProxyClient(Class<T> clientInterface, T target) {
ProxyBuilder proxyBuilder = new ProxyBuilder();
proxyBuilder.setIdGenerator(idGenerator);
proxyBuilder.setStartEventListener(getEventStartListener(eventListener));
proxyBuilder.setEndEventListener(getEventEndListener(eventListener));
proxyBuilder.setErrEventListener(getErrorListener(eventListener));
proxyBuilder.setStartEventPhases(ProxyBuilder.BEFORE_CALL_START);
proxyBuilder.setEndEventPhases(ProxyBuilder.BEFORE_CONTEXT_DESTROY);
return proxyBuilder.build(clientInterface, target);
return createProxyBuilder(clientInterface).build(clientInterface, target);
}
abstract protected Runnable getErrorListener(ClientEventListener eventListener);
protected ProxyBuilder createProxyBuilder(Class clientInterface) {
ProxyBuilder proxyBuilder = new ProxyBuilder();
proxyBuilder.setIdGenerator(idGenerator);
proxyBuilder.setStartEventListener(getOnCallStartEventListener());
proxyBuilder.setEndEventListener(getOnCallEndEventListener());
proxyBuilder.setErrEventListener(getErrorListener());
proxyBuilder.setMetadataExtender(getOnCallMetadataExtender(clientInterface));
return proxyBuilder;
}
abstract protected Runnable getEventStartListener(ClientEventListener eventListener);
abstract protected Runnable getErrorListener();
abstract protected Runnable getEventEndListener(ClientEventListener eventListener);
abstract protected Runnable getOnCallStartEventListener();
abstract <T> T createProxyTarget(Class<T> clientInterface, ClientEventListener listener, ClientProviderControl providerControl);
abstract protected Runnable getOnSendEventListener();
abstract protected Runnable getOnReceiveEventListener();
abstract protected Runnable getOnCallEndEventListener();
abstract protected MethodCallTracer getOnCallMetadataExtender(Class clientInterface);
abstract protected <T> T createProviderClient(Class<T> clientInterface);
protected static class ProxyBuilder {
private static final int AFTER_CONTEXT_INIT = 0b01;
private static final int BEFORE_CONTEXT_DESTROY = 0b10;
private static final int BEFORE_CALL_START = 0b100;
private static final int AFTER_CALL_END = 0b1000;
public static final int EVENT_DISABLE = 0b0;
public static final int EVENT_AFTER_CONTEXT_INIT = 0b01;
public static final int EVENT_BEFORE_CONTEXT_DESTROY = 0b10;
public static final int EVENT_BEFORE_CALL_START = 0b100;
public static final int EVENT_AFTER_CALL_END = 0b1000;
private int startEventPhases;
private int endEventPhases;
private int errorEventPhases;
private boolean allowObjectOverriding = false;
private final Runnable stub = () -> {
private final Runnable listenerStub = () -> {
};
private final MethodCallTracer extenderStub = new EmptyTracer();
private Runnable startEventListener;
private Runnable endEventListener;
private Runnable errEventListener;
private IdGenerator idGenerator;
private MethodCallTracer metadataExtender;
public void setStartEventListener(Runnable startEventListener) {
this.startEventListener = startEventListener;
@ -102,6 +123,10 @@ public abstract class AbstractClientBuilder implements ClientBuilder {
this.idGenerator = idGenerator;
}
public void setMetadataExtender(MethodCallTracer metadataExtender) {
this.metadataExtender = metadataExtender;
}
public void setAllowObjectOverriding(boolean allowObjectOverriding) {
this.allowObjectOverriding = allowObjectOverriding;
}
@ -114,25 +139,8 @@ public abstract class AbstractClientBuilder implements ClientBuilder {
endEventPhases = phases;
}
public ProxyFactory createProxyFactory() {
return new ProxyFactory(createMethodCallTracer(), allowObjectOverriding);
}
public MethodCallTracer createMethodCallTracer() {
return new ContextTracer(createTraceContext(), createEventTracer());
}
public TraceContext createTraceContext() {
return TraceContext.forClient(idGenerator,
hasFlag(AFTER_CONTEXT_INIT, startEventPhases) ? startEventListener : stub,
hasFlag(BEFORE_CONTEXT_DESTROY, endEventPhases) ? endEventListener : startEventListener);
}
public EventListenerTracer createEventTracer() {
return new EventListenerTracer(MetadataTracer.forClient(),
hasFlag(BEFORE_CALL_START, startEventPhases) ? startEventListener : stub,
hasFlag(AFTER_CALL_END, endEventPhases) ? endEventListener : stub,
errEventListener);
public void setErrorEventPhases(int phases) {
this.errorEventPhases = phases;
}
public <T> T build(Class<T> clientInterface, T target) {
@ -140,6 +148,31 @@ public abstract class AbstractClientBuilder implements ClientBuilder {
return proxyFactory.getInstance(clientInterface, target);
}
protected ProxyFactory createProxyFactory() {
return new ProxyFactory(createMethodCallTracer(), allowObjectOverriding);
}
protected MethodCallTracer createMethodCallTracer() {
return new ContextTracer(createTraceContext(), createEventTracer());
}
protected TraceContext createTraceContext() {
return TraceContext.forClient(idGenerator,
hasFlag(EVENT_AFTER_CONTEXT_INIT, startEventPhases) ? startEventListener : listenerStub,
hasFlag(EVENT_BEFORE_CONTEXT_DESTROY, endEventPhases) ? endEventListener : listenerStub,
hasFlag(EVENT_BEFORE_CONTEXT_DESTROY, errorEventPhases) ? errEventListener : listenerStub);
}
protected MethodCallTracer createEventTracer() {
return new CompositeTracer(MetadataTracer.forClient(),
metadataExtender == null ? extenderStub : metadataExtender,
new EventTracer(
hasFlag(EVENT_BEFORE_CALL_START, startEventPhases) ? startEventListener : listenerStub,
hasFlag(EVENT_AFTER_CALL_END, endEventPhases) ? endEventListener : listenerStub,
errEventListener)
);
}
private boolean hasFlag(int test, int flags) {
return (test & flags) != 0;
}

View File

@ -0,0 +1,164 @@
package com.rbkmoney.woody.api;
import com.rbkmoney.woody.api.event.ServiceEventListener;
import com.rbkmoney.woody.api.generator.IdGenerator;
import com.rbkmoney.woody.api.proxy.MethodCallTracer;
import com.rbkmoney.woody.api.proxy.ProxyFactory;
import com.rbkmoney.woody.api.trace.context.CompositeTracer;
import com.rbkmoney.woody.api.trace.context.EmptyTracer;
import com.rbkmoney.woody.api.trace.context.EventTracer;
import com.rbkmoney.woody.api.trace.context.MetadataTracer;
/**
* Created by vpankrashkin on 10.05.16.
*/
public abstract class AbstractServiceBuilder<Service> implements ServiceBuilder<Service> {
private ServiceEventListener eventListener;
private IdGenerator idGenerator;
@Override
public ServiceBuilder withEventListener(ServiceEventListener listener) {
this.eventListener = listener;
return this;
}
@Override
public ServiceBuilder withIdGenerator(IdGenerator generator) {
this.idGenerator = generator;
return this;
}
protected IdGenerator getIdGenerator() {
return idGenerator;
}
@Override
public <T> Service build(Class<T> serviceInterface, T serviceHandler) {
try {
T target = createProxyService(serviceInterface, serviceHandler);
return createProviderService(serviceInterface, target);
} catch (Exception e) {
throw new WoodyInstantiationException(e);
}
}
protected ServiceEventListener getEventListener() {
return eventListener;
}
abstract protected Runnable getErrorListener();
abstract protected Runnable getOnCallStartEventListener();
abstract protected Runnable getOnSendEventListener();
abstract protected Runnable getOnReceiveEventListener();
abstract protected Runnable getOnCallEndEventListener();
abstract protected MethodCallTracer getOnCallMetadataExtender(Class serviceInterface);
abstract protected <T> Service createProviderService(Class<T> serviceInterface, T handler);
protected <T> T createProxyService(Class<T> serviceInterface, T handler) {
return createProxyBuilder(serviceInterface).build(serviceInterface, handler);
}
protected ProxyBuilder createProxyBuilder(Class serviceInterface) {
ProxyBuilder proxyBuilder = new ProxyBuilder();
proxyBuilder.setIdGenerator(idGenerator);
proxyBuilder.setStartEventListener(getOnCallStartEventListener());
proxyBuilder.setEndEventListener(getOnCallEndEventListener());
proxyBuilder.setErrEventListener(getErrorListener());
proxyBuilder.setMetadataExtender(getOnCallMetadataExtender(serviceInterface));
return proxyBuilder;
}
protected static class ProxyBuilder {
public static final int EVENT_DISABLE = 0b0;
public static final int EVENT_BEFORE_CALL_START = 0b100;
public static final int EVENT_AFTER_CALL_END = 0b1000;
private int startEventPhases;
private int endEventPhases;
private int errorEventPhases;
private boolean allowObjectOverriding = false;
private final Runnable listenerStub = () -> {
};
private final MethodCallTracer extenderStub = new EmptyTracer();
private Runnable startEventListener;
private Runnable endEventListener;
private Runnable errEventListener;
private IdGenerator idGenerator;
private MethodCallTracer metadataExtender;
public void setStartEventListener(Runnable startEventListener) {
this.startEventListener = startEventListener;
}
public void setEndEventListener(Runnable endEventListener) {
this.endEventListener = endEventListener;
}
public void setErrEventListener(Runnable errEventListener) {
this.errEventListener = errEventListener;
}
public void setIdGenerator(IdGenerator idGenerator) {
this.idGenerator = idGenerator;
}
public void setMetadataExtender(MethodCallTracer metadataExtender) {
this.metadataExtender = metadataExtender;
}
public void setAllowObjectOverriding(boolean allowObjectOverriding) {
this.allowObjectOverriding = allowObjectOverriding;
}
public void setStartEventPhases(int phases) {
startEventPhases = phases;
}
public void setEndEventPhases(int phases) {
endEventPhases = phases;
}
public void setErrorEventPhases(int phases) {
this.errorEventPhases = phases;
}
public <T> T build(Class<T> serviceInterface, T target) {
ProxyFactory proxyFactory = createProxyFactory();
return proxyFactory.getInstance(serviceInterface, target);
}
protected ProxyFactory createProxyFactory() {
return new ProxyFactory(createMethodCallTracer(), allowObjectOverriding);
}
protected MethodCallTracer createMethodCallTracer() {
return createEventTracer();
}
protected MethodCallTracer createEventTracer() {
return new CompositeTracer(MetadataTracer.forServer(),
metadataExtender == null ? extenderStub : metadataExtender,
new EventTracer(
hasFlag(EVENT_BEFORE_CALL_START, startEventPhases) ? startEventListener : listenerStub,
hasFlag(EVENT_AFTER_CALL_END, endEventPhases) ? endEventListener : listenerStub,
errEventListener)
);
}
private boolean hasFlag(int test, int flags) {
return (test & flags) != 0;
}
}
}

View File

@ -2,7 +2,6 @@ package com.rbkmoney.woody.api;
import com.rbkmoney.woody.api.event.ClientEventListener;
import com.rbkmoney.woody.api.generator.IdGenerator;
import com.rbkmoney.woody.api.provider.ClientProviderControl;
import java.net.URI;
@ -17,6 +16,4 @@ public interface ClientBuilder {
ClientBuilder withIdGenerator(IdGenerator generator);
<T> T build(Class<T> clientInterface);
<T> T build(Class<T> clientInterface, ClientProviderControl configurator);
}

View File

@ -1,18 +1,15 @@
package com.rbkmoney.woody.api;
import com.rbkmoney.woody.api.event.ServiceEventListener;
import com.rbkmoney.woody.api.generator.IdGenerator;
import java.beans.EventHandler;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ServiceBuilder {
ServiceBuilder withEventHandler(EventHandler handler);
public interface ServiceBuilder<Service> {
ServiceBuilder withEventListener(ServiceEventListener listener);
ServiceBuilder withIdGenerator(IdGenerator generator);
<T> T build(Class<T> serviceInterface);
<T> T build(Class<T> serviceInterface, ServiceConfigurator configurator);
<T> Service build(Class<T> serviceInterface, T serviceHandler);
}

View File

@ -1,8 +0,0 @@
package com.rbkmoney.woody.api;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ServiceConfigurator<T> {
void configure(T serviceProvider);
}

View File

@ -0,0 +1,26 @@
package com.rbkmoney.woody.api;
/**
* Created by vpankrashkin on 05.05.16.
*/
public class WoodyInstantiationException extends RuntimeException {
public WoodyInstantiationException() {
super();
}
public WoodyInstantiationException(String message) {
super(message);
}
public WoodyInstantiationException(String message, Throwable cause) {
super(message, cause);
}
public WoodyInstantiationException(Throwable cause) {
super(cause);
}
protected WoodyInstantiationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,9 @@
package com.rbkmoney.woody.api.event;
/**
* Created by vpankrashkin on 05.05.16.
*/
public enum CallType {
CALL,
CAST
}

View File

@ -0,0 +1,19 @@
package com.rbkmoney.woody.api.event;
import com.rbkmoney.woody.api.trace.ContextSpan;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 06.05.16.
*/
public class ClientEvent extends Event {
public ClientEvent(TraceData traceData) {
super(traceData);
}
@Override
public ContextSpan getActiveSpan() {
return getTraceData().getClientSpan();
}
}

View File

@ -1,12 +1,9 @@
package com.rbkmoney.woody.api.event;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ClientEventListener extends EventListener<ClientEventType, ErrorType> {
void notifyEvent(ClientEventType eventType, TraceData traceData);
public interface ClientEventListener extends EventListener<ClientEvent> {
void notifyEvent(ClientEvent event);
void notifyError(ErrorType errorType, TraceData traceData);
}

View File

@ -2,9 +2,28 @@ package com.rbkmoney.woody.api.event;
/**
* Created by vpankrashkin on 22.04.16.
* <p>
* Custom error call constants, specific for
*/
public enum ErrorType {
UNKNOWN_CALL,
TRANSPORT,
ERROR_CALL
/**
* Any thrift errors (protocol, transport, etc).
*/
PROVIDER_ERROR,
/**
* Error which is registered in service method declaration.
*/
APPLICATION_KNOWN_ERROR,
/**
* Any other error which is not registered for calling method and doesn't refer to thrift errors.
*/
APPLICATION_UNKNOWN_ERROR,
/**
* Any other error
*/
OTHER
}

View File

@ -0,0 +1,81 @@
package com.rbkmoney.woody.api.event;
import com.rbkmoney.woody.api.trace.*;
/**
* Created by vpankrashkin on 06.05.16.
*/
public abstract class Event {
private final TraceData traceData;
public Event(TraceData traceData) {
this.traceData = traceData;
}
public TraceData getTraceData() {
return traceData;
}
public ClientEventType getEventType() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.EVENT_TYPE);
}
public CallType getCallType() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.CALL_TYPE);
}
public String getCallName() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.CALL_NAME);
}
public Object[] getCallArguments() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.CALL_ARGUMENTS);
}
public Object getCallResult() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.CALL_RESULT);
}
public ErrorType getErrorType() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.ERROR_TYPE);
}
public String getErrorName() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.ERROR_NAME);
}
public String getErrorMessage() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.ERROR_MESSAGE);
}
public String getSpanId() {
return getActiveSpan().getSpan().getId();
}
public String getParentId() {
return getActiveSpan().getSpan().getParentId();
}
public String getTraceId() {
return getActiveSpan().getSpan().getTraceId();
}
public long getTimeStamp() {
return getActiveSpan().getSpan().getTimestamp();
}
public long getDuration() {
return getActiveSpan().getSpan().getDuration();
}
public Endpoint getEndpoint() {
return getActiveSpan().getMetadata().getValue(MetadataProperties.CALL_ENDPOINT);
}
public boolean isSuccessfullCall() {
return !ContextUtils.hasCallErrors(getActiveSpan());
}
public abstract ContextSpan getActiveSpan();
}

View File

@ -1,12 +1,8 @@
package com.rbkmoney.woody.api.event;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 25.04.16.
*/
public interface EventListener<EvnT, ErrT> {
void notifyEvent(EvnT eventType, TraceData traceData);
void notifyError(ErrT errorType, TraceData traceData);
public interface EventListener<E extends Event> {
void notifyEvent(E event);
}

View File

@ -0,0 +1,18 @@
package com.rbkmoney.woody.api.event;
import com.rbkmoney.woody.api.trace.ContextSpan;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 06.05.16.
*/
public class ServiceEvent extends Event {
public ServiceEvent(TraceData traceData) {
super(traceData);
}
@Override
public ContextSpan getActiveSpan() {
return getTraceData().getServiceSpan();
}
}

View File

@ -1,12 +1,8 @@
package com.rbkmoney.woody.api.event;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 25.04.16.
*/
public interface ServiceEventListener extends EventListener<ServiceEventType, ErrorType> {
void notifyEvent(ServiceEventType eventType, TraceData traceData);
void notifyError(ErrorType errorType, TraceData traceData);
public interface ServiceEventListener extends EventListener<ServiceEvent> {
void notifyEvent(ServiceEvent serviceEvent);
}

View File

@ -0,0 +1,27 @@
package com.rbkmoney.woody.api.interceptor;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 04.05.16.
*/
public class BasicCommonInterceptor<ReqProvider, RespProvider> implements CommonInterceptor<ReqProvider, RespProvider> {
private RequestInterceptor<ReqProvider> requestInterceptor;
private ResponseInterceptor<RespProvider> responseInterceptor;
public BasicCommonInterceptor(RequestInterceptor<ReqProvider> requestInterceptor, ResponseInterceptor<RespProvider> responseInterceptor) {
this.requestInterceptor = requestInterceptor == null ? new EmptyCommonInterceptor() : requestInterceptor;
this.responseInterceptor = responseInterceptor == null ? new EmptyCommonInterceptor() : responseInterceptor;
}
@Override
public boolean interceptRequest(TraceData traceData, ReqProvider providerContext, Object... contextParams) {
return requestInterceptor.interceptRequest(traceData, providerContext, contextParams);
}
@Override
public boolean interceptResponse(TraceData traceData, RespProvider providerContext, Object... contextParams) {
return responseInterceptor.interceptResponse(traceData, providerContext, contextParams);
}
}

View File

@ -0,0 +1,7 @@
package com.rbkmoney.woody.api.interceptor;
/**
* Created by vpankrashkin on 28.04.16.
*/
public interface CommonInterceptor<ReqProvider, RespProvider> extends RequestInterceptor<ReqProvider>, ResponseInterceptor<RespProvider> {
}

View File

@ -0,0 +1,50 @@
package com.rbkmoney.woody.api.interceptor;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 28.04.16.
*/
public class CompositeInterceptor<ReqProvider, RespProvider> implements CommonInterceptor<ReqProvider, RespProvider> {
private final CommonInterceptor[] interceptors;
private final boolean breakOnError;
public CompositeInterceptor(boolean breakOnError, CommonInterceptor... interceptors) {
this.breakOnError = breakOnError;
this.interceptors = interceptors.clone();
}
public CompositeInterceptor(CommonInterceptor... interceptors) {
this(true, interceptors);
}
/*public CompositeInterceptor(Collection<? extends CommonInterceptor> interceptors) {
this(true, interceptors.stream().toArray(CommonInterceptor[]::new));
}*/
@Override
public boolean interceptRequest(TraceData traceData, ReqProvider providerContext, Object... contextParams) {
boolean successful = true;
for (int i = 0; i < interceptors.length; ++i) {
successful &= interceptors[i].interceptRequest(traceData, providerContext, contextParams);
if (!successful && breakOnError) {
return false;
}
}
return successful;
}
@Override
public boolean interceptResponse(TraceData traceData, RespProvider providerContext, Object... contextParams) {
boolean successful = true;
for (int i = 0; i < interceptors.length; ++i) {
successful &= interceptors[i].interceptResponse(traceData, providerContext, contextParams);
if (!successful && breakOnError) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,34 @@
package com.rbkmoney.woody.api.interceptor;
import com.rbkmoney.woody.api.trace.ContextUtils;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.api.trace.context.TraceContext;
/**
* Created by vpankrashkin on 10.05.16.
*/
public class ContextInterceptor<ReqProvider, RespProvider> implements CommonInterceptor<ReqProvider, RespProvider> {
private final TraceContext traceContext;
private final CommonInterceptor interceptor;
public ContextInterceptor(TraceContext traceContext, CommonInterceptor interceptor) {
this.traceContext = traceContext;
this.interceptor = interceptor;
}
@Override
public boolean interceptRequest(TraceData traceData, ReqProvider providerContext, Object... contextParams) {
traceContext.init();
return interceptor.interceptRequest(traceData, providerContext, contextParams);
}
@Override
public boolean interceptResponse(TraceData traceData, RespProvider providerContext, Object... contextParams) {
try {
interceptor.interceptResponse(traceData, providerContext, contextParams);
} finally {
traceContext.destroy(ContextUtils.hasCallErrors(traceData.getActiveSpan()));
}
return false;
}
}

View File

@ -0,0 +1,19 @@
package com.rbkmoney.woody.api.interceptor;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 05.05.16.
*/
public class EmptyCommonInterceptor<ReqProvider, RespProvider> implements CommonInterceptor<ReqProvider, RespProvider> {
@Override
public boolean interceptRequest(TraceData traceData, ReqProvider providerContext, Object... contextParams) {
return true;
}
@Override
public boolean interceptResponse(TraceData traceData, RespProvider providerContext, Object... contextParams) {
return true;
}
}

View File

@ -0,0 +1,13 @@
package com.rbkmoney.woody.api.interceptor;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface RequestInterceptor<Provider> {
/**
* @return true - if request is successfully intercepted and ready for further processing; false - if interception failed and processing must be switched to request err handling
*/
boolean interceptRequest(TraceData traceData, Provider providerContext, Object... contextParams);
}

View File

@ -0,0 +1,13 @@
package com.rbkmoney.woody.api.interceptor;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ResponseInterceptor<Provider> {
/**
* @return true - if response is successfully intercepted and ready for further processing; false - if interception failed and processing must be switched to response err handling
*/
boolean interceptResponse(TraceData traceData, Provider providerContext, Object... contextParams);
}

View File

@ -1,8 +0,0 @@
package com.rbkmoney.woody.api.provider;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ClientProviderControl<T> {
void configure(T clientProvider);
}

View File

@ -0,0 +1,37 @@
package com.rbkmoney.woody.api.provider;
import com.rbkmoney.woody.api.event.ClientEventType;
import com.rbkmoney.woody.api.event.ServiceEventType;
import com.rbkmoney.woody.api.interceptor.CommonInterceptor;
import com.rbkmoney.woody.api.trace.MetadataProperties;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 27.04.16.
*/
public class ProviderEventInterceptor<ReqProvider, RespProvider> implements CommonInterceptor<ReqProvider, RespProvider> {
private final Runnable reqListener;
private final Runnable respListener;
public ProviderEventInterceptor(Runnable reqListener, Runnable respListener) {
this.reqListener = reqListener != null ? reqListener : () -> {
};
this.respListener = respListener != null ? respListener : () -> {
};
}
@Override
public boolean interceptRequest(TraceData traceData, ReqProvider providerContext, Object... contextParams) {
traceData.getActiveSpan().getMetadata().putValue(MetadataProperties.EVENT_TYPE, traceData.isClient() ? ClientEventType.CALL_SERVICE : ServiceEventType.SERVICE_RECEIVE);
reqListener.run();
return true;
}
@Override
public boolean interceptResponse(TraceData traceData, RespProvider providerContext, Object... contextParams) {
traceData.getActiveSpan().getMetadata().putValue(MetadataProperties.EVENT_TYPE, traceData.isClient() ? ClientEventType.SERVICE_RESULT : ServiceEventType.HANDLER_RESULT);
respListener.run();
return true;
}
}

View File

@ -18,7 +18,7 @@ public class HandleMethodCallerFactory implements MethodCallerFactory {
MethodHandle mh = MethodHandles.lookup()
.findVirtual(target.getClass(), method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes())).asSpreader(Object[].class, method.getParameterCount());
return new InstanceMethodCaller() {
return new InstanceMethodCaller(method) {
@Override
public Object call(Object[] args) throws Throwable {

View File

@ -1,9 +1,21 @@
package com.rbkmoney.woody.api.proxy;
import java.lang.reflect.Method;
/**
* Created by vpankrashkin on 22.04.16.
*/
@FunctionalInterface
public interface InstanceMethodCaller {
Object call(Object[] args) throws Throwable;
public abstract class InstanceMethodCaller {
private final Method targetMethod;
public InstanceMethodCaller(Method targetMethod) {
this.targetMethod = targetMethod;
}
public Method getTargetMethod() {
return targetMethod;
}
public abstract Object call(Object[] args) throws Throwable;
}

View File

@ -9,4 +9,5 @@ public interface MethodCallTracer {
void afterCall(Object[] args, InstanceMethodCaller caller, Object result);
void callError(Object[] args, InstanceMethodCaller caller, Throwable error);
}

View File

@ -1,5 +1,6 @@
package com.rbkmoney.woody.api.proxy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
@ -9,6 +10,15 @@ public class ReflectionMethodCallerFactory implements MethodCallerFactory {
@Override
public InstanceMethodCaller getInstance(Object target, Method method) {
method.setAccessible(true);
return (args) -> method.invoke(target, args);
return new InstanceMethodCaller(method) {
@Override
public Object call(Object[] args) throws Throwable {
try {
return method.invoke(target, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
};
}
}

View File

@ -3,6 +3,6 @@ package com.rbkmoney.woody.api.trace;
/**
* Created by vpankrashkin on 21.04.16.
*/
public class ClientSpan extends AbstractSpan {
public class ClientSpan extends ContextSpan {
}

View File

@ -3,7 +3,7 @@ package com.rbkmoney.woody.api.trace;
/**
* Created by vpankrashkin on 22.04.16.
*/
public class AbstractSpan {
public class ContextSpan {
protected final Span span = new Span();
protected final Metadata metadata = new Metadata();

View File

@ -0,0 +1,64 @@
package com.rbkmoney.woody.api.trace;
import java.util.function.Function;
/**
* Created by vpankrashkin on 11.05.16.
*/
public class ContextUtils {
public static <T> T createErrIfNotIntercepted(ContextSpan span, Function<Throwable, T> errConstructor) {
Throwable err = getInterceptionError(span);
if (err != null) {
return errConstructor.apply(err);
}
return null;
}
public static Throwable getInterceptionError(ContextSpan span) {
return getMetadataParameter(span, Throwable.class, MetadataProperties.INTERCEPTION_ERROR);
}
public static void setInterceptionError(ContextSpan span, Throwable t) {
span.getMetadata().putValue(MetadataProperties.INTERCEPTION_ERROR, t);
}
public static void setCallError(ContextSpan span, Throwable t) {
span.getMetadata().putValue(MetadataProperties.CALL_ERROR, t);
}
public static Throwable getCallError(ContextSpan span) {
return getMetadataParameter(span, Throwable.class, MetadataProperties.CALL_ERROR);
}
public static boolean hasCallErrors(ContextSpan span) {
return span.getMetadata().containsKey(MetadataProperties.CALL_ERROR);
}
public static void tryThrowInterceptionError(ContextSpan span) throws Throwable {
Throwable t = getInterceptionError(span);
if (t != null) {
throw t;
}
}
public static <T> T getMetadataParameter(ContextSpan span, Class<T> targetType, String key) {
Object obj = span.getMetadata().getValue(key);
if (obj == null) {
return null;
} else if (targetType.isAssignableFrom(obj.getClass())) {
return (T) obj;
}
return null;
}
public static <T> T getContextParameter(Class<T> targetType, Object[] params, int index) {
if (params == null || params.length <= index || params[index] == null) {
return null;
}
if (targetType.isAssignableFrom(params[index].getClass())) {
return (T) params[index];
}
return null;
}
}

View File

@ -3,8 +3,8 @@ package com.rbkmoney.woody.api.trace;
/**
* Created by vpankrashkin on 21.04.16.
*/
public interface Endpoint<S, T> {
S getSource();
public interface Endpoint<T> {
String getStringValue();
T getTarget();
T getValue();
}

View File

@ -25,6 +25,10 @@ public class Metadata {
return (T) values.put(key, value);
}
public boolean containsKey(String key) {
return values.containsKey(key);
}
public Collection<String> getKeys() {
return values.keySet();
}

View File

@ -4,10 +4,22 @@ package com.rbkmoney.woody.api.trace;
* Created by vpankrashkin on 25.04.16.
*/
public class MetadataProperties {
public static final String CALL_ARGUMENTS = "md_call_arguments";
public static final String INSTANCE_METHOD_CALLER = "md_instance_method_caller";
public static final String CALL_ARGUMENTS = "md_call_arguments";
public static final String CALL_RESULT = "md_call_result";
public static final String CALL_ERROR = "md_call_error";
public static final String CALL_NAME = "md_call_name";
public static final String CALL_TYPE = "md_call_type";
public static final String CALL_ENDPOINT = "md_call_endpoint";
public static final String EVENT_TYPE = "md_event_type";
public static final String ERROR_TYPE = "md_error_type";
public static final String ERROR_NAME = "md_error_name";
public static final String ERROR_MESSAGE = "md_error_message";
public static final String INTERCEPTION_ERROR = "md_interception_error";
}

View File

@ -5,25 +5,16 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by vpankrashkin on 21.04.16.
*/
public class ServerSpan extends AbstractSpan {
private Endpoint endpoint;
public class ServiceSpan extends ContextSpan {
private final AtomicInteger counter = new AtomicInteger();
public Endpoint getEndpoint() {
return endpoint;
}
public void setEndpoint(Endpoint endpoint) {
this.endpoint = endpoint;
}
public AtomicInteger getCounter() {
return counter;
}
public void reset() {
super.reset();
endpoint = null;
counter.set(0);
}
}

View File

@ -4,19 +4,19 @@ package com.rbkmoney.woody.api.trace;
* Created by vpankrashkin on 21.04.16.
*/
public class TraceData {
private ClientSpan clientSpan = new ClientSpan();
private ServerSpan serverSpan = new ServerSpan();
private final ClientSpan clientSpan = new ClientSpan();
private final ServiceSpan serviceSpan = new ServiceSpan();
public ClientSpan getClientSpan() {
return clientSpan;
}
public ServerSpan getServerSpan() {
return serverSpan;
public ServiceSpan getServiceSpan() {
return serviceSpan;
}
/**
* Checks if {@link ServerSpan} is filled to determine root:
* Checks if {@link ServiceSpan} is filled to determine root:
* - request initialized by server: span must be filled by server with data referred from client: has filled server span, it's not root by default -> false
* - request initialized by client, produced by any server request handling event: has filled server span, it's not root -> false
* - request initialized by client, not produced by any server request handling event: server span not filled, it's root -> true
@ -24,7 +24,7 @@ public class TraceData {
* @return true - if root call is running; false - otherwise
*/
public boolean isRoot() {
return !serverSpan.isFilled();
return !serviceSpan.isFilled();
}
/**
@ -48,19 +48,19 @@ public class TraceData {
* @return true - if call is running as root client or child client call for server request handling; false - if call is running in server request handing
*/
public boolean isClient() {
return serverSpan.isFilled() ? clientSpan.isFilled() : true;
return serviceSpan.isFilled() ? clientSpan.isFilled() : true;
}
public AbstractSpan getActiveSpan() {
return isClient() ? clientSpan : serverSpan;
public ContextSpan getActiveSpan() {
return isClient() ? clientSpan : serviceSpan;
}
public AbstractSpan getSpan(boolean isClient) {
return isClient ? clientSpan : serverSpan;
public ContextSpan getSpan(boolean isClient) {
return isClient ? clientSpan : serviceSpan;
}
public void reset() {
clientSpan.reset();
serverSpan.reset();
serviceSpan.reset();
}
}

View File

@ -0,0 +1,43 @@
package com.rbkmoney.woody.api.trace.context;
import com.rbkmoney.woody.api.proxy.InstanceMethodCaller;
import com.rbkmoney.woody.api.proxy.MethodCallTracer;
import java.util.Arrays;
import java.util.Collection;
/**
* Created by vpankrashkin on 04.05.16.
*/
public class CompositeTracer implements MethodCallTracer {
private final MethodCallTracer[] tracers;
public CompositeTracer(MethodCallTracer... callTracers) {
this(Arrays.asList(callTracers));
}
public CompositeTracer(Collection<? extends MethodCallTracer> tracers) {
this.tracers = tracers.stream().toArray(MethodCallTracer[]::new);
}
@Override
public void beforeCall(Object[] args, InstanceMethodCaller caller) {
for (int i = 0; i < tracers.length; ++i) {
tracers[i].beforeCall(args, caller);
}
}
@Override
public void afterCall(Object[] args, InstanceMethodCaller caller, Object result) {
for (int i = 0; i < tracers.length; ++i) {
tracers[i].afterCall(args, caller, result);
}
}
@Override
public void callError(Object[] args, InstanceMethodCaller caller, Throwable error) {
for (int i = 0; i < tracers.length; ++i) {
tracers[i].callError(args, caller, error);
}
}
}

View File

@ -5,6 +5,8 @@ import com.rbkmoney.woody.api.proxy.MethodCallTracer;
/**
* Created by vpankrashkin on 26.04.16.
*
* Used to control context lifecycle on interface method call and return
*/
public class ContextTracer implements MethodCallTracer {
private final TraceContext traceContext;
@ -35,7 +37,7 @@ public class ContextTracer implements MethodCallTracer {
try {
targetTracer.callError(args, caller, error);
} finally {
traceContext.destroy();
traceContext.destroy(true);
}
}
}

View File

@ -0,0 +1,24 @@
package com.rbkmoney.woody.api.trace.context;
import com.rbkmoney.woody.api.proxy.InstanceMethodCaller;
import com.rbkmoney.woody.api.proxy.MethodCallTracer;
/**
* Created by vpankrashkin on 04.05.16.
*/
public class EmptyTracer implements MethodCallTracer {
@Override
public void beforeCall(Object[] args, InstanceMethodCaller caller) {
}
@Override
public void afterCall(Object[] args, InstanceMethodCaller caller, Object result) {
}
@Override
public void callError(Object[] args, InstanceMethodCaller caller, Throwable error) {
}
}

View File

@ -7,22 +7,17 @@ import com.rbkmoney.woody.api.proxy.MethodCallTracer;
/**
* Created by vpankrashkin on 25.04.16.
*/
public class EventListenerTracer implements MethodCallTracer {
public class EventTracer implements MethodCallTracer {
private final MethodCallTracer callTracer;
private final Runnable beforeCallListener;
private final Runnable afterCallListener;
private final Runnable errListener;
public EventListenerTracer(MethodCallTracer callTracer) {
this(callTracer, null, null, null);
public EventTracer() {
this(null, null, null);
}
public EventListenerTracer(MethodCallTracer callTracer, Runnable beforeCallListener, Runnable afterCallListener, Runnable errListener) {
if (callTracer == null) {
throw new NullPointerException("Tracer or listener cannot be null");
}
this.callTracer = callTracer;
public EventTracer(Runnable beforeCallListener, Runnable afterCallListener, Runnable errListener) {
this.beforeCallListener = beforeCallListener != null ? beforeCallListener : () -> {
};
this.afterCallListener = afterCallListener != null ? afterCallListener : () -> {
@ -33,19 +28,16 @@ public class EventListenerTracer implements MethodCallTracer {
@Override
public void beforeCall(Object[] args, InstanceMethodCaller caller) {
callTracer.beforeCall(args, caller);
beforeCallListener.run();
}
@Override
public void afterCall(Object[] args, InstanceMethodCaller caller, Object result) {
callTracer.afterCall(args, caller, result);
afterCallListener.run();
}
@Override
public void callError(Object[] args, InstanceMethodCaller caller, Throwable error) {
callTracer.callError(args, caller, error);
errListener.run();
}
}

View File

@ -4,6 +4,8 @@ import com.rbkmoney.woody.api.event.ClientEventType;
import com.rbkmoney.woody.api.event.ServiceEventType;
import com.rbkmoney.woody.api.proxy.InstanceMethodCaller;
import com.rbkmoney.woody.api.proxy.MethodCallTracer;
import com.rbkmoney.woody.api.trace.ContextSpan;
import com.rbkmoney.woody.api.trace.ContextUtils;
import com.rbkmoney.woody.api.trace.Metadata;
import com.rbkmoney.woody.api.trace.MetadataProperties;
@ -41,7 +43,7 @@ public class MetadataTracer implements MethodCallTracer {
boolean isClient = isClient();
setBeforeCall(isClient ?
TraceContext.getCurrentTraceData().getClientSpan().getMetadata() :
TraceContext.getCurrentTraceData().getServerSpan().getMetadata(),
TraceContext.getCurrentTraceData().getServiceSpan().getMetadata(),
args, caller, isClient);
}
@ -51,7 +53,7 @@ public class MetadataTracer implements MethodCallTracer {
boolean isClient = isClient();
setAfterCall(isClient ?
TraceContext.getCurrentTraceData().getClientSpan().getMetadata() :
TraceContext.getCurrentTraceData().getServerSpan().getMetadata(),
TraceContext.getCurrentTraceData().getServiceSpan().getMetadata(),
args, caller, result, isClient);
}
@ -59,8 +61,8 @@ public class MetadataTracer implements MethodCallTracer {
public void callError(Object[] args, InstanceMethodCaller caller, Throwable error) {
boolean isClient = isClient();
setCallError(isClient ?
TraceContext.getCurrentTraceData().getClientSpan().getMetadata() :
TraceContext.getCurrentTraceData().getServerSpan().getMetadata(),
TraceContext.getCurrentTraceData().getClientSpan() :
TraceContext.getCurrentTraceData().getServiceSpan(),
args, caller, error, isClient);
}
@ -75,9 +77,9 @@ public class MetadataTracer implements MethodCallTracer {
metadata.putValue(MetadataProperties.EVENT_TYPE, isClient ? ClientEventType.SERVICE_RESULT : ServiceEventType.HANDLER_RESULT);
}
private void setCallError(Metadata metadata, Object[] args, InstanceMethodCaller caller, Throwable error, boolean isClient) {
metadata.putValue(MetadataProperties.CALL_ERROR, error);
metadata.putValue(MetadataProperties.EVENT_TYPE, isClient ? ClientEventType.ERROR : ServiceEventType.ERROR);
private void setCallError(ContextSpan contextSpan, Object[] args, InstanceMethodCaller caller, Throwable error, boolean isClient) {
ContextUtils.setCallError(contextSpan, error);
contextSpan.getMetadata().putValue(MetadataProperties.EVENT_TYPE, isClient ? ClientEventType.ERROR : ServiceEventType.ERROR);
}
private boolean isClient() {

View File

@ -34,38 +34,42 @@ public class TraceContext {
}
public static TraceContext forClient(IdGenerator idGenerator, Runnable postInit, Runnable preDestroy) {
return new TraceContext(idGenerator, postInit, preDestroy);
public static TraceContext forClient(IdGenerator idGenerator, Runnable postInit, Runnable preDestroy, Runnable preErrDestroy) {
return new TraceContext(idGenerator, postInit, preDestroy, preErrDestroy);
}
public static TraceContext forServer(IdGenerator idGenerator, Runnable postInit, Runnable preDestroy) {
return new TraceContext(idGenerator, postInit, preDestroy);
public static TraceContext forServer(IdGenerator idGenerator, Runnable postInit, Runnable preDestroy, Runnable preErrDestroy) {
return new TraceContext(idGenerator, postInit, preDestroy, preErrDestroy);
}
private final IdGenerator idGenerator;
private final Runnable postInit;
private final Runnable preDestroy;
private final Runnable preErrDestroy;
private final boolean isAuto;
private final boolean isClient;
public TraceContext(IdGenerator idGenerator) {
this(idGenerator, () -> {
}, () -> {
}, () -> {
});
}
public TraceContext(IdGenerator idGenerator, Runnable postInit, Runnable preDestroy) {
public TraceContext(IdGenerator idGenerator, Runnable postInit, Runnable preDestroy, Runnable preErrDestroy) {
this.idGenerator = idGenerator;
this.postInit = postInit;
this.preDestroy = preDestroy;
this.preErrDestroy = preErrDestroy;
this.isAuto = true;
this.isClient = false;
}
public TraceContext(IdGenerator idGenerator, Runnable postInit, Runnable preDestroy, boolean isClient) {
public TraceContext(IdGenerator idGenerator, Runnable postInit, Runnable preDestroy, Runnable preErrDestroy, boolean isClient) {
this.idGenerator = idGenerator;
this.postInit = postInit;
this.preDestroy = preDestroy;
this.preErrDestroy = preErrDestroy;
this.isAuto = false;
this.isClient = isClient;
}
@ -84,11 +88,19 @@ public class TraceContext {
}
public void destroy() {
destroy(false);
}
public void destroy(boolean onError) {
TraceData traceData = getCurrentTraceData();
boolean isClient = isClientDestroy(traceData);
setDuration(isClient ? traceData.getClientSpan().getSpan() : traceData.getServerSpan().getSpan());
setDuration(isClient ? traceData.getClientSpan().getSpan() : traceData.getServiceSpan().getSpan());
try {
preDestroy.run();
if (onError) {
preErrDestroy.run();
} else {
preDestroy.run();
}
} finally {
if (isClient) {
destroyClientContext(traceData);
@ -98,10 +110,15 @@ public class TraceContext {
}
}
public void setDuration() {
TraceData traceData = getCurrentTraceData();
setDuration(isClient ? traceData.getClientSpan().getSpan() : traceData.getServiceSpan().getSpan());
}
private void initClientContext(TraceData traceData) {
long timestamp = System.currentTimeMillis();
Span clientSpan = traceData.getClientSpan().getSpan();
Span serverSpan = traceData.getServerSpan().getSpan();
Span serverSpan = traceData.getServiceSpan().getSpan();
boolean root = traceData.isRoot();
String traceId = root ? idGenerator.generateId(timestamp) : serverSpan.getTraceId();
@ -109,7 +126,7 @@ public class TraceContext {
clientSpan.setId(traceId);
clientSpan.setParentId(NO_PARENT_ID);
} else {
clientSpan.setId(idGenerator.generateId(timestamp, traceData.getServerSpan().getCounter().incrementAndGet()));
clientSpan.setId(idGenerator.generateId(timestamp, traceData.getServiceSpan().getCounter().incrementAndGet()));
clientSpan.setParentId(serverSpan.getId());
}
clientSpan.setTraceId(traceId);
@ -122,7 +139,7 @@ public class TraceContext {
private void initServerContext(TraceData traceData) {
long timestamp = System.currentTimeMillis();
traceData.getServerSpan().getSpan().setTimestamp(timestamp);
traceData.getServiceSpan().getSpan().setTimestamp(timestamp);
}
private void destroyServerContext(TraceData traceData) {
@ -142,19 +159,19 @@ public class TraceContext {
}
private boolean isClientInitAuto(TraceData traceData) {
Span serverSpan = traceData.getServerSpan().getSpan();
Span serverSpan = traceData.getServiceSpan().getSpan();
assert !(traceData.getClientSpan().isStarted() & traceData.getServerSpan().isStarted());
assert !(traceData.getClientSpan().isFilled() & traceData.getServerSpan().isFilled());
assert !(traceData.getClientSpan().isStarted() & traceData.getServiceSpan().isStarted());
assert !(traceData.getClientSpan().isFilled() & traceData.getServiceSpan().isFilled());
return serverSpan.isFilled() ? serverSpan.isStarted() : true;
}
private boolean isClientDestroyAuto(TraceData traceData) {
assert !(traceData.getClientSpan().isStarted() || traceData.getServerSpan().isStarted());
assert (traceData.getClientSpan().isStarted());
return traceData.getServerSpan().isStarted() ? traceData.getClientSpan().isStarted() : true;
return traceData.getServiceSpan().isStarted() ? traceData.getClientSpan().isStarted() : true;
}

View File

@ -0,0 +1,37 @@
package com.rbkmoney.woody.api.transport;
import com.rbkmoney.woody.api.event.ClientEventType;
import com.rbkmoney.woody.api.event.ServiceEventType;
import com.rbkmoney.woody.api.interceptor.CommonInterceptor;
import com.rbkmoney.woody.api.trace.MetadataProperties;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 27.04.16.
*/
public class TransportEventInterceptor<ReqProvider, RespProvider> implements CommonInterceptor<ReqProvider, RespProvider> {
private final Runnable reqListener;
private final Runnable respListener;
public TransportEventInterceptor(Runnable reqListener, Runnable respListener) {
this.reqListener = reqListener != null ? reqListener : () -> {
};
this.respListener = respListener != null ? respListener : () -> {
};
}
@Override
public boolean interceptRequest(TraceData traceData, ReqProvider providerContext, Object... contextParams) {
traceData.getActiveSpan().getMetadata().putValue(MetadataProperties.EVENT_TYPE, traceData.isClient() ? ClientEventType.CLIENT_SEND : ServiceEventType.SERVICE_RECEIVE);
reqListener.run();
return true;
}
@Override
public boolean interceptResponse(TraceData traceData, RespProvider providerContext, Object... contextParams) {
traceData.getActiveSpan().getMetadata().putValue(MetadataProperties.EVENT_TYPE, traceData.isClient() ? ClientEventType.CLIENT_RECEIVE : ServiceEventType.SERVICE_RESULT);
respListener.run();
return true;
}
}

View File

@ -1,9 +0,0 @@
package com.rbkmoney.woody.api.transport.interceptor;
import com.rbkmoney.woody.api.trace.ClientSpan;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ClientRequestInterceptor<Transport> extends RequestInterceptor<ClientSpan, Transport> {
}

View File

@ -1,9 +0,0 @@
package com.rbkmoney.woody.api.transport.interceptor;
import com.rbkmoney.woody.api.trace.ServerSpan;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ClientResponseInterceptor<Transport> extends ResponseInterceptor<ServerSpan, Transport> {
}

View File

@ -1,10 +0,0 @@
package com.rbkmoney.woody.api.transport.interceptor;
import com.rbkmoney.woody.api.trace.AbstractSpan;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface RequestInterceptor<Context extends AbstractSpan, Transport> {
boolean interceptRequest(Context context, Transport spanContext);
}

View File

@ -1,10 +0,0 @@
package com.rbkmoney.woody.api.transport.interceptor;
import com.rbkmoney.woody.api.trace.AbstractSpan;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ResponseInterceptor<Context extends AbstractSpan, Transport> {
void interceptResponse(Context context, Transport transport);
}

View File

@ -1,9 +0,0 @@
package com.rbkmoney.woody.api.transport.interceptor;
import com.rbkmoney.woody.api.trace.ServerSpan;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ServerRequestInterceptor<Transport> extends RequestInterceptor<ServerSpan, Transport> {
}

View File

@ -1,9 +0,0 @@
package com.rbkmoney.woody.api.transport.interceptor;
import com.rbkmoney.woody.api.trace.ServerSpan;
/**
* Created by vpankrashkin on 22.04.16.
*/
public interface ServerResponseIntercepor<Transport> extends ResponseInterceptor<ServerSpan, Transport> {
}

View File

@ -1,36 +0,0 @@
package com.rbkmoney.woody.api.transport.interceptor;
import com.rbkmoney.woody.api.event.ClientEventType;
import com.rbkmoney.woody.api.event.ServiceEventType;
import com.rbkmoney.woody.api.trace.AbstractSpan;
import com.rbkmoney.woody.api.trace.MetadataProperties;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.api.trace.context.TraceContext;
/**
* Created by vpankrashkin on 27.04.16.
*/
public class WrappedRequestInterceptor<Context extends AbstractSpan, Transport> implements RequestInterceptor<Context, Transport> {
private final RequestInterceptor interceptor;
private final Runnable listener;
public WrappedRequestInterceptor(RequestInterceptor interceptor, Runnable listener) {
this.interceptor = interceptor;
this.listener = listener;
}
@Override
public boolean interceptRequest(Context context, Transport spanContext) {
TraceData traceContext = TraceContext.getCurrentTraceData();
boolean isClient = traceContext.isClient();
if (interceptor.interceptRequest(context, spanContext)) {
traceContext.getActiveSpan().getMetadata().putValue(MetadataProperties.EVENT_TYPE, isClient ? ClientEventType.CLIENT_RECEIVE : ServiceEventType.SERVICE_RECEIVE);
listener.run();
return true;
} else {
traceContext.getActiveSpan().getMetadata().putValue(MetadataProperties.EVENT_TYPE, isClient ? ClientEventType.ERROR : ServiceEventType.ERROR);
return false;
}
}
}

View File

@ -1,6 +1,6 @@
package com.rbkmoney.woody.api.proxy;
import com.rbkmoney.woody.api.trace.context.EventListenerTracer;
import com.rbkmoney.woody.api.trace.context.EventTracer;
import org.junit.Test;
import static org.junit.Assert.assertSame;
@ -37,7 +37,7 @@ public class TestProxyInvocationFactory {
}
};
MethodCallTracer wrappedCallTracer = new EventListenerTracer(callTracer);
MethodCallTracer wrappedCallTracer = new EventTracer();
ProxyFactory reflectionProxyFactory = new ProxyFactory(new ReflectionMethodCallerFactory(), wrappedCallTracer, false);
ProxyFactory handleProxyFactory = new ProxyFactory(new HandleMethodCallerFactory(), wrappedCallTracer, false);

View File

@ -11,5 +11,70 @@
<artifactId>woody-thrift</artifactId>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-quickstart</artifactId>
<version>9.3.9.M1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.rbkmoney.woody</groupId>
<artifactId>woody-api</artifactId>
<version>${api-version}</version>
</dependency>
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.9.3-woody_b0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.thrift</groupId>
<artifactId>thrift-maven-plugin</artifactId>
<version>1.0-forked</version>
<configuration>
<thriftExecutable>/usr/local/bin/thrift</thriftExecutable>
</configuration>
<executions>
<execution>
<id>thrift-sources</id>
<phase>generate-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>thrift-test-sources</id>
<phase>generate-test-sources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<finalName>rpc-lib</finalName>
</build>
</project>

View File

@ -1,24 +0,0 @@
package com.rbkmoney.woody.thrift;
/**
* Created by vpankrashkin on 22.04.16.
* <p>
* Custom error call constants, specific for
*/
public enum ErrorCallType {
/**
* Any thrift errors (protocol, transport, etc).
*/
PROVIDER_ERROR,
/**
* Error which is registered in service method declaration.
*/
APPLICATION_KNOWN_ERROR,
/**
* Any other error which is not registered for calling method and doesn't refer to thrift errors.
*/
APPLICATION_UNKNOWN_ERROR
}

View File

@ -0,0 +1,11 @@
package com.rbkmoney.woody.thrift.impl.http;
/**
* Created by vpankrashkin on 22.04.16.
*/
public enum TErrorType {
UNKNOWN_CALL,
TRANSPORT,
PROTOCOL,
UNKNOWN
}

View File

@ -0,0 +1,149 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.AbstractClientBuilder;
import com.rbkmoney.woody.api.event.ClientEventListener;
import com.rbkmoney.woody.api.interceptor.BasicCommonInterceptor;
import com.rbkmoney.woody.api.interceptor.CommonInterceptor;
import com.rbkmoney.woody.api.interceptor.CompositeInterceptor;
import com.rbkmoney.woody.api.provider.ProviderEventInterceptor;
import com.rbkmoney.woody.api.proxy.InstanceMethodCaller;
import com.rbkmoney.woody.api.proxy.MethodCallTracer;
import com.rbkmoney.woody.api.trace.context.EmptyTracer;
import com.rbkmoney.woody.api.trace.context.TraceContext;
import com.rbkmoney.woody.api.transport.TransportEventInterceptor;
import com.rbkmoney.woody.thrift.impl.http.event.THClientEvent;
import com.rbkmoney.woody.thrift.impl.http.interceptor.THCMessageRequestInterceptor;
import com.rbkmoney.woody.thrift.impl.http.interceptor.THCMessageResponseInterceptor;
import com.rbkmoney.woody.thrift.impl.http.interceptor.THCRequestInterceptor;
import com.rbkmoney.woody.thrift.impl.http.interceptor.THCResponseInterceptor;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.thrift.TServiceClient;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.THttpClient;
import org.apache.thrift.transport.TTransport;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Optional;
/**
* Created by vpankrashkin on 28.04.16.
*/
public class THClientBuilder extends AbstractClientBuilder {
private HttpClient httpClient = createHttpClient();
public THClientBuilder withHttpClient(HttpClient httpClient) {
this.httpClient = httpClient;
return this;
}
@Override
protected MethodCallTracer getOnCallMetadataExtender(Class clientInterface) {
return new EmptyTracer() {
THErrorMetadataExtender metadataExtender = new THErrorMetadataExtender(clientInterface);
@Override
public void callError(Object[] args, InstanceMethodCaller caller, Throwable error) {
metadataExtender.extendClientError(TraceContext.getCurrentTraceData());
}
};
}
@Override
protected Runnable getErrorListener() {
return createEventRunnable(getEventListener());
}
@Override
protected Runnable getOnCallStartEventListener() {
return createEventRunnable(getEventListener());
}
@Override
protected Runnable getOnCallEndEventListener() {
return createEventRunnable(getEventListener());
}
@Override
protected Runnable getOnSendEventListener() {
return createEventRunnable(getEventListener());
}
@Override
protected Runnable getOnReceiveEventListener() {
return createEventRunnable(getEventListener());
}
@Override
protected <T> T createProviderClient(Class<T> clientInterface) {
try {
THttpClient tHttpClient = new THttpClient(getAddress().toString(), httpClient, createTransportInterceptor());
TProtocol tProtocol = createProtocol(tHttpClient);
return createThriftClient(clientInterface, tProtocol, createMessageInterceptor());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
protected ProxyBuilder createProxyBuilder(Class clientInterface) {
ProxyBuilder proxyBuilder = super.createProxyBuilder(clientInterface);
proxyBuilder.setStartEventPhases(ProxyBuilder.EVENT_DISABLE);
proxyBuilder.setEndEventPhases(ProxyBuilder.EVENT_BEFORE_CONTEXT_DESTROY);
return proxyBuilder;
}
protected TProtocol createProtocol(TTransport tTransport) {
return new TCompactProtocol(tTransport);
}
protected HttpClient createHttpClient() {
return HttpClientBuilder.create().build();
}
protected CommonInterceptor createMessageInterceptor() {
return new CompositeInterceptor(
new BasicCommonInterceptor(new THCMessageRequestInterceptor(), new THCMessageResponseInterceptor()),
new ProviderEventInterceptor(getOnCallStartEventListener(), null)
);
}
protected CommonInterceptor createTransportInterceptor() {
return new CompositeInterceptor(
new BasicCommonInterceptor(new THCRequestInterceptor(), new THCResponseInterceptor()),
new TransportEventInterceptor(getOnSendEventListener(), getOnReceiveEventListener())
);
}
protected static <T> T createThriftClient(Class<T> clientIface, TProtocol tProtocol, CommonInterceptor interceptor) {
try {
Optional<? extends Class> clientClass = Arrays.stream(clientIface.getDeclaringClass().getClasses())
.filter(cl -> cl.getSimpleName().equals("Client")).findFirst();
if (!clientClass.isPresent()) {
throw new IllegalArgumentException("Client interface doesn't conform to Thrift generated class structure");
}
if (!TServiceClient.class.isAssignableFrom(clientClass.get())) {
throw new IllegalArgumentException("Client class doesn't conform to Thrift generated class structure");
}
if (!clientIface.isAssignableFrom(clientClass.get())) {
throw new IllegalArgumentException("Client class has wrong type which is not assignable to client interface");
}
Constructor constructor = clientClass.get().getConstructor(TProtocol.class);
if (constructor == null) {
throw new IllegalArgumentException("Client class doesn't have required constructor to be created");
}
TServiceClient tClient = (TServiceClient) constructor.newInstance(tProtocol);
tClient.setInterceptor(interceptor);
return (T) tClient;
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalArgumentException("Failed to create provider client", e);
}
}
private Runnable createEventRunnable(ClientEventListener eventListener) {
return () -> eventListener.notifyEvent(new THClientEvent(TraceContext.getCurrentTraceData()));
}
}

View File

@ -0,0 +1,190 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.event.ErrorType;
import com.rbkmoney.woody.api.proxy.InstanceMethodCaller;
import com.rbkmoney.woody.api.proxy.MethodShadow;
import com.rbkmoney.woody.api.trace.*;
import com.rbkmoney.woody.thrift.impl.http.interceptor.THRequestInterceptionException;
import org.apache.thrift.TApplicationException;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TStruct;
import org.apache.thrift.transport.TTransportException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Function;
/**
* Created by vpankrashkin on 29.04.16.
*/
public class THErrorMetadataExtender {
private static final String UNKNOWN_ERROR_MESSAGE = "thrift application exception unknown";
private final Map<Method, Class[]> errorsMap;
public THErrorMetadataExtender(Class iface) {
this.errorsMap = getDeclaredErrorsMap(iface);
}
public TraceData extendClientError(TraceData traceData) {
extendError(traceData.getClientSpan(), (traceData1 -> {
Metadata metadata = traceData.getClientSpan().getMetadata();
Throwable callErr = ContextUtils.getCallError(traceData.getClientSpan());
if (isWrappedError(callErr)) {
extendWrappedErrorMetadata(metadata, callErr);
} else {
metadata.putValue(MetadataProperties.ERROR_TYPE, ErrorType.OTHER);
metadata.putValue(MetadataProperties.ERROR_MESSAGE, UNKNOWN_ERROR_MESSAGE);
}
return traceData1;
}));
return traceData;
}
public TraceData extendServiceError(TraceData traceData) {
extendError(traceData.getServiceSpan(), traceData1 -> {
Metadata metadata = traceData.getClientSpan().getMetadata();
Throwable callErr = ContextUtils.getCallError(traceData.getClientSpan());
if (isWrappedError(callErr)) {
extendWrappedErrorMetadata(metadata, callErr);
} else if (callErr instanceof THRequestInterceptionException) {
metadata.putValue(MetadataProperties.ERROR_TYPE, ErrorType.PROVIDER_ERROR);
metadata.putValue(THMetadataProperties.TH_ERROR_TYPE, TErrorType.TRANSPORT);
metadata.putValue(THMetadataProperties.TH_ERROR_SUBTYPE, ((THRequestInterceptionException) callErr).getErrorType());
} else {
metadata.putValue(MetadataProperties.ERROR_TYPE, ErrorType.APPLICATION_UNKNOWN_ERROR);
metadata.putValue(MetadataProperties.ERROR_MESSAGE, UNKNOWN_ERROR_MESSAGE);
}
return traceData1;
});
return traceData;
}
private void extendError(ContextSpan contextSpan, Function<ContextSpan, ContextSpan> undeclaredErrExtender) {
Metadata metadata = contextSpan.getMetadata();
Throwable callErr = ContextUtils.getCallError(contextSpan);
if (callErr == null) {
return;
}
InstanceMethodCaller caller = getCaller(metadata);
if (caller == null) {
return;
}
Throwable previousErr = ContextUtils.getMetadataParameter(contextSpan, Throwable.class, THMetadataProperties.TH_ERROR_METADATA_SOURCE);
if (callErr == previousErr) {
return;
} else {
contextSpan.getMetadata().removeValue(THMetadataProperties.TH_TRANSPORT_RESPONSE_SET);
}
metadata.putValue(MetadataProperties.ERROR_MESSAGE, callErr.getMessage());
if (isDeclaredError(callErr.getClass(), caller.getTargetMethod())) {
extendDeclaredErrorMetadata(metadata, callErr);
} else {
undeclaredErrExtender.apply(contextSpan);
}
return;
}
private void extendDeclaredErrorMetadata(Metadata metadata, Throwable err) {
metadata.putValue(MetadataProperties.ERROR_TYPE, ErrorType.APPLICATION_KNOWN_ERROR);
metadata.putValue(MetadataProperties.ERROR_NAME, getDeclaredErrName(err));
}
private void extendWrappedErrorMetadata(Metadata metadata, Throwable err) {
ErrorType errorType;
TErrorType tErrorType = null;
String errMessage;
if (err instanceof TApplicationException) {
TApplicationException appError = (TApplicationException) err;
switch (appError.getType()) {
case TApplicationException.INTERNAL_ERROR:
errorType = ErrorType.APPLICATION_UNKNOWN_ERROR;
errMessage = UNKNOWN_ERROR_MESSAGE;
break;
case TApplicationException.PROTOCOL_ERROR:
errorType = ErrorType.PROVIDER_ERROR;
tErrorType = TErrorType.PROTOCOL;
errMessage = err.getMessage();
break;
case TApplicationException.UNKNOWN_METHOD:
errorType = ErrorType.PROVIDER_ERROR;
tErrorType = TErrorType.UNKNOWN_CALL;
errMessage = err.getMessage();
break;
default:
errorType = ErrorType.PROVIDER_ERROR;
tErrorType = TErrorType.UNKNOWN;
errMessage = err.getMessage();
break;
}
} else if (err instanceof TTransportException) {
TTransportException trError = (TTransportException) err;
errorType = ErrorType.PROVIDER_ERROR;
tErrorType = TErrorType.TRANSPORT;
errMessage = trError.getMessage();
} else {
errorType = ErrorType.OTHER;
errMessage = err.getMessage();
}
metadata.putValue(MetadataProperties.ERROR_TYPE, errorType);
metadata.putValue(MetadataProperties.ERROR_MESSAGE, errMessage);
if (tErrorType != null) {
metadata.putValue(THMetadataProperties.TH_ERROR_TYPE, tErrorType);
}
}
public boolean isDeclaredError(Class errClass, Method callMethod) {
Class[] declaredErrors = errorsMap.get(callMethod);
for (int i = 0; i < declaredErrors.length; ++i) {
if (declaredErrors[i].isAssignableFrom(errClass)) {
return true;
}
}
return false;
}
private boolean isWrappedError(Throwable t) {
return t instanceof TException;
}
private String getDeclaredErrName(Throwable t) {
//TODO optimise this
try {
Field field = t.getClass().getDeclaredField("STRUCT_DESC");
field.setAccessible(true);
Object struct = field.get(t);
if (struct instanceof TStruct) {
return ((TStruct) struct).name;
}
return null;
} catch (NoSuchFieldException | IllegalAccessException e) {
return null;
}
}
private InstanceMethodCaller getCaller(Metadata metadata) {
Object callerObj = metadata.getValue(MetadataProperties.INSTANCE_METHOD_CALLER);
return (callerObj instanceof InstanceMethodCaller) ? (InstanceMethodCaller) callerObj : null;
}
private Map<Method, Class[]> getDeclaredErrorsMap(Class iface) {
Map<Method, Class[]> errorsMap = new TreeMap<>(MethodShadow.METHOD_COMPARATOR);
Arrays.stream(iface.getMethods()).forEach(m ->
errorsMap.put(m, Arrays.stream(m.getExceptionTypes())
.filter(e -> !e.getName().equals(TException.class.getName()))
.toArray(Class[]::new))
);
return errorsMap;
}
}

View File

@ -0,0 +1,22 @@
package com.rbkmoney.woody.thrift.impl.http;
/**
* Created by vpankrashkin on 29.04.16.
*/
public class THMetadataProperties {
public static final String TH_PROPERTY_PREFIX = "md_thrift_http_";
public static final String TH_ERROR_TYPE = TH_PROPERTY_PREFIX + "error_type";
public static final String TH_ERROR_SUBTYPE = TH_PROPERTY_PREFIX + "error_subtype";
public static final String TH_RESPONSE_STATUS = TH_PROPERTY_PREFIX + "response_status";
public static final String TH_RESPONSE_MESSAGE = TH_PROPERTY_PREFIX + "response_message";
public static final String TH_CALL_MSG_TYPE = TH_PROPERTY_PREFIX + "call_msg_type";
public static final String TH_CALL_RESULT_MSG_TYPE = TH_PROPERTY_PREFIX + "call_result_msg_type";
public static final String TH_TRANSPORT_RESPONSE = TH_PROPERTY_PREFIX + "transport_response";
public static final String TH_TRANSPORT_RESPONSE_SET = TH_PROPERTY_PREFIX + "transport_response_set";
public static final String TH_ERROR_METADATA_SOURCE = TH_PROPERTY_PREFIX + "error_metadata_source";
}

View File

@ -0,0 +1,42 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.interceptor.CommonInterceptor;
import com.rbkmoney.woody.api.trace.context.TraceContext;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TMessage;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.protocol.TProtocolDecorator;
/**
* Created by vpankrashkin on 10.05.16.
*/
public class THSProtocolWrapper extends TProtocolDecorator {
private final CommonInterceptor interceptor;
/**
* Encloses the specified protocol.
*
* @param protocol All operations will be forward to this protocol. Must be non-null.
*/
public THSProtocolWrapper(TProtocol protocol, CommonInterceptor interceptor) {
super(protocol);
this.interceptor = interceptor;
}
@Override
public TMessage readMessageBegin() throws TException {
TMessage tMessage = super.readMessageBegin();
//todo process errors
interceptor.interceptRequest(TraceContext.getCurrentTraceData(), tMessage);
return tMessage;
}
@Override
public void writeMessageBegin(TMessage tMessage) throws TException {
//todo process errors
interceptor.interceptResponse(TraceContext.getCurrentTraceData(), tMessage);
super.writeMessageBegin(tMessage);
}
}

View File

@ -0,0 +1,153 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.AbstractServiceBuilder;
import com.rbkmoney.woody.api.event.ServiceEventListener;
import com.rbkmoney.woody.api.interceptor.BasicCommonInterceptor;
import com.rbkmoney.woody.api.interceptor.CommonInterceptor;
import com.rbkmoney.woody.api.interceptor.CompositeInterceptor;
import com.rbkmoney.woody.api.interceptor.ContextInterceptor;
import com.rbkmoney.woody.api.proxy.InstanceMethodCaller;
import com.rbkmoney.woody.api.proxy.MethodCallTracer;
import com.rbkmoney.woody.api.trace.context.EmptyTracer;
import com.rbkmoney.woody.api.trace.context.TraceContext;
import com.rbkmoney.woody.api.transport.TransportEventInterceptor;
import com.rbkmoney.woody.thrift.impl.http.event.THServiceEvent;
import com.rbkmoney.woody.thrift.impl.http.interceptor.*;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.server.TServlet;
import javax.servlet.Servlet;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Optional;
/**
* Created by vpankrashkin on 28.04.16.
*/
public class THServiceBuilder extends AbstractServiceBuilder<Servlet> {
@Override
protected MethodCallTracer getOnCallMetadataExtender(Class serviceInterface) {
return new EmptyTracer() {
THErrorMetadataExtender metadataExtender = new THErrorMetadataExtender(serviceInterface);
@Override
public void callError(Object[] args, InstanceMethodCaller caller, Throwable error) {
metadataExtender.extendServiceError(TraceContext.getCurrentTraceData());
}
};
}
@Override
protected Runnable getErrorListener() {
return createEventRunnable(getEventListener());
}
@Override
protected Runnable getOnCallStartEventListener() {
return createEventRunnable(getEventListener());
}
@Override
protected Runnable getOnCallEndEventListener() {
return createEventRunnable(getEventListener());
}
@Override
protected Runnable getOnSendEventListener() {
return createEventRunnable(getEventListener());
}
@Override
protected Runnable getOnReceiveEventListener() {
return createEventRunnable(getEventListener());
}
@Override
protected <T> Servlet createProviderService(Class<T> serviceInterface, T handler) {
try {
TProcessor tProcessor = createThriftProcessor(serviceInterface, handler);
return createThriftServlet(tProcessor, createTransportInterceptor(), new THErrorMetadataExtender(serviceInterface));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
protected ProxyBuilder createProxyBuilder(Class clientInterface) {
ProxyBuilder proxyBuilder = super.createProxyBuilder(clientInterface);
proxyBuilder.setStartEventPhases(ProxyBuilder.EVENT_BEFORE_CALL_START);
proxyBuilder.setEndEventPhases(ProxyBuilder.EVENT_AFTER_CALL_END);
return proxyBuilder;
}
protected CommonInterceptor createMessageInterceptor() {
return new CompositeInterceptor(
new BasicCommonInterceptor(new THSMessageRequestInterceptor(), new THSMessageResponseInterceptor())
);
}
protected CommonInterceptor createTransportInterceptor() {
TraceContext traceContext = createTraceContext();
return new CompositeInterceptor(
new BasicCommonInterceptor(new THSRequestInterceptor(), new THSResponseInterceptor(true)),
new ContextInterceptor(
traceContext,
new TransportEventInterceptor(getOnReceiveEventListener(), null)
)
);
}
protected TraceContext createTraceContext() {
return TraceContext.forServer(getIdGenerator(), () -> {
}, getOnSendEventListener(), getErrorListener());
}
protected TProtocolFactory wrapProtocolFactory(TProtocolFactory tProtocolFactory, CommonInterceptor commonInterceptor) {
return tTransport -> {
TProtocol tProtocol = tProtocolFactory.getProtocol(tTransport);
return new THSProtocolWrapper(tProtocol, commonInterceptor);
};
}
protected Servlet createThriftServlet(TProcessor tProcessor, CommonInterceptor servletInterceptor, THErrorMetadataExtender metadataExtender) {
CompositeInterceptor protInterceptor = new CompositeInterceptor(
createMessageInterceptor(),
new BasicCommonInterceptor(null, new THSResponseMetadataInterceptor(metadataExtender)),
new BasicCommonInterceptor(null, new THSResponseInterceptor(false))
);
TProtocolFactory tProtocolFactory = wrapProtocolFactory(new TCompactProtocol.Factory(), protInterceptor);
return new TServlet(tProcessor, tProtocolFactory, servletInterceptor);
}
protected <T> TProcessor createThriftProcessor(Class<T> serviceInterface, T handler) {
try {
Optional<? extends Class> processorClass = Arrays.stream(serviceInterface.getDeclaringClass().getClasses())
.filter(cl -> cl.getSimpleName().equals("Processor")).findFirst();
if (!processorClass.isPresent()) {
throw new IllegalArgumentException("Service interface doesn't conform to Thrift generated class structure");
}
if (!TProcessor.class.isAssignableFrom(processorClass.get())) {
throw new IllegalArgumentException("Service class doesn't conform to Thrift generated class structure");
}
Constructor constructor = processorClass.get().getConstructor(serviceInterface);
if (constructor == null) {
throw new IllegalArgumentException("Service class doesn't have required constructor to be created");
}
TProcessor tProcessor = (TProcessor) constructor.newInstance(handler);
return tProcessor;
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalArgumentException("Failed to create provider service", e);
}
}
private Runnable createEventRunnable(ServiceEventListener eventListener) {
return () -> eventListener.notifyEvent(new THServiceEvent(TraceContext.getCurrentTraceData()));
}
}

View File

@ -0,0 +1,35 @@
package com.rbkmoney.woody.thrift.impl.http.event;
import com.rbkmoney.woody.api.event.ClientEvent;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.thrift.impl.http.TErrorType;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
/**
* Created by vpankrashkin on 06.05.16.
*/
public class THClientEvent extends ClientEvent {
public THClientEvent(TraceData traceData) {
super(traceData);
}
public Integer getThriftCallMsgType() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_CALL_MSG_TYPE);
}
public Integer getThiftCallResultMsgType() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_CALL_RESULT_MSG_TYPE);
}
public TErrorType getThriftErrorType() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_ERROR_TYPE);
}
public Integer getThriftResponseStatus() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_RESPONSE_STATUS);
}
public String getThriftResponseMessage() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_RESPONSE_MESSAGE);
}
}

View File

@ -0,0 +1,35 @@
package com.rbkmoney.woody.thrift.impl.http.event;
import com.rbkmoney.woody.api.event.ServiceEvent;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.thrift.impl.http.TErrorType;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
/**
* Created by vpankrashkin on 06.05.16.
*/
public class THServiceEvent extends ServiceEvent {
public THServiceEvent(TraceData traceData) {
super(traceData);
}
public Integer getThriftCallMsgType() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_CALL_MSG_TYPE);
}
public Integer getThiftCallResultMsgType() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_CALL_RESULT_MSG_TYPE);
}
public TErrorType getThriftErrorType() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_ERROR_TYPE);
}
public Integer getThriftResponseStatus() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_RESPONSE_STATUS);
}
public String getThriftResponseMessage() {
return getActiveSpan().getMetadata().getValue(THMetadataProperties.TH_RESPONSE_MESSAGE);
}
}

View File

@ -0,0 +1,33 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.event.ClientEventType;
import com.rbkmoney.woody.api.interceptor.CommonInterceptor;
import com.rbkmoney.woody.api.trace.MetadataProperties;
import com.rbkmoney.woody.api.trace.TraceData;
/**
* Created by vpankrashkin on 06.05.16.
*/
public class THCEventCommonInterceptorDEL implements CommonInterceptor {
private final Runnable requestListener;
private final Runnable responseListener;
public THCEventCommonInterceptorDEL(Runnable requestListener, Runnable responseListener) {
this.requestListener = requestListener;
this.responseListener = responseListener;
}
@Override
public boolean interceptRequest(TraceData traceData, Object providerContext, Object... contextParams) {
traceData.getClientSpan().getMetadata().putValue(MetadataProperties.EVENT_TYPE, ClientEventType.CLIENT_SEND);
requestListener.run();
return true;
}
@Override
public boolean interceptResponse(TraceData traceData, Object providerContext, Object... contextParams) {
traceData.getClientSpan().getMetadata().putValue(MetadataProperties.EVENT_TYPE, ClientEventType.CLIENT_RECEIVE);
responseListener.run();
return true;
}
}

View File

@ -0,0 +1,36 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.event.CallType;
import com.rbkmoney.woody.api.interceptor.RequestInterceptor;
import com.rbkmoney.woody.api.trace.Metadata;
import com.rbkmoney.woody.api.trace.MetadataProperties;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
import org.apache.thrift.protocol.TMessage;
import org.apache.thrift.protocol.TMessageType;
/**
* Created by vpankrashkin on 05.05.16.
*/
public class THCMessageRequestInterceptor implements RequestInterceptor<TMessage> {
private final Runnable eventListener;
public THCMessageRequestInterceptor() {
this(null);
}
public THCMessageRequestInterceptor(Runnable eventListener) {
this.eventListener = eventListener == null ? () -> {
} : eventListener;
}
@Override
public boolean interceptRequest(TraceData traceData, TMessage providerContext, Object... contextParams) {
Metadata metadata = traceData.getClientSpan().getMetadata();
metadata.putValue(MetadataProperties.CALL_NAME, providerContext.name);
metadata.putValue(MetadataProperties.CALL_TYPE, providerContext.type == TMessageType.ONEWAY ? CallType.CAST : CallType.CALL);
metadata.putValue(THMetadataProperties.TH_CALL_MSG_TYPE, providerContext.type);
eventListener.run();
return true;
}
}

View File

@ -0,0 +1,31 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.interceptor.ResponseInterceptor;
import com.rbkmoney.woody.api.trace.Metadata;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
import org.apache.thrift.protocol.TMessage;
/**
* Created by vpankrashkin on 05.05.16.
*/
public class THCMessageResponseInterceptor implements ResponseInterceptor<TMessage> {
private final Runnable eventListener;
public THCMessageResponseInterceptor() {
this(() -> {
});
}
public THCMessageResponseInterceptor(Runnable eventListener) {
this.eventListener = eventListener;
}
@Override
public boolean interceptResponse(TraceData traceData, TMessage providerContext, Object... contextParams) {
Metadata metadata = traceData.getClientSpan().getMetadata();
metadata.putValue(THMetadataProperties.TH_CALL_RESULT_MSG_TYPE, providerContext.type);
eventListener.run();
return true;
}
}

View File

@ -0,0 +1,64 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.interceptor.RequestInterceptor;
import com.rbkmoney.woody.api.trace.*;
import com.rbkmoney.woody.thrift.impl.http.transport.UrlStringEndpoint;
import org.apache.http.HttpHost;
import org.apache.http.client.methods.HttpRequestBase;
import java.net.HttpURLConnection;
import java.util.ArrayList;
/**
* Created by vpankrashkin on 29.04.16.
*/
public class THCRequestInterceptor implements RequestInterceptor {
@Override
public boolean interceptRequest(TraceData traceData, Object providerContext, Object... contextParams) {
if (providerContext instanceof HttpRequestBase) {
return interceptRequestBase(traceData.getClientSpan(), (HttpRequestBase) providerContext, contextParams);
} else if (providerContext instanceof HttpURLConnection) {
return interceptUrlConnection(traceData.getClientSpan(), (HttpURLConnection) providerContext, contextParams);
}
return interceptError(traceData, "Unknown type:" + providerContext.getClass());
}
private boolean interceptUrlConnection(ClientSpan clientSpan, HttpURLConnection connection, Object... contextParams) {
extendMetadata(clientSpan, connection.getURL().toString());
ArrayList<String[]> headers = prepareClientHeaders(clientSpan);
for (int i = 0; i < headers.size(); ++i) {
connection.setRequestProperty(headers.get(i)[0], headers.get(i)[1]);
}
return true;
}
protected boolean interceptRequestBase(ClientSpan clientSpan, HttpRequestBase requestBase, Object... contextParams) {
HttpHost httpHost = ContextUtils.getContextParameter(HttpHost.class, contextParams, 0);
extendMetadata(clientSpan, httpHost == null ? null : httpHost.toString());
ArrayList<String[]> headers = prepareClientHeaders(clientSpan);
for (int i = 0; i < headers.size(); ++i) {
requestBase.setHeader(headers.get(i)[0], headers.get(i)[1]);
}
return true;
}
private void extendMetadata(ClientSpan clientSpan, String url) {
clientSpan.getMetadata().putValue(MetadataProperties.CALL_ENDPOINT, new UrlStringEndpoint(url));
}
private ArrayList<String[]> prepareClientHeaders(ClientSpan clientSpan) {
Span span = clientSpan.getSpan();
ArrayList<String[]> headers = new ArrayList<>();
headers.add(new String[]{"x-rbk-trace-id", span.getTraceId()});
headers.add(new String[]{"x-rbk-span-id", span.getId()});
headers.add(new String[]{"x-rbk-parent-id", span.getParentId()});
return headers;
}
private boolean interceptError(TraceData traceData, String message) {
ContextUtils.setInterceptionError(traceData.getClientSpan(), new RuntimeException(message));
return false;
}
}

View File

@ -0,0 +1,48 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.interceptor.ResponseInterceptor;
import com.rbkmoney.woody.api.trace.ClientSpan;
import com.rbkmoney.woody.api.trace.ContextUtils;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
import org.apache.http.HttpResponse;
import java.io.IOException;
import java.net.HttpURLConnection;
/**
* Created by vpankrashkin on 29.04.16.
*/
public class THCResponseInterceptor implements ResponseInterceptor {
@Override
public boolean interceptResponse(TraceData traceData, Object providerContext, Object... contextParams) {
if (providerContext instanceof HttpResponse) {
return interceptResponseBase(traceData.getClientSpan(), (HttpResponse) providerContext);
} else if (providerContext instanceof HttpURLConnection) {
return interceptUrlConnection(traceData.getClientSpan(), (HttpURLConnection) providerContext);
}
return interceptError(traceData.getClientSpan(), "Unknown type:" + providerContext.getClass(), null);
}
private boolean interceptUrlConnection(ClientSpan clientSpan, HttpURLConnection connection) {
try {
clientSpan.getMetadata().putValue(THMetadataProperties.TH_RESPONSE_STATUS, connection.getResponseCode());
clientSpan.getMetadata().putValue(THMetadataProperties.TH_RESPONSE_MESSAGE, connection.getResponseMessage());
return true;
} catch (IOException e) {
return interceptError(clientSpan, "Failed to get response data", e);
}
}
protected boolean interceptResponseBase(ClientSpan clientSpan, HttpResponse response) {
clientSpan.getMetadata().putValue(THMetadataProperties.TH_RESPONSE_STATUS, response.getStatusLine().getStatusCode());
clientSpan.getMetadata().putValue(THMetadataProperties.TH_RESPONSE_MESSAGE, response.getStatusLine().getReasonPhrase());
return true;
}
private boolean interceptError(ClientSpan clientSpan, String message, Throwable cause) {
ContextUtils.setInterceptionError(clientSpan, cause);
return false;
}
}

View File

@ -0,0 +1,19 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.thrift.impl.http.transport.TTransportErrorType;
/**
* Created by vpankrashkin on 11.05.16.
*/
public class THRequestInterceptionException extends RuntimeException {
private TTransportErrorType errorType;
public THRequestInterceptionException(TTransportErrorType transportErrorType) {
super();
errorType = transportErrorType;
}
public TTransportErrorType getErrorType() {
return errorType;
}
}

View File

@ -0,0 +1,36 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.event.CallType;
import com.rbkmoney.woody.api.interceptor.RequestInterceptor;
import com.rbkmoney.woody.api.trace.Metadata;
import com.rbkmoney.woody.api.trace.MetadataProperties;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
import org.apache.thrift.protocol.TMessage;
import org.apache.thrift.protocol.TMessageType;
/**
* Created by vpankrashkin on 05.05.16.
*/
public class THSMessageRequestInterceptor implements RequestInterceptor<TMessage> {
private final Runnable eventListener;
public THSMessageRequestInterceptor() {
this(null);
}
public THSMessageRequestInterceptor(Runnable eventListener) {
this.eventListener = eventListener == null ? () -> {
} : eventListener;
}
@Override
public boolean interceptRequest(TraceData traceData, TMessage providerContext, Object... contextParams) {
Metadata metadata = traceData.getClientSpan().getMetadata();
metadata.putValue(MetadataProperties.CALL_NAME, providerContext.name);
metadata.putValue(MetadataProperties.CALL_TYPE, providerContext.type == TMessageType.ONEWAY ? CallType.CAST : CallType.CALL);
metadata.putValue(THMetadataProperties.TH_CALL_MSG_TYPE, providerContext.type);
eventListener.run();
return true;
}
}

View File

@ -0,0 +1,31 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.interceptor.ResponseInterceptor;
import com.rbkmoney.woody.api.trace.Metadata;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
import org.apache.thrift.protocol.TMessage;
/**
* Created by vpankrashkin on 05.05.16.
*/
public class THSMessageResponseInterceptor implements ResponseInterceptor<TMessage> {
private final Runnable eventListener;
public THSMessageResponseInterceptor() {
this(() -> {
});
}
public THSMessageResponseInterceptor(Runnable eventListener) {
this.eventListener = eventListener;
}
@Override
public boolean interceptResponse(TraceData traceData, TMessage providerContext, Object... contextParams) {
Metadata metadata = traceData.getClientSpan().getMetadata();
metadata.putValue(THMetadataProperties.TH_CALL_RESULT_MSG_TYPE, providerContext.type);
eventListener.run();
return true;
}
}

View File

@ -0,0 +1,81 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.interceptor.RequestInterceptor;
import com.rbkmoney.woody.api.trace.*;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
import com.rbkmoney.woody.thrift.impl.http.transport.THttpHeader;
import com.rbkmoney.woody.thrift.impl.http.transport.TTransportErrorType;
import com.rbkmoney.woody.thrift.impl.http.transport.UrlStringEndpoint;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Created by vpankrashkin on 29.04.16.
*/
public class THSRequestInterceptor implements RequestInterceptor {
@Override
public boolean interceptRequest(TraceData traceData, Object providerContext, Object... contextParams) {
if (providerContext instanceof HttpServletRequest) {
return interceptHttpRequest(traceData, (HttpServletRequest) providerContext, contextParams);
}
return interceptError(traceData, "Unknown type:" + providerContext.getClass());
}
protected boolean interceptHttpRequest(TraceData traceData, HttpServletRequest request, Object... contextParams) {
THttpHeader errHeader = setSpanHeaders(traceData.getServiceSpan(), request);
if (errHeader != null) {
return interceptError(traceData, new THRequestInterceptionException(TTransportErrorType.BAD_TRACE_HEADERS));
}
extendMetadata(traceData.getServiceSpan(), request, contextParams);
return true;
}
private THttpHeader setSpanHeaders(ServiceSpan serviceSpan, HttpServletRequest request) {
Span span = serviceSpan.getSpan();
String header = getSpanHeader(THttpHeader.TRACE_ID.getKeyValue(), request);
if (header == null) {
return THttpHeader.TRACE_ID;
}
span.setTraceId(header);
header = getSpanHeader(THttpHeader.SPAN_ID.getKeyValue(), request);
if (header == null) {
return THttpHeader.SPAN_ID;
}
span.setId(header);
header = getSpanHeader(THttpHeader.PARENT_ID.getKeyValue(), request);
span.setParentId(header);
return null;
}
private void extendMetadata(ServiceSpan serviceSpan, HttpServletRequest request, Object... contextParams) {
StringBuffer sb = request.getRequestURL().append(request.getQueryString());
serviceSpan.getMetadata().putValue(MetadataProperties.CALL_ENDPOINT, new UrlStringEndpoint(sb.toString()));
HttpServletResponse response = ContextUtils.getContextParameter(HttpServletResponse.class, contextParams, 0);
if (response != null) {
serviceSpan.getMetadata().putValue(THMetadataProperties.TH_TRANSPORT_RESPONSE, response);
}
}
private String getSpanHeader(String name, HttpServletRequest request) {
String value = request.getHeader(name);
if (value == null || value.length() == 0) {
return null;
}
return value;
}
private boolean interceptError(TraceData traceData, String message) {
return interceptError(traceData, new RuntimeException(message));
}
private boolean interceptError(TraceData traceData, Throwable t) {
ContextUtils.setInterceptionError(traceData.getServiceSpan(), t);
return false;
}
}

View File

@ -0,0 +1,146 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.event.ErrorType;
import com.rbkmoney.woody.api.interceptor.ResponseInterceptor;
import com.rbkmoney.woody.api.trace.*;
import com.rbkmoney.woody.thrift.impl.http.TErrorType;
import com.rbkmoney.woody.thrift.impl.http.THMetadataProperties;
import com.rbkmoney.woody.thrift.impl.http.transport.THttpHeader;
import com.rbkmoney.woody.thrift.impl.http.transport.TTransportErrorType;
import javax.servlet.http.HttpServletResponse;
/**
* Created by vpankrashkin on 29.04.16.
*/
public class THSResponseInterceptor implements ResponseInterceptor {
public static final String THRFIT_TRANSPORT_ERROR_MSG = "thrift transport error";
public static final String THRFIT_PROTOCOL_ERROR_MSG = "thrift protocol error";
public static final String UNKNOWN_PROVIDER_ERROR_MSG = "unknown provider error";
public static final String BAD_REQUEST_HEADERS_MSG = "bad request headers";
boolean isUseContext;
public THSResponseInterceptor(boolean isUseContext) {
this.isUseContext = isUseContext;
}
@Override
public boolean interceptResponse(TraceData traceData, Object providerContext, Object... contextParams) {
if (traceData.getServiceSpan().getMetadata().containsKey(THMetadataProperties.TH_TRANSPORT_RESPONSE_SET)) {
return true;
}
HttpServletResponse response = null;
if (isUseContext) {
if (providerContext instanceof HttpServletResponse) {
response = (HttpServletResponse) providerContext;
}
} else {
response = ContextUtils.getMetadataParameter(traceData.getServiceSpan(), HttpServletResponse.class, THMetadataProperties.TH_TRANSPORT_RESPONSE);
}
if (response == null) {
return interceptError(traceData, "Unknown type:" + providerContext.getClass());
}
if (response.isCommitted()) {
return true;
}
return interceptHttpResponse(traceData.getServiceSpan(), response);
}
private boolean interceptHttpResponse(ServiceSpan serviceSpan, HttpServletResponse response) {
response.addHeader(THttpHeader.TRACE_ID.getKeyValue(), serviceSpan.getSpan().getTraceId());
response.addHeader(THttpHeader.SPAN_ID.getKeyValue(), serviceSpan.getSpan().getId());
response.addHeader(THttpHeader.PARENT_ID.getKeyValue(), serviceSpan.getSpan().getParentId());
int responseStatus;
String errLogicValue = null;
String errThriftValue = null;
Metadata metadata = serviceSpan.getMetadata();
ErrorType errType = ContextUtils.getMetadataParameter(serviceSpan, ErrorType.class, MetadataProperties.ERROR_TYPE);
if (errType != null) {
switch (errType) {
case APPLICATION_KNOWN_ERROR:
responseStatus = 200;
errLogicValue = metadata.getValue(MetadataProperties.ERROR_NAME);
break;
case PROVIDER_ERROR:
TErrorType tErrorType = ContextUtils.getMetadataParameter(serviceSpan, TErrorType.class, THMetadataProperties.TH_ERROR_TYPE);
if (tErrorType != null) {
switch (tErrorType) {
case UNKNOWN_CALL:
responseStatus = 405;
errThriftValue = "Unknown method:" + metadata.getValue(MetadataProperties.CALL_NAME);
break;
case TRANSPORT:
TTransportErrorType tTransportErrorType = ContextUtils.getMetadataParameter(serviceSpan, TTransportErrorType.class, THMetadataProperties.TH_ERROR_SUBTYPE);
if (tTransportErrorType != null) {
switch (tTransportErrorType) {
case BAD_TRACE_HEADERS:
case BAD_CONTENT_TYPE:
responseStatus = 403;
errThriftValue = BAD_REQUEST_HEADERS_MSG;
break;
default:
responseStatus = 403;
errThriftValue = THRFIT_TRANSPORT_ERROR_MSG;
}
} else {
responseStatus = 403;
errThriftValue = THRFIT_TRANSPORT_ERROR_MSG;
}
break;
case PROTOCOL:
responseStatus = 406;
errThriftValue = THRFIT_PROTOCOL_ERROR_MSG;
break;
case UNKNOWN:
default:
responseStatus = 410;
errThriftValue = UNKNOWN_PROVIDER_ERROR_MSG;
break;
}
} else {
responseStatus = 410;
errThriftValue = UNKNOWN_PROVIDER_ERROR_MSG;
}
break;
case APPLICATION_UNKNOWN_ERROR:
case OTHER:
default:
responseStatus = 500;
errThriftValue = metadata.getValue(MetadataProperties.ERROR_NAME);
break;
}
} else {
responseStatus = 200;
}
if (errLogicValue != null) {
response.addHeader(THttpHeader.ERROR_LOGIC.getKeyValue(), errLogicValue);
}
if (errThriftValue != null) {
response.addHeader(THttpHeader.ERROR_THRIFT.getKeyValue(), errThriftValue);
}
response.setStatus(responseStatus);
serviceSpan.getMetadata().putValue(THMetadataProperties.TH_TRANSPORT_RESPONSE_SET, true);
return true;
}
private boolean interceptError(TraceData traceData, String message) {
return interceptError(traceData, new RuntimeException(message));
}
private boolean interceptError(TraceData traceData, Throwable cause) {
ContextUtils.setInterceptionError(traceData.getServiceSpan(), cause);
return false;
}
}

View File

@ -0,0 +1,24 @@
package com.rbkmoney.woody.thrift.impl.http.interceptor;
import com.rbkmoney.woody.api.interceptor.ResponseInterceptor;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.thrift.impl.http.THErrorMetadataExtender;
import javax.servlet.http.HttpServletResponse;
/**
* Created by vpankrashkin on 11.05.16.
*/
public class THSResponseMetadataInterceptor implements ResponseInterceptor<HttpServletResponse> {
private final THErrorMetadataExtender metadataExtender;
public THSResponseMetadataInterceptor(THErrorMetadataExtender metadataExtender) {
this.metadataExtender = metadataExtender;
}
@Override
public boolean interceptResponse(TraceData traceData, HttpServletResponse providerContext, Object... contextParams) {
metadataExtender.extendServiceError(traceData);
return true;
}
}

View File

@ -0,0 +1,22 @@
package com.rbkmoney.woody.thrift.impl.http.transport;
/**
* Created by vpankrashkin on 11.05.16.
*/
public enum THttpHeader {
TRACE_ID("x-rbk-trace-id"),
SPAN_ID("x-rbk-span-id"),
PARENT_ID("x-rbk-parent-id"),
ERROR_LOGIC("x-rbk-rpc-error-logic"),
ERROR_THRIFT("x-rbk-rpc-error-thrift");
private String value;
THttpHeader(String name) {
this.value = name;
}
public String getKeyValue() {
return value;
}
}

View File

@ -0,0 +1,11 @@
package com.rbkmoney.woody.thrift.impl.http.transport;
/**
* Created by vpankrashkin on 11.05.16.
*/
public enum TTransportErrorType {
NO_DATA,
BAD_REQUEST_TYPE,
BAD_CONTENT_TYPE,
BAD_TRACE_HEADERS
}

View File

@ -1,11 +0,0 @@
package com.rbkmoney.woody.thrift.impl.http.transport;
/**
* Created by vpankrashkin on 22.04.16.
*/
public enum TransportErrorType {
NO_DATA,
BAD_REQUEST_TYPE,
BAD_CONTENT_TYPE
}

View File

@ -0,0 +1,24 @@
package com.rbkmoney.woody.thrift.impl.http.transport;
import com.rbkmoney.woody.api.trace.Endpoint;
/**
* Created by vpankrashkin on 06.05.16.
*/
public class UrlStringEndpoint implements Endpoint<String> {
private String url;
public UrlStringEndpoint(String url) {
this.url = url;
}
@Override
public String getStringValue() {
return url;
}
@Override
public String getValue() {
return url;
}
}

View File

@ -0,0 +1,33 @@
package com.rbkmoney.woody.rpc;
import org.apache.thrift.TException;
/**
* Created by vpankrashkin on 19.04.16.
*/
public class OwnerServiceImpl implements OwnerService.Iface {
@Override
public Owner getOwner(int id) throws TException {
return new Owner(1, "name");
}
@Override
public Owner getErrOwner(int id) throws err_one, TException {
return null;
}
@Override
public void setOwner(Owner owner) throws TException {
}
@Override
public void setOwnerOneway(Owner owner) throws TException {
}
@Override
public Owner setErrOwner(Owner owner, int id) throws TException {
return null;
}
}

View File

@ -0,0 +1,72 @@
package com.rbkmoney.woody.rpc;
/**
* Created by vpankrashkin on 19.04.16.
*/
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.server.TServlet;
import org.apache.thrift.transport.THttpClient;
import org.apache.thrift.transport.TTransportException;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class TestHttp {
private Server server;
@Before
public void startJetty() throws Exception {
server = new Server(8080);
ServletContextHandler context = new ServletContextHandler();
ServletHolder defaultServ = new ServletHolder("default", TServletExample.class);
//defaultServ.setInitParameter("resourceBase",System.getProperty("user.dir"));
//defaultServ.setInitParameter("dirAllowed","true");
context.addServlet(defaultServ, "/");
server.setHandler(context);
// Start Server
server.start();
}
@After
public void stopJetty() {
try {
server.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testServlet() throws TTransportException, TException {
String servletUrl = "http://localhost:8080/";
THttpClient thc = new THttpClient(servletUrl);
TProtocol loPFactory = new TCompactProtocol(thc);
OwnerService.Client client = new OwnerService.Client(loPFactory);
Owner bean = client.getOwner(1);
Assert.assertEquals("name", bean.getName());
}
public static class TServletExample extends TServlet {
public TServletExample() {
super(
new OwnerService.Processor(
new OwnerServiceImpl()),
new TCompactProtocol.Factory()
);
}
}
}

View File

@ -0,0 +1,88 @@
package com.rbkmoney.woody.rpc;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.net.URISyntaxException;
/**
* Created by vpankrashkin on 19.04.16.
*/
public class TestSocket {
private static final int PORT = 7911;
@BeforeClass
@SuppressWarnings({"static-access"})
public static void startServer() throws URISyntaxException, IOException {
// Start thrift server in a seperate thread
new Thread(new ServerExample()).start();
try {
// wait for the server start up
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void testExample() throws TTransportException, TException {
TTransport transport = new TSocket("localhost", PORT);
TProtocol protocol = new TBinaryProtocol(transport);
OwnerService.Client client = new OwnerService.Client(protocol);
transport.open();
Owner bean = client.getOwner(1);
transport.close();
Assert.assertEquals("name", bean.getName());
}
public static class TestHttpClient {
public static void main(String[] args) {
try {
TTransport transport = new TSocket("localhost", PORT);
TProtocol protocol = new TBinaryProtocol(transport);
OwnerService.Client client = new OwnerService.Client(protocol);
transport.open();
Owner bean = client.getOwner(1);
transport.close();
System.out.println(bean);
} catch (TTransportException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
}
}
}
public static class ServerExample implements Runnable {
public static void main(String[] args) {
new Thread(new ServerExample()).run();
}
@Override
public void run() {
try {
TServerSocket serverTransport = new TServerSocket(PORT);
OwnerService.Processor processor = new OwnerService.Processor(new OwnerServiceImpl());
TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(serverTransport).processor(processor));
System.out.println("Starting server on port " + PORT);
server.serve();
} catch (TTransportException e) {
e.printStackTrace();
}
}
}
}

View File

@ -0,0 +1,96 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.event.ClientEventListener;
import com.rbkmoney.woody.api.generator.IdGenerator;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.thrift.TException;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.server.TServlet;
import org.apache.thrift.transport.THttpClient;
import org.apache.thrift.transport.TTransportException;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.After;
import org.junit.Before;
import javax.servlet.Servlet;
import java.net.URI;
import java.net.URISyntaxException;
/**
* Created by vpankrashkin on 06.05.16.
*/
public class AbstractClientTest<I> {
protected Server server;
protected Servlet servlet = createMutableTervlet();
protected int serverPort = 8080;
protected TProcessor tProcessor;
@Before
public void startJetty() throws Exception {
server = new Server(serverPort);
ServletContextHandler context = new ServletContextHandler();
ServletHolder defaultServ = new ServletHolder("default", servlet);
context.addServlet(defaultServ, "/");
server.setHandler(context);
server.start();
}
@After
public void stopJetty() {
try {
server.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
protected String getUrlString() {
return "http://localhost:" + serverPort;
}
public TServlet createTServlet(TProcessor tProcessor) {
return new TServlet(tProcessor, new TCompactProtocol.Factory());
}
public TServlet createMutableTervlet() {
return new TServlet(new TProcessor() {
@Override
public boolean process(TProtocol in, TProtocol out) throws TException {
return tProcessor.process(in, out);
}
}, new TCompactProtocol.Factory());
}
protected <T> T createThriftClient(Class<T> iface) throws TTransportException {
try {
THttpClient thc = new THttpClient(getUrlString(), HttpClientBuilder.create().build());
TProtocol tProtocol = new TCompactProtocol(thc);
return THClientBuilder.createThriftClient(iface, tProtocol, null);
} catch (TTransportException e) {
throw new RuntimeException(e);
}
}
protected <T> T createThriftRPCClient(Class<T> iface, IdGenerator idGenerator, ClientEventListener eventListener) {
try {
THClientBuilder clientBuilder = new THClientBuilder();
clientBuilder.withAddress(new URI(getUrlString()));
clientBuilder.withHttpClient(HttpClientBuilder.create().build());
clientBuilder.withIdGenerator(idGenerator);
clientBuilder.withEventListener(eventListener);
return clientBuilder.build(iface);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,18 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.generator.IdGenerator;
/**
* Created by vpankrashkin on 06.05.16.
*/
public class IdGeneratorStub implements IdGenerator {
@Override
public String generateId(long timestamp) {
return Long.toString(timestamp);
}
@Override
public String generateId(long timestamp, int counter) {
return new StringBuilder().append(timestamp).append(':').append(counter).toString();
}
}

View File

@ -0,0 +1,36 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.rpc.Owner;
import com.rbkmoney.woody.rpc.OwnerService;
import com.rbkmoney.woody.rpc.err_one;
import org.apache.thrift.TException;
/**
* Created by vpankrashkin on 19.04.16.
*/
public class OwnerServiceStub implements OwnerService.Iface {
@Override
public Owner getOwner(int id) throws TException {
return null;
}
@Override
public Owner getErrOwner(int id) throws err_one, TException {
return null;
}
@Override
public void setOwner(Owner owner) throws TException {
}
@Override
public void setOwnerOneway(Owner owner) throws TException {
}
@Override
public Owner setErrOwner(Owner owner, int id) throws TException {
return null;
}
}

View File

@ -0,0 +1,176 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.event.CallType;
import com.rbkmoney.woody.api.event.ClientEvent;
import com.rbkmoney.woody.api.event.ErrorType;
import com.rbkmoney.woody.api.generator.IdGenerator;
import com.rbkmoney.woody.rpc.Owner;
import com.rbkmoney.woody.rpc.OwnerService;
import com.rbkmoney.woody.rpc.err_one;
import com.rbkmoney.woody.thrift.impl.http.event.THClientEvent;
import org.apache.thrift.TException;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Created by vpankrashkin on 06.05.16.
*/
public class TestClientErrHandling extends AbstractClientTest {
{
tProcessor = new OwnerService.Processor<>(new
OwnerServiceStub() {
@Override
public Owner getOwner(int id) throws TException {
switch (id) {
case 0:
throw new RuntimeException("err");
case 1:
return new Owner(1, "name1");
default:
return new Owner(-1, "default");
}
}
@Override
public Owner getErrOwner(int id) throws err_one {
throw new err_one(id);
}
}
);
}
@Test
public void testUnexpectedError() {
OwnerService.Iface client = (OwnerService.Iface) createThriftRPCClient(OwnerService.Iface.class, new IdGeneratorStub(), (ClientEvent clientEvent) -> {
THClientEvent thClientEvent = (THClientEvent) clientEvent;
switch (thClientEvent.getEventType()) {
case CALL_SERVICE:
assertArrayEquals(new Object[]{0}, thClientEvent.getCallArguments());
assertEquals("getOwner", thClientEvent.getCallName());
assertEquals(CallType.CALL, thClientEvent.getCallType());
assertEquals(IdGenerator.NO_PARENT_ID, thClientEvent.getParentId());
assertNotNull(thClientEvent.getTraceId());
assertEquals(thClientEvent.getTraceId(), thClientEvent.getSpanId());
assertNull(thClientEvent.getEndpoint());
assertNotEquals(thClientEvent.getTimeStamp(), 0);
break;
case CLIENT_SEND:
assertEquals(getUrlString(), thClientEvent.getEndpoint().getStringValue());
break;
case CLIENT_RECEIVE:
assertEquals(new Integer(500), thClientEvent.getThriftResponseStatus());
assertEquals("Server Error", thClientEvent.getThriftResponseMessage());
break;
case SERVICE_RESULT:
fail("Should not be invoked on error");
break;
case ERROR:
assertFalse(thClientEvent.isSuccessfullCall());
assertEquals(ErrorType.PROVIDER_ERROR, thClientEvent.getErrorType());
assertEquals(TErrorType.PROTOCOL, thClientEvent.getThriftErrorType());
default:
}
});
try {
client.getOwner(0);
} catch (TException e) {
e.printStackTrace();
}
}
@Test
public void testExpectedError() {
OwnerService.Iface client = (OwnerService.Iface) createThriftRPCClient(OwnerService.Iface.class, new IdGeneratorStub(), (ClientEvent clientEvent) -> {
THClientEvent thClientEvent = (THClientEvent) clientEvent;
switch (thClientEvent.getEventType()) {
case CALL_SERVICE:
assertArrayEquals(new Object[]{0}, thClientEvent.getCallArguments());
assertEquals("getErrOwner", thClientEvent.getCallName());
assertEquals(CallType.CALL, thClientEvent.getCallType());
assertEquals(IdGenerator.NO_PARENT_ID, thClientEvent.getParentId());
assertNotNull(thClientEvent.getTraceId());
assertEquals(thClientEvent.getTraceId(), thClientEvent.getSpanId());
assertNull(thClientEvent.getEndpoint());
assertNotEquals(thClientEvent.getTimeStamp(), 0);
break;
case CLIENT_SEND:
assertEquals(getUrlString(), thClientEvent.getEndpoint().getStringValue());
break;
case CLIENT_RECEIVE:
assertEquals(new Integer(200), thClientEvent.getThriftResponseStatus());
assertEquals("OK", thClientEvent.getThriftResponseMessage());
break;
case SERVICE_RESULT:
fail("Should not be invoked on error");
break;
case ERROR:
assertFalse(thClientEvent.isSuccessfullCall());
assertEquals(ErrorType.APPLICATION_KNOWN_ERROR, thClientEvent.getErrorType());
assertEquals("err_one", thClientEvent.getErrorName());
assertNull(thClientEvent.getThriftErrorType());
default:
fail();
}
});
try {
client.getErrOwner(0);
} catch (TException e) {
e.printStackTrace();
}
}
@Test
public void testGetOwnerOK() {
OwnerService.Iface client = (OwnerService.Iface) createThriftRPCClient(OwnerService.Iface.class, new IdGeneratorStub(), (ClientEvent clientEvent) -> {
THClientEvent thClientEvent = (THClientEvent) clientEvent;
switch (thClientEvent.getEventType()) {
case CALL_SERVICE:
assertArrayEquals(new Object[]{0}, thClientEvent.getCallArguments());
assertEquals("getOwner", thClientEvent.getCallName());
assertEquals(CallType.CALL, thClientEvent.getCallType());
assertEquals(IdGenerator.NO_PARENT_ID, thClientEvent.getParentId());
assertNotNull(thClientEvent.getTraceId());
assertEquals(thClientEvent.getTraceId(), thClientEvent.getSpanId());
assertNull(thClientEvent.getEndpoint());
assertNotEquals(thClientEvent.getTimeStamp(), 0);
break;
case CLIENT_SEND:
assertEquals(getUrlString(), thClientEvent.getEndpoint().getStringValue());
break;
case CLIENT_RECEIVE:
assertEquals(new Integer(200), thClientEvent.getThriftResponseStatus());
assertEquals("OK", thClientEvent.getThriftResponseMessage());
break;
case SERVICE_RESULT:
assertEquals(new Owner(1, "name1"), thClientEvent.getCallResult());
break;
case ERROR:
fail("Should not be invoked on success");
default:
fail();
}
});
try {
client.getOwner(1);
} catch (TException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,164 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.generator.IdGenerator;
import com.rbkmoney.woody.rpc.Owner;
import com.rbkmoney.woody.rpc.OwnerService;
import com.rbkmoney.woody.rpc.err_one;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.server.TServlet;
import org.apache.thrift.transport.THttpClient;
import org.apache.thrift.transport.TTransportException;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.stream.IntStream;
/**
* Created by vpankrashkin on 05.05.16.
*/
public class TestErrLoadThriftRPCClient {
private Server server;
@Before
public void startJetty() throws Exception {
server = new Server(8080);
ServletContextHandler context = new ServletContextHandler();
ServletHolder defaultServ = new ServletHolder("default", TServletExample.class);
//defaultServ.setInitParameter("resourceBase",System.getProperty("user.dir"));
//defaultServ.setInitParameter("dirAllowed","true");
context.addServlet(defaultServ, "/");
server.setHandler(context);
// Start Server
server.start();
}
@After
public void stopJetty() {
try {
server.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testServlet() throws TTransportException, TException, URISyntaxException {
String servletUrl = "http://localhost:8080/";
OwnerService.Iface tClient = createThriftClient(servletUrl);
OwnerService.Iface tRPCClient = createThriftRPCClient(servletUrl);
try {
tClient.getErrOwner(0);
} catch (TException e) {
Assert.assertSame(e.getClass(), err_one.class);
//e.printStackTrace();
}
try {
tRPCClient.getErrOwner(0);
} catch (TException e) {
Assert.assertSame(e.getClass(), err_one.class);
//e.printStackTrace();
}
int testCount = 20000;
System.out.println("Start warmup");
runHtriftRPC(testCount, tRPCClient);
runThrift(testCount, tClient);
System.out.println("Warmup ended.");
testCount = 10000;
runHtriftRPC(testCount, tRPCClient);
runThrift(testCount, tClient);
testCount = 10000;
runThrift(testCount, tClient);
runHtriftRPC(testCount, tRPCClient);
}
private void runThrift(int testCount, OwnerService.Iface tClient) {
long start = System.currentTimeMillis();
IntStream.range(1, testCount).forEach(i -> {
try {
tClient.getErrOwner(i);
} catch (err_one e) {
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.printf("Thrift: %d iterations, %d time\n", testCount, System.currentTimeMillis() - start);
}
private void runHtriftRPC(int testCount, OwnerService.Iface tRPCClient) {
long start = System.currentTimeMillis();
IntStream.range(1, testCount).forEach(i -> {
try {
tRPCClient.getErrOwner(i);
} catch (err_one e) {
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.printf("Thrift RPC: %d iterations, %d time\n", testCount, System.currentTimeMillis() - start);
}
public static class TServletExample extends TServlet {
public TServletExample() {
super(
new OwnerService.Processor(
new TestErrLoadThriftRPCClient.OwnerServiceImpl()),
new TCompactProtocol.Factory()
);
}
}
private OwnerService.Iface createThriftClient(String url) throws TTransportException {
THttpClient thc = new THttpClient(url, HttpClientBuilder.create().build());
TProtocol loPFactory = new TCompactProtocol(thc);
return new OwnerService.Client(loPFactory);
}
private OwnerService.Iface createThriftRPCClient(String url) throws URISyntaxException {
THClientBuilder clientBuilder = new THClientBuilder();
clientBuilder.withAddress(new URI(url));
clientBuilder.withHttpClient(HttpClientBuilder.create().build());
clientBuilder.withIdGenerator(new IdGenerator() {
@Override
public String generateId(long timestamp) {
return Long.toString(timestamp);
}
@Override
public String generateId(long timestamp, int counter) {
return new StringBuilder().append(timestamp).append(':').append(counter).toString();
}
});
return clientBuilder.build(OwnerService.Iface.class);
}
private static class OwnerServiceImpl extends OwnerServiceStub {
@Override
public Owner getErrOwner(int id) throws TException, err_one {
throw new err_one(id);
}
}
}

View File

@ -0,0 +1,169 @@
package com.rbkmoney.woody.thrift.impl.http;
import com.rbkmoney.woody.api.event.ClientEvent;
import com.rbkmoney.woody.api.event.ClientEventListener;
import com.rbkmoney.woody.api.event.ServiceEvent;
import com.rbkmoney.woody.api.event.ServiceEventListener;
import com.rbkmoney.woody.rpc.Owner;
import com.rbkmoney.woody.rpc.OwnerService;
import com.rbkmoney.woody.rpc.TestHttp;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.server.TServlet;
import org.apache.thrift.transport.THttpClient;
import org.apache.thrift.transport.TTransportException;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import javax.servlet.Servlet;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.stream.IntStream;
/**
* Created by vpankrashkin on 05.05.16.
*/
public class TestLoadThriftRPCClient {
private Server server;
@Before
public void startJetty() throws Exception {
server = new Server(8080);
ServletContextHandler context = new ServletContextHandler();
ServletHolder defaultServ = new ServletHolder("default", TestHttp.TServletExample.class);
context.addServlet(defaultServ, "/default");
THServiceBuilder serviceBuilder = new THServiceBuilder();
serviceBuilder.withIdGenerator(new IdGeneratorStub());
serviceBuilder.withEventListener(new ServiceEventListener() {
@Override
public void notifyEvent(ServiceEvent serviceEvent) {
}
});
Servlet rpcServlet = serviceBuilder.build(OwnerService.Iface.class, new OwnerServiceImpl());
ServletHolder rpcServ = new ServletHolder("rpc", rpcServlet);
context.addServlet(defaultServ, "/rpc");
server.setHandler(context);
// Start Server
server.start();
}
@After
public void stopJetty() {
try {
server.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testServlet() throws TTransportException, TException, URISyntaxException {
String defaultServletUrl = "http://localhost:8080/default";
String rpcServletUrl = "http://localhost:8080/rpc";
OwnerService.Iface tClient = createThriftClient(defaultServletUrl);
OwnerService.Iface tRPCClient = createThriftRPCClient(rpcServletUrl);
Owner bean = tClient.getOwner(1);
Assert.assertEquals("name", bean.getName());
bean = tRPCClient.getOwner(1);
Assert.assertEquals("name", bean.getName());
IntStream.range(1, 1000).forEach(i -> {
try {
tClient.getOwner(i);
tRPCClient.getOwner(i);
} catch (TException e) {
e.printStackTrace();
}
});
int testCount = 10000;
runHtriftRPC(testCount, tRPCClient);
runThrift(testCount, tClient);
System.out.println("Warmup ended.");
testCount = 10000;
runHtriftRPC(testCount, tRPCClient);
runThrift(testCount, tClient);
testCount = 10000;
runThrift(testCount, tClient);
runHtriftRPC(testCount, tRPCClient);
}
private void runThrift(int testCount, OwnerService.Iface tClient) {
long start = System.currentTimeMillis();
IntStream.range(1, testCount).forEach(i -> {
try {
tClient.getOwner(i);
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.printf("Thrift: %d iterations, %d time\n", testCount, System.currentTimeMillis() - start);
}
private void runHtriftRPC(int testCount, OwnerService.Iface tRPCClient) {
long start = System.currentTimeMillis();
IntStream.range(1, testCount).forEach(i -> {
try {
tRPCClient.getOwner(i);
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.printf("Thrift RPC: %d iterations, %d time\n", testCount, System.currentTimeMillis() - start);
}
public static class TServletExample extends TServlet {
public TServletExample() {
super(
new OwnerService.Processor(
new OwnerServiceImpl()),
new TCompactProtocol.Factory()
);
}
}
private OwnerService.Iface createThriftClient(String url) throws TTransportException {
THttpClient thc = new THttpClient(url, HttpClientBuilder.create().build());
TProtocol loPFactory = new TCompactProtocol(thc);
return new OwnerService.Client(loPFactory);
}
private OwnerService.Iface createThriftRPCClient(String url) throws URISyntaxException {
THClientBuilder clientBuilder = new THClientBuilder();
clientBuilder.withAddress(new URI(url));
clientBuilder.withHttpClient(HttpClientBuilder.create().build());
clientBuilder.withIdGenerator(new IdGeneratorStub());
clientBuilder.withEventListener(new ClientEventListener() {
@Override
public void notifyEvent(ClientEvent event) {
}
});
return clientBuilder.build(OwnerService.Iface.class);
}
private static class OwnerServiceImpl extends OwnerServiceStub {
@Override
public Owner getOwner(int id) throws TException {
return new Owner(id, "name");
}
}
}

View File

@ -0,0 +1,6 @@
# Configure Jetty for StdErrLog Logging
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StrErrLog
# Overall Logging Level is INFO
org.eclipse.jetty.LEVEL=INFO
# Detail Logging for WebSocket
org.eclipse.jetty.websocket.LEVEL=DEBUG

View File

@ -0,0 +1,20 @@
namespace java com.rbkmoney.woody.rpc
typedef i32 int // We can use typedef to get pretty names for the types we are using
struct Owner {
1:int id,
2:string name
}
exception err_one {
1:int id
}
service OwnerService
{
Owner getOwner(1:int id),
Owner getErrOwner(1:int id) throws (1:err_one err),
void setOwner(1:Owner owner),
oneway void setOwnerOneway(1:Owner owner)
Owner setErrOwner(1:Owner owner, 2:int id) throws (1:err_one err)
}