Django channels with Spring Boot Websockets (StompSession) do not work - django

Hey there we want to use Django just to execute python code and use channels for the results. Implemented everything the websockets are not working as they should. If I try to send something from our Angular frontend to Django it works fine. And otherwise our Spring Boot StompSession Websockets work fine with other Spring Boot Applications and Angular.
But Django 3.1.3 and Spring Boot 2.3.3 do not.
Additionally channels 3.0.2, channels-redis 3.2.0 and a docker container of redis 6 are used.
What I can see from the Django server logs:
WebSocket HANDSHAKING /ws/card-detection/ [127.0.0.1:53209]
i'm in connect method
WebSocket CONNECT /ws/card-detection/ [127.0.0.1:53209]
i'm in receive method
text_data: CONNECT
This gets executed as soon as the websocket request comes in.
The django consumer (without the prints that are logged above):
class MyConsumer(WebsocketConsumer):
groups = ["broadcast"]
def connect(self):
self.accept()
def receive(self, text_data=None, bytes_data=None):
self.send(text_data="Hello world!")
def disconnect(self, close_code):
pass
From Spring Boot we send an base64 encoded image. But also everything else does not work. As you can see the text_data is just CONNECT.
Spring Boot:
StompSessionHandler sessionHandler =
new CardDetectionStompSessionHandler(request, user, webSocketMessagingService);
createStompClient().connect(djangoServiceWSPath, sessionHandler);
}
private WebSocketStompClient createStompClient() {
WebSocketClient client = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(client);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
return stompClient;
}
The stomp session handler to give you an idea how it looks. It should go in the afterConnected() method but it will never go in any of this methods:
public class CardDetectionStompSessionHandler extends StompSessionHandlerAdapter {
private final ImageClassificationRequest request;
private final String username;
private final String destination = "/card-detection/";
private final WebSocketMessagingService webSocketMessagingService;
private StompSession session;
public CardDetectionStompSessionHandler(
ImageClassificationRequest request,
String username,
WebSocketMessagingService webSocketMessagingService) {
this.request = request;
this.username = username;
this.webSocketMessagingService = webSocketMessagingService;
}
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
log.info("Client: New session established: {}", session.getSessionId());
this.session = session;
session.subscribe("/user/queue/response", this);
log.info("Client: Subscribed to \"/user/queue/response\"");
session.send("/app" + destination, request);
log.info("Client: Message sent to {}", session.getSessionId());
}
#Override
public Type getPayloadType(StompHeaders headers) {
return ImageClassificationResponse.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
log.info("Client: Message received");
ImageClassificationResponse response = (ImageClassificationResponse) payload;
webSocketMessagingService.sendToUser(username, response);
if (response.isDone()) {
session.disconnect();
log.info("Client: Disconnected.");
}
}
#Override
public void handleTransportError(StompSession session, Throwable exception) {
log.info("Client: Django is not available");
WebSocketResponse response = new WebSocketResponse();
response.setDisconnect(true);
webSocketMessagingService.sendToUser(username, response);
}
}
We tried to change it to an async websocket in Django but that didn't changed a thing. The self.accept in Djangos connect() method does not seem to send a response or the response Spring Boot needs to go on.

Related

Data is not reaching to the websocket clients in AWS API Gateway websocket

I have created an lambda function to which I am not able to send the data to the web socket clients. However there is NO error is coming.
Till yesterday code was working. Dont know what happend now it stopped working.
public string FunctionHandler(string input, ILambdaContext context)
{
try
{
string SK = "xxxxxx";
string AK = "xxxx";
var stream = new MemoryStream(UTF8Encoding.UTF8.GetBytes(input));
var apiClient = new AmazonApiGatewayManagementApiClient(AK,SK,new AmazonApiGatewayManagementApiConfig
{ ServiceURL = $"https://xxxxxx.execute-api.us-east-1.amazonaws.com/Test" });
apiClient.PostToConnectionAsync(new Amazon.ApiGatewayManagementApi.Model.PostToConnectionRequest
{
ConnectionId = "​fdCqPfd0oAMCJmg=",
Data = stream
});
return input.ToUpper();
}
catch(Exception ex)
{
return ex.ToString();
}
}
I gather you're using .NET AmazonApiGatewayManagementApiClient. You're missing the await keyword for apiClient.PostToConnectionAsync():
await apiClient.PostToConnectionAsync(...);
Otherwise you're calling PostToConnectionAsync() without waiting for completion and your Lambda might complete and exit before your request is run and you'll never know about it. (See asynchronous concepts for details)
Alternatively, don't use async method:
apiClient.PostToConnection(...);

Why won't unit tests connect to a websocket

UPDATE: I've uploaded a repo - https://github.com/mrpmorris/CannotIntegrationTestWebApp/blob/master/TestProject1/UnitTest1.cs
I have a web server that serves both HTTPS and WebSocket requests. When I run the app I am able to connect and make requests from postman for both HTTPS://localhost:8080 and WSS://localhost:8080/game-server
using Gambit.ApplicationLayer;
using Gambit.GameServer.Configuration;
using Gambit.GameServer.UseCases;
namespace Gambit.GameServer;
public class Program
{
public static async Task Main(string[] args)
{
WebApplication app = BuildApp(args);
await RunAppAsync(app);
}
public static WebApplication BuildApp(string[] args, Action<WebApplicationBuilder>? configure = null)
{
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
IServiceCollection services = builder.Services;
IConfiguration configuration = builder.Configuration;
IWebHostEnvironment environment = builder.Environment;
services.AddControllers();
services.AddLogging(opts =>
{
opts.ClearProviders();
opts.AddConfiguration(configuration.GetSection("Logging"));
opts.AddDebug();
opts.AddEventSourceLogger();
#if DEBUG
if (environment.IsDevelopment())
opts.AddConsole();
#endif
});
services.Configure<GameServerOptions>(configuration.GetSection("GameServer"));
services.AddApplicationServices(configuration);
configure?.Invoke(builder);
WebApplication app = builder.Build();
return app;
}
public static async Task RunAppAsync(WebApplication app)
{
app.MapGet("/", () => "Gambit.Server.API is running");
app.AddUserUseCases();
app.AddGameUseCases();
app.MapControllers();
app.UseWebSockets();
await app.RunAsync();
}
}
When I run my unit tests I use the same code to create and run the server (once per test run) my tests are able to make HTTPS requests but not connect via a WebSocket. When I try, I get a 404 error. I experience the same in PostMan.
static IntegrationTestsServer()
{
ConfigureMocks();
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "IntegrationTesting");
var app = Program.BuildApp(Array.Empty<string>(), builder =>
{
builder.WebHost.UseSetting("urls", "https://localhost:8080");
});
Configuration = app.Services.GetRequiredService<IConfiguration>();
GameServerOptions = app.Services.GetRequiredService<IOptions<GameServerOptions>>();
var dbContextOptions = app.Services.GetRequiredService<DbContextOptions<ApplicationDbContext>>();
using var dbContext = new ApplicationDbContext(dbContextOptions);
dbContext.Database.EnsureDeleted();
dbContext.Database.EnsureCreated();
HttpClient = new HttpClient { BaseAddress = new Uri("https://localhost:8080") };
_ = Program.RunAppAsync(app);
}
I can even perform a successful HttpClient.GetAsync("https://localhost:8080") immediately before the ClientWebSocket fails
System.Net.WebSockets.WebSocketException : The server returned status code '404' when status code '101' was expected.
Does anyone have any ideas why this might be?
Set ApplicationName in the WebApplicationOptions sent to WebApplication.CreateBuilder
WebApplication.CreateBuilder
(
new WebApplicationOptions
{
ApplicationName = typeof(Gambit.GameServer.Program).Assembly.GetName().Name // <==
}
);
Now it will be able to find your manifest file when running from a test.
See the following blog post for more of the back story on how I figured it out.
https://thefreezeteam.com/posts/StevenTCramer/2022/08/25/runwebserverintest

Camel Restlet sends response to client asynchronously

I have a scenario that I'm using camel-restlet component to receive post requests, I'm forwarding these requests to an external web service, after receiving the response code from the external service, I need to add this response code to my own response to the client asynchronously.
Im trying to save the response object to a hashMap where key is an unique serial number generated based on the request content, once upon receiving the response from external web service, I can retrieve the response object from the hashMap using this unique key. Seems like restlet saves the response to exchange.getOut() message and sends back to the client synchronously which is not something I want. Not setting an out message would give me a nullPointerException.
route Class:
public class ReceiveRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("restlet:http://localhost:8083/api/atmp?restletMethod=post")
.to("activemq:queue:requestReceiveQueue");
from("activemq:queue:requestReceiveQueue")
.process(new RequestProcessor())
.to("activemq:queue:requestSendQueue");
from("activemq:queue:requestSendQueue")
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.to("jetty:http://localhost:8080/rest_api_demo/api/restService")
.bean("responseProcessor");
}
}
requestProcessor class:
public class RequestProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
Message message = exchange.getIn();
byte[] bytes = (byte[])message.getBody();
String body = new String(bytes);
String atmpId = GUIDGenerator.generateAtmpSerialNumber();
String terIndentifier = GUIDGenerator.generateTerminalIdentifier(body);
MapLookupHelper.insertResponse(atmpId, terIndentifier, exchange);
Map<String, Object> messageMap = new HashMap<String, Object>();
messageMap = FormatUtil.parseJson(body);
messageMap.put("ATMPId", atmpId);
exchange.getIn().setBody(messageMap.toString());
}
}
responseProcessor class
#Component
public class ResponseProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
Message in = exchange.getIn();
String responseCode = in.getHeader(Exchange.HTTP_RESPONSE_CODE).toString();
String body = in.getBody().toString();
Map<String, Object> resMap = new HashMap<String, Object>();
resMap = FormatUtil.parseJson(body);
String atmpId = resMap.get("ATMPId").toString();
Exchange ex = MapLookupHelper.getOutMessage(atmpId);
ex.getOut().setHeader("HostResponseCode", responseCode);
ex.getOut().setBody(resMap.toString());
}
}
I'm new to Apache Camel and would like to know if restlet is the right way to go, if not, any suggestion on how I can handle async responses to client in Camel? Is AsyncProcessor only solution to such scenario?
I think it's not issue of restlet. Your exchange pattern is InOut, that's why all jms-endpoint's waiting synchronously result of your .bean("responseProcessor").
Even if you change pattern to InOnly your client will not receive response asynchronously. I think you should make another route's architecture, like below:
from("restlet:http://localhost:8083/api/atmp_asyncRequest?restletMethod=post")
.process(exchange -> {
exchange.setProperty("uniqueRequestId", GUIDGenerator.generateAtmpSerialNumber());
})
.inOnly("seda:requestReceiveQueue")// here starts async processing of your request
.process(exchange -> {
exchange.getProperty("uniqueRequestId");
// make here response for client with generated request id
});
from("seda:requestReceiveQueue")
.process(exchange -> {
// prepare\process request if need
})
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.to("jetty:http://localhost:8080/rest_api_demo/api/restService")
.process(exchange -> {
exchange.getProperty("uniqueRequestId");
// save somewhere prepared response for client bound to generated request id
});
from("restlet:http://localhost:8083/api/atmp_getResponse?restletMethod=post")
.process(exchange -> {
String requestId = ;//extract request id from client's request
Object body = ;//find response that you saved asynchronously by extracted request id
// if response not found, then async processing request not ended, so you should send message to client to continue polling
exchange.getIn().setBody(body);
});
That will work if you haven't callback server for async responses on client's side.
Also you can use Seda component instead of jms, for queueing tasks between routes.

Web service to connect to remote queue

I am developing a Web Service solution that is hosted inside a JBoss 4.2.3 sever (as the following) and my code connect to Remote JMS queue hat is hosted on another server(192.168.35.25)
public static Context getInitialContext () throws JMSException,NamingException
{
Properties prop = new Properties();
prop.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
prop.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming");
prop.setProperty("java.naming.provider.url", "192.168.35.20:1099");
Context context = new InitialContext(prop);
return context;
}
public String SendMessages(String msg) throws ServletException, IOException, URISyntaxException {
String body="";
try
{
Context init =ClsSat.getInitialContext();
javax.jms.Queue destination = (javax.jms.Queue) init.lookup("Queue/RemoteQueue");
ConnectionFactory connectionFactory = (ConnectionFactory) init.lookup("ConnectionFactory");
Connection connection = connectionFactory.createConnection("un","pwd");//
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
;
MessageProducer producer = session.createProducer(destination);
TextMessage message = session.createTextMessage();
message.setText(msg);
connection.start();
producer.send(message);
body = message.getText();
session.close();
connection.close();
}
catch (Exception e)
{
return(e.toString());
}
return body ;
}
But I got an error ... RemoteQueue not bound
so any one have an idea or help about connect to remote queus throw web service
or any documents about connect to Remote queue on remote machine from web service hosted on local machine
Can it be because of the wrong IP address in your getInitialContext(), please check if not you can provide some more detail.

Netbeans Basic Http Auth Jax-WS

how can I access a webservice through a basic http authentification? I am using the netbeans built in webservice client features. But when I try to access the webservice, I get an exception with a 401 auth failed error message.
How can I pass the right username and password?
Thank you!
You could use BindingProvider or WSBindingProvider class to access a Web Service through a basic http authentification.
The code is as follows.
XxxService service = new XxxService();
Xxx port = service.getXxxPort();
Map<String, Object> reqContext = ((BindingProvider)port).getRequestContext();
reqContext.put(BindingProvider.USERNAME_PROPERTY, "username");
reqContext.put(BindingProvider.PASSWORD_PROPERTY, "password");
You can also provide your own Authenticator. That way it will work even if the WDSL itself is protected by basic HTTP authentication.
#WebServiceRef(wsdlLocation = "https://laka/sito?wsdl")
static XxxService service;
public static void main(String[] args) {
Authenticator.setDefault(new Authenticator() {
#Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("user", "password".toCharArray());
}
});
service = new XxxService();
Xxx port = service.getXxxPort();
// invoke webservice and print response
XxxResponse resp = port.foo();
System.out.println(resp.toString());
}