How to create a Stomp Websocket Client in .NET that uses HTTP? - c#

I built a Websocket Server in Java with Spring Boot
My server configuration looks like:
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/my-websocket")
.setAllowedOrigins("*")
.withSockJS();
}
I've created a client in Javascript which works, and the connection looks like:
function connect() {
var myUsername = document.getElementById('from').value;
var host = 'http://localhost:8080/my-websocket';
var socket = new SockJS(host);
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
stompClient.send('/app/status', {}, JSON.stringify(createUserStatus('AVAILABLE')));
stompClient.subscribe('/topic/status', function (message) {
receiveStatus(JSON.parse(message.body));
});
stompClient.subscribe('/queue/private.message.' + myUsername, function (message) {
displayMessage(JSON.parse(message.body));
});
});
}
Now I want to create a client in .NET, but every Stomp client library I've found, doesn't allow http to be the URI, and my server only accepts http connections. My server fails to parse ws and tcp connections, and I can't find a client out there that uses http.
In my .NET client, when I try to connect to string brokerUri = "tcp://localhost:8080/my-websocket"
My server throws this exception:
client-id:ID:DESKTOP-AA29FJ6-52716-637205011942817986-0:0
host:localhost
accept-version:1.0,1.1
heart-beat:10000,30000
]
2020-03-22 19:13:14.414 DEBUG 25036 --- [nio-8080-exec-2] o.apache.coyote.http11.Http11Processor : Error parsing HTTP request header
java.lang.IllegalArgumentException: Invalid character found in method name. HTTP method names must be tokens
How can I create a Websocket connection in .NET that can subscribe to different topics and queues, and send messages over http?
Alternatively, how could I make my server handle ws or tcp connections?
Thank you!

Related

signalR with multiple client connections

I have written an Application where I am using SignalR. I am sending connectionId from Client to Server(controller).
Everything is working fine with single browser (request will sent to server with connectionId="conn_1") and signalR is sending response to only conn_1, but when i open new browser and send a request from that client the previous connection gets disposed. Which means only one connection with particular connectionId remains alive.
Is there any way SignalR can not dispose and send response to both with data they want?
I am new to SignalR and would really appropriate any help or guidance.
Angular SignalRService to start connection with server
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(this.paymentDraftHubUrl)
.build();
return this.hubConnection
.start()
.then(() => this.hubConnectionStatus = 'Connection started')
.catch(err => (this.hubConnectionStatus = 'Error while starting connection: ' + err));
}
sending connectionId from client component to Api
this.signalRService.startConnection().then((connection) => {
this.connectionId = connection.toString();
//Calling Api
this.getAllTransactionException(
this.connectionId,
this.pageNumber,
this.pageSize
}
MyHub class in C#
public class PaymentDraftServiceHub : Hub, IPaymentDraftHub
{}
Controller for API
using timer to keep calling repository for new data,
[HttpGet]
[Route("GetCsrTranactions")]
public IActionResult GetCsrTranactions([FromQuery] TransactionExceptionDataRequest queryParams)
{
TimeManager.Dispose();
var timerManager = new TimeManager(async () =>
await _paymentDraftHub.Clients.Clients.Client(queryParams.ConnectionId).SendAsync(SignalRConstants.TransferPaymentDraftServiceData, await _paymentTransactionRepository.GetCsrTranactionsAsync(queryParams)));
var response = new ResponseMessage { Message = "Accepted", Code = "201" };
return Ok(response);
}
Client can have multiple connections with multiple connection IDs if client connect from multiple browser windows or tabs.
According to the code you provided, we can find that you just pass connection ID of SignalR client within current active browser tab/window to your controller, and in your controller action, you use this code snippet .Client(queryParams.ConnectionId).SendAsync() to send message to a specific client, so other browser windows or tabs would not receive the message.
If you'd like to send message(s) to a client with multiple connections, you need to map SignalR users to connection Ids and retain information about users-to-connectionIds mapping, then you can get all connectionIds of a client and send messages to that client with with multiple connectionIds, like below.
//code logic here
//to get all connectinIds of a client/user
//from user-to-connectionIds mapping table
await _paymentDraftHub.Clients.Clients(connectionIds_here).SendAsync("method_here",args_here);

Proper way to have 2 requests between client and server

I have an P2P network app which acts as a server and client.
The app connects to multiple clients in a mesh and sends "data" to each other once the data has been processed.
If a client gets an incoming request then creates their own request before completing the incoming request it seems to deadlock and neither requests complete. Everything works fine unless a create is sent which processing one.
Is this because I'm not using async on the server methods or is this because I shouldn't send a request while processing one?
proto
rpc submit_data(DataSubmission) returns (DataSubmissionAck);
RPC Server
public async void Start()
{
_server = new Server()
{
Services = {
DataService.BindService(new DataService())),
},
Ports = { new ServerPort(_IpAddress, _Port, ServerCredentials.Insecure) }
};
_server.Start();
}
Client method
public void SubmitData(Data data)
{
...
var serverResponse = _serviceClient.submit_data(request);
...
}
Server method
public override Task<DataSubmissionAck> submit_data(DataSubmission request, ServerCallContext context)
{
DataSubmissionAck clientResponse = new DataSubmissionAck();
return Task.FromResult(clientResponse);
}
What seems to have worked is making the server and client-side calls all async.

C# WebSockets Client to MVC Server

I have an API controller that creates a web socket connection and can connect and interact with fine when using JavaScript, but I'd also like to be able to connect to it using a C# client. I've tried a few different libraries (Alchemy, WebSocket4Net, and WebSocket-Sharp), and they all seem to be timing out during connection open.
Here's the code for the server side:
class WsController : ApiController
{
public HttpResponseMessage Get(string id)
{
HttpContext.Current.AcceptWebSocketRequest(new WsHandler(id));
return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);
}
internal class WsHandler : WebSocketHandler
{
public WsHandler(string id) { /* ... */ }
}
}
If I'm using a JS client, I can use var ws = new WebSocket(url + "?id=someid"); to connect and then send and receive messages. When using a C# client, I get stuck during the handshake and the connect times out. Here's some example code using WebSocket4Net.
var ws = new WebSocket(url);
ws.Opened += new EventHandler(ws_Connect);
ws.Closed += new EventHandler(ws_Disconnect);
ws.Error += new EventHandler<ErrorEventArgs>(ws_Error);
ws.MessageReceived += new EventHandler<MessageReceivedEventArgs>(ws_Receive);
ws.Open(); // Timeout on this line. No errors are thrown to ws_Error.
Is there another way to create a websocket server using MVC's ApiController?

Connecting to Websocket server using C# client instead of Javascript

I was using Alchemy websockets for both my client and server but ran into a problem with corrupted/dropped messsages. So I'm trying out another server side implementation. I implemented the server using Fleck, and when I send messages using javascript, the server receives all the messages, solving my previous problem.
However, I need to be able to send messages to the websocket server from a C# client also. Since Fleck does not have a client side implementation in C#, I thought I'd stick with Alchemy. I left the client-side code unchanged so I thought it should just connect to the server as before, however, no messages are being received (though they are being sent according to the debugger).
Here is my server side implementation (Fleck):
private void OnStartWebSocketServer()
{
var server = new WebSocketServer("ws://localhost:11005");
server.Start(socket =>
{
socket.OnOpen = () => Console.WriteLine("Open!");
socket.OnClose = () => Console.WriteLine("Close!");
socket.OnMessage = message => OnReceive(message);
});
}
private static void OnReceive(String message)
{
UpdateUserLocation(message);
}
Here is my client side implementation (Alchemy):
class WSclient
{
WebSocketClient aClient;
public WSclient(String host, String port)
{
aClient = new WebSocketClient("ws://" + host + ":" + 11005 + "/chat")
{
OnReceive = OnReceive,
OnSend = OnSend,
OnConnect = OnConnected,
OnConnected = OnConnect,
OnDisconnect = OnDisconnect
};
aClient.Connect();
}
...
public void Send(String data)
{
aClient.Send(data);
}
I thought it might have something to do with the fact that the Alchemy client requires a channel at the end of the connection string '/chat'. However leaving it blank, or just the '/' gives an error.

Websocket help!

I'm working on a websocket application. I have a server that is written in C#. I have tested it using another C# application for sending and receiving data. The problem occurs when I use a JavaScript on the chrome developer tool (console) and use Websockets to connect to my server.
I receive the header string from the websocket script, with two keys and the last 8 characters for hashing.
I used the header string keys to generate a hash code and crate a header to send back to chrome(j script on the developer tool).
Issues:-
the onopen event is never triggered and the websocket does not receive the header(I assume). I use the onerror to capture any errors. Which never occur.
The readystate on the websocket is 0 or 2(always).
But when I send a disconnect response from the server, the websocket triggers the onclose method. (So I assume that he was open but not ready to communicate)
Any suggestions??????? Here's the JavaScript if it helps.
websocket = new WebSocket('ws://My server IP here:8080');
try {
websocket.onopen = function(evt) {
open(evt)
//websocket.send("Message to send");
alert("Message is sent...");
}
}
catch(err) {
debug(err,'error')
}
websocket.onerror = function(evt) {
error(evt)
}
websocket.onclose = function(evt) {
close(evt)
}
websocket.onmessage = function(evt) {
message(evt)
}
function open(evt) {
alert("CONNECTED");
doSend("WebSocket rocks");
}
function error(evt) {
alert (evt.data)
}
function close(evt) {
alert("DISCONNECTED");
}
function message(evt) {
alert(evt.data);
}
function doSend(message) {
alert(message);
websocket.send(message);
}
And the header I sent back
HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Origin: chrome://newtab
Sec-WebSocket-Location: ws://My server IP:8080
??i???m?!??9?
Thanks everyone.
It looks like you're trying to respond to the handshake request without the appropriate challenge response. Like Robin mentioned, the handshake is now more complex and involves a challenge for newer versions of the WebSocket protocol. This is a good article that explains the version 76 handshake in more detail.
Here's a code sample that detects the WebSocket protocol version and replies with the appropriate response. (It's in Java, so YMMV, but it should point you in the right direction.)
// Create the WebSocket handshake response.
HttpResponse res = new DefaultHttpResponse(HTTP_1_1,
new HttpResponseStatus(101, "Web Socket Protocol Handshake"));
res.addHeader(Names.UPGRADE, WEBSOCKET);
res.addHeader(CONNECTION, Values.UPGRADE);
// Fill in the headers and contents depending on handshake method.
// New handshake specification has a challenge.
if (req.containsHeader(SEC_WEBSOCKET_KEY1)
&& req.containsHeader(SEC_WEBSOCKET_KEY2)) {
// New handshake method with challenge
res.addHeader(SEC_WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
res.addHeader(SEC_WEBSOCKET_LOCATION, getWebSocketLocation(req));
String protocol = req.getHeader(SEC_WEBSOCKET_PROTOCOL);
if (protocol != null) {
res.addHeader(SEC_WEBSOCKET_PROTOCOL, protocol);
}
// Calculate the answer of the challenge.
String key1 = req.getHeader(SEC_WEBSOCKET_KEY1);
String key2 = req.getHeader(SEC_WEBSOCKET_KEY2);
int a = (int) (Long.parseLong(key1.replaceAll("[^0-9]", "")) / key1
.replaceAll("[^ ]", "").length());
int b = (int) (Long.parseLong(key2.replaceAll("[^0-9]", "")) / key2
.replaceAll("[^ ]", "").length());
long c = req.getContent().readLong();
ChannelBuffer input = ChannelBuffers.buffer(16);
input.writeInt(a);
input.writeInt(b);
input.writeLong(c);
ChannelBuffer output = ChannelBuffers
.wrappedBuffer(MessageDigest.getInstance("MD5").digest(
input.array()));
res.setContent(output);
} else {
// Old handshake method with no challenge:
res.addHeader(WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
res.addHeader(WEBSOCKET_LOCATION, getWebSocketLocation(req));
String protocol = req.getHeader(WEBSOCKET_PROTOCOL);
if (protocol != null) {
res.addHeader(WEBSOCKET_PROTOCOL, protocol);
}
}
// Send the response...
Are you implementing the correct Websockets Protocol version? Chrome has moved to version 76, which means the handshake is more complex than it was. If your Javascript client is failing to connect properly this is possibly the cause.

Categories

Resources