XSocket.net. how to send a message to a client from an object which is not a controller - xsockets.net

I have a class that starts a server:
public class SocketServer
{
private static IXSocketServerContainer server = null;
public SocketServer()
{
server = XSockets.Plugin.Framework.Composable
.GetExport<IXSocketServerContainer>();
}
public bool StartServers()
{
try
{
server.StartServers();
return true;
} catch
{
return false;
}
}
this class has a method:
public void SendEventMessageToAllClients(string message)
{
XSockets.Core.XSocket.Helpers.XSocketHelper
.SendToAll<MyController>(new MyController(), message, "events");
}
where MyController is my own controller, it is implemented and the server can find it and this method work.
Now I would like to expand the functionality with a new method that allows me to send an event to an specific client:
public void SendEventMessageToClient(string clientId, string message)
{
XSockets.Core.XSocket.Helpers.XSocketHelper
.SendTo<MyController>(new MyController(),
p => p.ClientId == clientId, message, "events");
}
Is this the right approach or am I doing something wrong?
Thanks!

I would not recomend that approach, I have not even tested if that actaully works.
You create a new controller every time just to be able to access the extension method.
I am guessing that since you have this on the class starting the server you only use this as a publisher?
If so the correct way would be to install the XSockets.Client package and use the client pool to publish messages: client pool documentation
Example with client pool
The nice thing about the client pool is that you do not need to create an instance every time. The pool will reuse your connection to the controller.
Using the clientpool (or a real client connection) will ensure that the message pass through the Pipeline and all interceptors if you have any. Using a controller instance directly will never reach the pipline, interceptors etc.
//Get a pool client
ClientPool poolClient =
XSockets.Client.ClientPool.GetInstance("ws://127.0.0.1:4502/MyController", "*");
Methods for sending a message to the controller.
public void SendEventMessageToClient(Guid clientId, string message)
{
poolClient.Send(new {clientId, message}, "SendEventMessageToClient");
}
public void SendEventMessageToAllClients(string message)
{
poolClient.Send(message, "SendEventMessageToAllClients");
}
The controller
public void SendEventMessageToClient(Guid clientId, string message)
{
this.SendTo(p => p.ClientId == clientId, message, "SendEventMessageToClient");
}
public void SendEventMessageToAllClients(string message)
{
this.SendToAll(message, "SendEventMessageToAllClients");
}
Example with instance of controller
If you decide to use the way you have done you should at least create on ONE instance of the controller to use the in the server class.
Important: Using a controller instance directly will never reach the pipline, interceptors etc.
//Be aware of the fact that this controller NEVER will have a connection.
//It can only send to others, it can never receive messages!
MyController c = new MyController();
//You should probably have a Guid here instead of string
//Also note that the client have to subscribe for "events" to get the message
public void SendEventMessageToClient(Guid clientId, string message)
{
this.SendTo(p => p.ClientId == clientId, message, "SendEventMessageToClient");
}
public void SendEventMessageToAllClients(string message)
{
this.SendToAll(message, "SendEventMessageToAllClients");
}
Since I do not know what you are trying to accomplish Im not sure this is the best way, but one of the merhods above should work.
EDIT: Also, in a real application you probably dont have access to the MyController class since it probably is in a separate assembly not being referenced at compile time. So then you approach will not even be possible and the way to go then is client or clientpool
/Uffe

Uffe, you're right an the ClientPool is the right option for me, I had problems running your code because some of the mappings proposed by you are not working, here is your proposed solution slightly modified to make it run:
//Get a pool client
ClientPool poolClient = XSockets.Client.ClientPool.GetInstance("ws://127.0.0.1:4502/MyController", "*");
Methods for sending a message to the controller. ITextArgs are needed in this case
public void SendEventMessageToClient(Guid clientId, string message)
{
ITextArgs textargs = new TextArgs(mess, "SendEventMessageToClient");
poolClient.Send(new {clientId = guid, message = "Hello to one client"}, "SendEventMessageToClient");
}
Here, I TextArgs is not needed, it can be used, but string works also properly. It seems that the conversion to ITextArgs works fine here.
public void SendEventMessageToAllClients(string message)
{
poolClient.Send("hello all", "SendEventMessageToAllClients");
}
The controller: Only ITextArgs messages are mapped. Using string will not work.
public void SendEventMessageToClient(Guid clientId, ITextArgs message)
{
c.SendTo(p => p.ClientId == clientId, message.data, "events");
}
public void SendEventMessageToAllClients(ITextArgs message)
{
c.SendToAll(message.data, "events");
}
Thanks you very much Uffe for your help!

Related

Unknown thread spawns which ignores the filter chain and fails on async decorator

I am currently facing a strange issue I am not able to reproduce locally, but happens in AWS ECS regularly, letting the application crash or run slow.
We have a spring boot application which extracts the tenant from the incoming GraphQL request and sets the tenant to a ThreadLocal instance.
To support DataLoader from GraphQL Java kickstart we populate the tenant to each child thread which will be used by the graphql dataloader. The tenant is mandatory to specify the database schema.
The executor
#Bean
#Override
public Executor getAsyncExecutor() {
log.info("Configuring async executor for multi tenancy...");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(15);
executor.setThreadNamePrefix("tenant-child-executor-");
// Important part: Set the MultiTenancyTaskDecorator to populate current tenant to child thread
executor.setTaskDecorator(new MultiTenancyAsyncTaskDecorator());
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
log.info("Executor configured successfully!");
executor.initialize();
return executor;
}
Task Decorator
#NonNull
#Override
public Runnable decorate(#NonNull Runnable runnable) {
if (Objects.isNull(CurrentTenantContext.getTenant())) {
log.warn("Current tenant is null while decorating a new thread!");
}
final TenantIdentifier parentThreadTenantIdentifier = Objects.isNull(CurrentTenantContext.getTenant()) ? TenantIdentifier.asSystem() : CurrentTenantContext.getTenant();
// Also need to get the MDC context map as it is bound to the current local thread
final Map<String, String> parentContextMap = MDC.getCopyOfContextMap();
final var requestAttributes = RequestContextHolder.getRequestAttributes();
return () -> {
try {
CurrentTenantContext.setTenant(TenantIdentifier.of(parentThreadTenantIdentifier.getTenantName()));
if (Objects.isNull(requestAttributes)) {
log.warn("RequestAttributes are not available!");
log.warn("Running on tenant: {}", parentThreadTenantIdentifier.getTenantName());
} else {
RequestContextHolder.setRequestAttributes(requestAttributes, true);
}
if (Objects.isNull(parentContextMap)) {
log.warn("Parent context map not available!");
log.warn("Running on tenant: {}", parentThreadTenantIdentifier.getTenantName());
} else {
MDC.setContextMap(parentContextMap);
}
runnable.run();
} finally {
// Will be executed after thread finished or on exception
RequestContextHolder.resetRequestAttributes();
CurrentTenantContext.clear();
MDC.clear();
}
};
}
Tenant Context
public class CurrentTenantContext {
private static final ThreadLocal<TenantIdentifier> currentTenant = new ThreadLocal<>();
private CurrentTenantContext() {
// Hide constructor to only provide static functionality
}
public static TenantIdentifier getTenant() {
return currentTenant.get();
}
public static String getTenantName() {
return getTenant().getTenantName();
}
public static void setTenant(TenantIdentifier tenant) {
currentTenant.set(tenant);
}
public static void clear() {
currentTenant.remove();
}
public static boolean isTenantSet() {
return Objects.nonNull(currentTenant.get());
}
}
Locally, this works like a charm. Even in a docker compose environment with limited resources (CPU and Mem) like in AWS. Even 100.000 requests (JMETER) everything works like expected.
On AWS we can easily let the application crash.
After one or two requests, containing some child objects to resolve by GraphQL, we see a thread spawning which seems to ignore or not go through the chain
Thread-110 | [sys ] | WARN | MultiTenancyAsyncTaskDecorator | Current tenant is null while decorating a new thread!
An interesting thing in this line is the name of the thread.
Each incoming request has the pattern http-nio-9100-exec-[N] and each child thread the pattern tenant-child-executor-[I] but this one has the pattern Thread-[Y].
Now I am wondering where this thread is coming from and why is it not reproducible locally.
I was able to find the solution to the problem.
I needed to change
private static final ThreadLocal<TenantIdentifier> currentTenant = new ThreadLocal<>();
to
private static final InheritableThreadLocal<TenantIdentifier> currentTenant = new InheritableThreadLocal<>();
But I don't know why it works with InheritableThreadLocal but not with ThreadLocal within the AWS environment.
Further, I wonder why this change was not necessary for local testing which works with both ways.
Maybe somebody can provide some ideas.

Mockito mock(clazz, delegatesTo(..)) vs anonymous class

I'm working on GRPC client for the server.
In GRPC repo the advise is to mock a service in a such manner:
private final GreeterGrpc.GreeterImplBase serviceImpl =
mock(GreeterGrpc.GreeterImplBase.class, delegatesTo(
new GreeterGrpc.GreeterImplBase() {
// By default the client will receive Status.UNIMPLEMENTED for all RPCs.
// You might need to implement necessary behaviors for your test here, like this:
//
// #Override
// public void sayHello(HelloRequest request, StreamObserver<HelloReply> respObserver) {
// respObserver.onNext(HelloReply.getDefaultInstance());
// respObserver.onCompleted();
// }
}));
https://github.com/grpc/grpc-java/blob/master/examples/src/test/java/io/grpc/examples/helloworld/HelloWorldClientTest.java
I wonder, what would change if I just replace
mock(GreeterGrpc.GreeterImplBase.class, delegatesTo(
with anonymous class creation like this:
private final GreeterGrpc.GreeterImplBase serviceImpl =
new GreeterGrpc.GreeterImplBase() {
// By default the client will receive Status.UNIMPLEMENTED for all RPCs.
// You might need to implement necessary behaviors for your test here, like this:
//
// #Override
// public void sayHello(HelloRequest request, StreamObserver<HelloReply> respObserver) {
// respObserver.onNext(HelloReply.getDefaultInstance());
// respObserver.onCompleted();
// }
};
I don't see any benefits Mockito can offer here as all calls are delegated to the delegate.
Is it correct or am I missing something?
You will lose the ability to use Mockito to verify that your service was interacted with in some specific way. E.g. the "verify(serviceImpl)" call you can see in HelloWorldClientTest would not work.

Spring Boot #Async not working

I expect that uploadImage method finishes once the file is uploaded to AWS, while scanFile method is still running asynchronously in the background;
#RestController
public class EmailController {
#PostMapping("/upload")
#ResponseStatus(HttpStatus.OK)
public void uploadImage(#RequestParam MultipartFile photos) {
awsAPIService.uploadImage(photos);
}
}
...
#Service
public class AwsAPIService {
public void uploadImage(MultipartFile file) {
try {
File fileToUpload = this.convertMultiPartToFile(file);
String fileName = this.generateFileName(file);
s3client.putObject(new PutObjectRequest(AWS_S3_QUARANTINE_BUCKET_NAME,fileName, fileToUpload));
fileToUpload.delete();
// start scan file
scanFile();
} ...
}
#Async
public void scanFile() {
log.info("Start scanning");
String queueUrl = sqs.getQueueUrl("bucket-antivirus").getQueueUrl();
List<Message> messages = sqs.receiveMessage(new ReceiveMessageRequest().withQueueUrl(queueUrl)
.withWaitTimeSeconds(20)).getMessages();
for (Message message : messages) {
// delete message
...
}
}
}
...
#EnableAsync
public class AppConfig {
#Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setMaxPoolSize(2);
taskExecutor.setQueueCapacity(200);
taskExecutor.afterPropertiesSet();
return taskExecutor;
}
}
But this seems still running synchronously. What is the problem here?
By default #Async and other Spring method-level annotations like #Transactional work only on the external, bean-to-bean method call. An internal method call from uploadImage() to scanFile() in the same bean won't trigger the proxy implementing the Spring behaviour. As per Spring docs:
In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with #Transactional. Also, the proxy must be fully initialized to provide the expected behaviour so you should not rely on this feature in your initialization code, i.e. #PostConstruct.
You could configure AspectJ to enable annotations on internal method calls, but it's usually easier to refactor the code.

Getting SQS dead letter queue to work with Spring Boot and JMS

I've been working on a small Spring Boot application that receives messages from Amazon SQS. However I foresee that processing these messages may fail, so that's why I thought adding a dead letter queue would be a good idea.
There is a problem though: when the processing fails (which I force by throwing an Exception for some of the messages) it is not reattempted later on and it's not moved to the dead letter queue. I am struggling to find the issue, since there doesn't seem to much info on it.
However if I look at Amazon's documentation, they seem to be able to do it, but without using the Spring Boot annotations. Is there any way I can make the code below work transactional without writing too much of the JMS code myself?
This is the current configuration that I am using.
#Configuration
public class AWSConfiguration {
#Value("${aws.sqs.endpoint}")
private String endpoint;
#Value("${aws.iam.key}")
private String iamKey;
#Value("${aws.iam.secret}")
private String iamSecret;
#Value("${aws.sqs.queue}")
private String queue;
#Bean
public JmsTemplate createJMSTemplate() {
JmsTemplate jmsTemplate = new JmsTemplate(getSQSConnectionFactory());
jmsTemplate.setDefaultDestinationName(queue);
jmsTemplate.setDeliveryPersistent(true);
jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);
return jmsTemplate;
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(getSQSConnectionFactory());
factory.setConcurrency("1-1");
return factory;
}
#Bean
public JmsTransactionManager jmsTransactionManager() {
return new JmsTransactionManager(getSQSConnectionFactory());
}
#Bean
public ConnectionFactory getSQSConnectionFactory() {
return SQSConnectionFactory.builder()
.withAWSCredentialsProvider(awsCredentialsProvider)
.withEndpoint(endpoint)
.withNumberOfMessagesToPrefetch(10).build();
}
private final AWSCredentialsProvider awsCredentialsProvider = new AWSCredentialsProvider() {
#Override
public AWSCredentials getCredentials() {
return new BasicAWSCredentials(iamKey, iamSecret);
}
#Override
public void refresh() {
}
};
}
And finally the receiving end:
#Service
public class QueueReceiver {
private static final String EXPERIMENTAL_QUEUE = "${aws.sqs.queue}";
#JmsListener(destination = EXPERIMENTAL_QUEUE)
public void receiveSegment(String jsonSegment) throws IOException {
Segment segment = Segment.fromJSON(jsonSegment);
if(segment.shouldFail()) {
throw new IOException("This segment is expected to fail");
}
System.out.println(segment.getText());
}
}
Spring Cloud AWS
You can greatly simplify your configuration by leveraging Spring Cloud AWS.
MessageHandler
#Service
public class MessageHandler {
#SqsListener(value = "test-queue", deletionPolicy = SqsMessageDeletionPolicy.NEVER)
public void queueListener(String msg, Acknowledgment acknowledgment){
System.out.println("message: " + msg);
if(/*successful*/){
acknowledgment.acknowledge();
}
}
}
The example shown above is all you need to receive messages. This assumes you've created an sqs queue with an associated dead letter queue. If you're messages aren't acknowledged, then they will be retried again until they reach the maximum # of receives. Then it will be forwarded to the dead letter queue.

Cannot resolve the Symbol Callback. Unable to import play.libs.F.Callback

I am implementing unit test using TestBrowser. I need to send the configuration to the test browser.
Here is the link of what I want to implement.
I write the code as :
#Test
public void testIndexPageRetrieval() {
running(testServer(testPort, fakeApplication(configuration.asMap())), HTMLUNIT, new Callback<TestBrowser>() {
#Override
public void invoke(TestBrowser browser) {
browser.maximizeWindow();
IndexPage indexPage = new IndexPage(browser.getDriver(), testPort, 0);
browser.goTo(indexPage);
indexPage.isAt();
}
});
}
Unable to import the Callback class. I tried import it manually but play.libs.F.Callback is not available. How do I can use Callback class or is there otherway around for this ?