Background: using Vertx 3.3.3 Core and Web on Java side as a server, using vertx-web-3.3.3-client.js as the client with sockjsclient1.1.2.js
Issue: I am successfully making a connection to the eventbus from the client when on my local machine or LAN. When I go through a proxy, the wss eventbus connection is being blocked (in Firefox I see "Firefox can't establish a connection to "wss://..."; in Chromium I see "WebSocket connection to wss://... failed: Error during WebSocket handshake: Unexpected response code: 400", then I see "https ://.../eventbus/.../xhr_send?t=... Failed to load resource: the server responded with a status code of 500"). However, the onopen fires and I receive some data (connection downgraded to an accepted protocol?). Immediately after this, onclose fires and I have lost connection. I know that I am successfully reaching the Java vertx server because my static web and API calls are working.
Questions: I have looked through the Vertx and SockJS documentation extensively. Is there:
Documentation on how vertx tries different transport protocols in the JS Eventbus connection?
An example of JS Vertx Eventbus working through a business proxy?
Another way to implement the Eventbus connection, perhaps specifying the SockJS protocol(s) to try/use? (I am trying the simplest way to create an eventbus connection, as shown in many places in documentation)
Something I need to do on the Java side of the SockJS/Eventbus setup?
Thanks in advance for any advice/help!
EDIT 1: Adding the following code for both Java server and JavaScript web client sides. The web side is very basic (and what is failing). The Java side is using Spring for dependency injection and application config, has an Eventbus connection, one API call, and serves static web content.
The API call from client to server works, and the server sources the web contents correctly, so accessing the tool is working. However, the proxy is causing wss to fail (as expected) but the downgrade to xhr-streaming is failing (I think)
Javascript:
var EB;
var URL;
var APICall = "api/eventbus/publish/";
var IncomingAddress = "heartbeat-test";
var OutgoingAddress = "client-test";
function createConnection(){
URL = $("#serveraddressinput").val(); //Getting url from html text box
console.log("Creating Eventbus connection at " + URL + "eventbus"); //Eventbus address is '<link>/eventbus'
EB = new EventBus(URL + "eventbus");
testAPICall();
EB.onopen = function(){
console.log("Eventbus connection successfully made at " + URL + "eventbus");
console.log("Registering Eventbus handler for messages at " + IncomingAddress);
EB.registerHandler(IncomingAddress, function(error, message){
console.log("Received Eventbus message " + JSON.stringify(message));
};
EB.onclose = function(){
console.log("Eventbus connection at " + URL + " has been lost");
URL = "";
};
}
function testAPICall(){
var link = URL + APICall + "heartbeat-test";
console.log("Testing API call to " + link);
$.ajax({
url: link,
type: 'POST',
data: JSON.stringify({"testFromClient": "Test message sent from Client via API Call"}),
dataType: 'json',
success: function (data, textStatus) {
console.log("API Call Success: " + JSON.stringify(data));
},
error: function (request, error) {
console.log("API Call ERROR: " + JSON.stringify(request) + " " + error);
}
});
}
function sendTestMessage(){
console.log("Sending test message to address " + OutgoingAddress);
EB.send(OutgoingAddress, "Testing 1, 2, 3...");
}
Java:
...
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.JksOptions;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.CorsHandler;
import io.vertx.ext.web.handler.StaticHandler;
import io.vertx.ext.web.handler.sockjs.BridgeEvent;
import io.vertx.ext.web.handler.sockjs.BridgeEventType;
import io.vertx.ext.web.handler.sockjs.BridgeOptions;
import io.vertx.ext.web.handler.sockjs.PermittedOptions;
import io.vertx.ext.web.handler.sockjs.SockJSHandler;
import org.apache.logging.log4j.Level;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
#Service
public class MyTestVerticle extends AbstractVerticle {
private final static Logger log = LoggerFactory.getLogger(MyTestVerticle.class);
final Level ACCESS = Level.forName("ACCESS", 450);
private boolean started;
private int port;
#Value("${webserver.testpath.enabled}")
private boolean testPathEnabled;
#Value("${webserver.urlpath.test}")
private String testUrlPath;
#Value("${webserver.filepath.test}")
private String testFilePath;
#Value("${webserver.caching.enabled}")
private boolean cachingEnabled;
#Value("${webserver.ssl.enabled}")
private boolean sslEnabled;
private BridgeOptions bridgeOptions;
private SockJSHandler sockJsHandler;
private Router router;
private JksOptions sslKeyStoreOptions;
private JksOptions sslTrustStoreOptions;
public MyTestVerticle() {
this.started = false;
}
#Override
public void start(Future<Void> fut) throws Exception {
log.info("start() -- starting Vertx Verticle with eventbus, API handler, and static file handler");
// grab the router
router = getRouter();
// enable CORS for the router
CorsHandler corsHandler = CorsHandler.create("*"); //Wildcard(*) not allowed if allowCredentials is true
corsHandler.allowedMethod(HttpMethod.OPTIONS);
corsHandler.allowedMethod(HttpMethod.GET);
corsHandler.allowedMethod(HttpMethod.POST);
corsHandler.allowedMethod(HttpMethod.PUT);
corsHandler.allowedMethod(HttpMethod.DELETE);
corsHandler.allowCredentials(false);
corsHandler.allowedHeader("Access-Control-Request-Method");
corsHandler.allowedHeader("Access-Control-Allow-Method");
corsHandler.allowedHeader("Access-Control-Allow-Credentials");
corsHandler.allowedHeader("Access-Control-Allow-Origin");
corsHandler.allowedHeader("Access-Control-Allow-Headers");
corsHandler.allowedHeader("Content-Type");
// enable handling of body
router.route().handler(BodyHandler.create());
router.route().handler(corsHandler);
router.route().handler(this::handleAccessLogging);
// publish a payload to provided eventbus destination
router.post("/api/eventbus/publish/:destination").handler(this::publish);
// open up all for outbound and inbound traffic
bridgeOptions = new BridgeOptions();
bridgeOptions.addOutboundPermitted(new PermittedOptions().setAddressRegex(".*"));
bridgeOptions.addInboundPermitted(new PermittedOptions().setAddressRegex(".*"));
// sockJsHandler = SockJSHandler.create(vertx).bridge(bridgeOptions);
sockJsHandler = SockJSHandler.create(vertx);
sockJsHandler.bridge(bridgeOptions, be -> {
try {
if (be.type() == BridgeEventType.SOCKET_CREATED) {
handleSocketOpenEvent(be);
}
else if(be.type() ==BridgeEventType.REGISTER) {
handleRegisterEvent(be);
}
else if(be.type() ==BridgeEventType.UNREGISTER) {
handleUnregisterEvent(be);
}
else if(be.type() ==BridgeEventType.SOCKET_CLOSED) {
handleSocketCloseEvent(be);
}
} catch (Exception e) {
} finally {
be.complete(true);
}
});
router.route("/eventbus/*").handler(sockJsHandler);
if(testPathEnabled){
router.route("/" + testUrlPath + "/*").handler(StaticHandler.create(testFilePath).setCachingEnabled(cachingEnabled));
}
// create periodic task, pushing all current EventBusRegistrations
vertx.setPeriodic(1000, handler -> {
JsonObject obj =new JsonObject();
obj.put("testMessage", "Periodic test message from server...");
vertx.eventBus().publish("heartbeat-test", Json.encodePrettily(obj));
});
EventBus eb = vertx.eventBus();
eb.consumer("client-test", message -> {
log.info("Received message from client: " + Json.encodePrettily(message.body()) + " at " + System.currentTimeMillis());
});
HttpServerOptions httpOptions = new HttpServerOptions();
if(sslEnabled){
httpOptions.setSsl(true);
httpOptions.setKeyStoreOptions(sslKeyStoreOptions);
}
log.info("starting web server on port: " + port);
vertx
.createHttpServer(httpOptions)
.requestHandler(router::accept).listen(
port,
result -> {
if (result.succeeded()) {
setStarted(true);
log.info("Server started and ready to accept requests");
fut.complete();
} else {
setStarted(false);
fut.fail(result.cause());
}
}
);
}
private void handleSocketOpenEvent(BridgeEvent be){
String host =be.socket().remoteAddress().toString();
String localAddress = be.socket().localAddress().toString();
log.info("Socket connection opened! Host: " + host + " Local address: " + localAddress);
}
private void handleRegisterEvent(BridgeEvent be){
String host =be.socket().remoteAddress().toString();
String localAddress = be.socket().localAddress().toString();
String address = be.getRawMessage().getString("address").trim();
log.info("Eventbus register event! Address: " + address + " Host: " + host + " Local address: " + localAddress);
}
private void handleUnregisterEvent(BridgeEvent be){
String host =be.socket().remoteAddress().toString();
String localAddress = be.socket().localAddress().toString();
String address = be.getRawMessage().getString("address").trim();
log.info("Eventbus unregister event! Address: " + address + " Host: " + host + " Local address: " + localAddress);
}
private void handleSocketCloseEvent(BridgeEvent be){
String host =be.socket().remoteAddress().toString();
String localAddress = be.socket().localAddress().toString();
log.info("Socket connection closed! Host: " + host + " Local address: " + localAddress);
}
//Method handles logging at custom level for access logging to files
private void handleAccessLogging(RoutingContext routingContext){
Marker accessMarker = MarkerFactory.getMarker("ACCESS");
if(routingContext.normalisedPath().contains("/api")){
log.info(accessMarker, "Api access log: request= " + routingContext.normalisedPath() + " source=" + routingContext.request().remoteAddress());
}
else{
log.info(accessMarker, "Web access log: path= " + routingContext.normalisedPath() + " source= " + routingContext.request().remoteAddress());
}
routingContext.next();
}
/**
* Accept a payload (anything) and publish to the provided destination
*
* #param routingContext
*/
private void publish(RoutingContext routingContext) {
String destination = routingContext.request().getParam("destination");
String payload = routingContext.getBodyAsString();
if ((destination == null) || (payload == null)) {
Exception e = new Exception("Missing arguments");
routingContext.response().setStatusCode(406);
routingContext.fail(e);
} else {
log.info("API Call -> Publishing to destination: " + destination + " payload: " + payload);
vertx.eventBus().publish(destination, payload);
routingContext
.response()
.setStatusCode(200)
.putHeader("content-type", "application/json; charset=utf-8")
.end(payload);
}
}
public boolean isStarted() {
return started;
}
public void setStarted(boolean started) {
this.started = started;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public Router getRouter(){
if(router == null){
router = Router.router(vertx);
}
return router;
}
public void setRouter(Router router){
this.router = router;
}
public void setSslOptions(JksOptions keyStoreOptions, JksOptions trustStoreOptions) {
this.sslKeyStoreOptions = keyStoreOptions;
this.sslTrustStoreOptions = trustStoreOptions;
}
}
This error can be resolved by doing the following:
In the Java verticle, move the Eventbus handler to the top, before any other handlers. I believe the BodyHandler or CorsHandler were messing it up and causing the 500 error.
...
router.route("/eventbus/*").handler(sockJsHandler);
...
// enable handling of body
router.route().handler(BodyHandler.create());
router.route().handler(corsHandler);
router.route().handler(this::handleAccessLogging);
// publish a payload to provided eventbus destination
router.post("/api/eventbus/publish/:destination").handler(this::publish);
Related
In my chrome extension, I am using the PAC script chrome.proxy API that has worked in the past.
How do I connect to a SOCKS5 proxy using this method? I have been using this code, but I get a socks error.
var config = {
mode: "pac_script",
pacScript: {
data: "function FindProxyForURL(url, host) {\n" +
" if (host == 'mygeoip.co /*')\n" +
" return 'SOCKS5 blackhole:80';\n" +
" return 'SOCKS5 "+ip+":"+port+"';\n" +
"}"
}
};
chrome.proxy.settings.set(
{value: config, scope: 'regular'},
function() {}
);
I'm hosting a SignalR Hub on Windows Server 2012 with IIS as an ASP.NET Web application that I've tested successfully on my local machine. But when I publish and try to connect from a Angular application the server responds with 403 Forbidden on the /negotiate request. The Angular application is located on a different domain then the Hub server.
I've read that this is caused by a CORS issue, but I've tried every solution I can find without any change. Can it be a IIS server issue or have I missed something in my code?
The route being called is https://example.com/signalr/negotiate
SignalR Server:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
EnableJSONP = true,
EnableDetailedErrors = true
};
map.RunSignalR(hubConfiguration);
});
}
}
// Hub that handles Online user list
public class OnlineHub : Hub
{
private static List<AppUserDto> _usersOnline = new List<AppUserDto>();
public OnlineHub()
{
// Automapper Setup
MappingConfig.Init();
}
public override Task OnConnected()
{
var user = GetUser();
_usersOnline.Add(user);
Clients.All.listUpdated(_usersOnline);
return base.OnConnected();
}
public override Task OnReconnected()
{
var user = GetUser();
// Add user to list of online users if it doesn't exist
if (!_usersOnline.Any(u => u.Email == user.Email))
{
_usersOnline.Add(user);
Clients.All.listUpdated(_usersOnline);
}
return base.OnReconnected();
}
public override Task OnDisconnected(bool stopCalled)
{
var user = GetUser();
if (!_usersOnline.Any(u => u.Email == user.Email))
{
// Remove user from list of online users
_usersOnline.Remove(user);
Clients.All.listUpdated(_usersOnline);
}
return base.OnDisconnected(stopCalled);
}
private AppUserDto GetUser()
{
using (var db = new EntityDbContext())
{
// Get connected AppUserDto
var user = db.AppUsers.FirstOrDefault(u => u.UserName == Context.User.Identity.Name);
// Add user to list of online users
if (user != null)
{
return Mapper.Map<AppUserDto>(user);
}
return null;
}
}
}
Angular Application SignalR Service
import { AppSettings } from './../app.settings';
import { EventEmitter, Injectable } from '#angular/core';
declare const $: any;
#Injectable()
export class SignalRService {
// Declare the variables
private proxy: any;
private connection: any;
private authData: any;
// create the Event Emitter
public messageReceived: EventEmitter<any>;
public connectionEstablished: EventEmitter<Boolean>;
public connectionExists: Boolean;
constructor(private appSettings: AppSettings) {
// Setup
this.connectionEstablished = new EventEmitter<Boolean>();
this.messageReceived = new EventEmitter<any>();
this.connectionExists = false;
}
public initialize(proxyName: string): void {
this.connection = $.hubConnection(this.appSettings.SIGNALR_BASE_URL);
this.proxy = this.connection.createHubProxy(proxyName);
this.registerOnServerEvents();
this.startConnection();
}
private startConnection(): void {
this.connection.start({withCredentials: false})
.done((data: any) => {
console.log('SignalR Connected with: ' + data.transport.name);
this.connectionEstablished.emit(true);
this.connectionExists = true;
})
.fail((error: any) => {
console.log('SignalR could not connect: ' + error);
this.connectionEstablished.emit(false);
});
}
private registerOnServerEvents() {
this.proxy.on('listUpdated', (list: any) => {
console.log(list);
this.messageReceived.emit(list);
});
}
}
initialize(proxyName) gets called from a controller to start a connection to the Hub.
UPDATE
I've tried to rebuild the server and Hub using .NET Core 2.0, but when I test that on the IIS server I get:
"Failed to load https://signalr.example.com/online/negotiate: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://example.com' is therefore not allowed access."
So it's still a CORS issue even though I've setup everything just as multiple guides have done.
I've had issues in the past where the api path you are trying to hit is actually a virtual directory, and then IIS returns you a 403 because it thinks you are trying to view / access that directory instead of the webAPI route.
GET api/negotiate will 403 if you have the directory api/negotiate on your server.
This will be the case if you WebApiController is located in your project in a directory like:
/api/negotiate/NegotiateApiController.cs
You can resolve this very easily if that's the case by either changing the route or the directory name.
Note: This will come back as a 405 on some browsers.
I have a problem getting token for Microsoft Graph.
I have followed this documentation to get the token.
With the Office Javascript API, I get from my add-in the identity token and the application token.
I have put my add-in on the exchange server, I have checked that an application was created in Azure Active Directory, add all authorizations for Microsoft Graph and Azure Active Directory for this application and generate a key for API access.
When I'm in Outlook, I open my add-in and I get the 2 token. In this step, i think the first step is done.
function getCallbackToken() {
Office.context.mailbox.getCallbackTokenAsync(cbToken);
}
function cbToken(asyncResult) {
var token = asyncResult.value;
console.log("token : " + token);
}
function getIdentityToken() {
Office.context.mailbox.getUserIdentityTokenAsync(cbIdentity);
}
function cbIdentity(asyncResult) {
var identity = asyncResult.value;
console.log("identity : " + identity);
}
function getMailUser() {
console.log(
"displayName : " +
Office.context.mailbox.userProfile.displayName +
", mail adresse : " +
Office.context.mailbox.userProfile.emailAddress
);
}
when I send theses token to my java server, I would like to get the token to Microsoft Graph, I request azure with ADAL4J library with this code
//idToken , token identity or token application get from addin api javascript
public AuthenticationResult acquireTokenForGraphApi(String idToken)
throws Throwable {
final ClientCredential credential = new ClientCredential(" --- application id get in azure application list --- ",
" --- generate key from azure application setting, only display one time ---");
final UserAssertion assertion = new UserAssertion(idToken);
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
String tenantId = "--- tocken get in azure configuration panel, application endpoints";
final AuthenticationContext context = new AuthenticationContext(
"https://login.microsoftonline.com/" + tenantId + "/", false, service);
final Future<AuthenticationResult> future = context.acquireToken("https://graph.windows.net/", assertion, credential, null);
result = future.get();
} catch (ExecutionException e) {
throw e.getCause();
} finally {
if (service != null) {
service.shutdown();
}
}
if (result == null) {
throw new ServiceUnavailableException(
"unable to acquire on-behalf-of token for client " + aadAuthFilterProp.getClientId());
}
return result;
i get an error code
com.microsoft.aad.adal4j.AuthenticationException: {
"error_description": "AADSTS50013: Assertion contains an invalid signature."
[
Reason - The key was not found.,
Thumbprint of key used by client: '0600F9F674620737E73404E287C45A818CB7CEB8',
Configured keys:
[
Key0:Start=02/18/2018, End=02/19/2020, Thumbprint=oZkMJ7Omv9GN7JVM;
Key1:Start=03/31/2018, End=03/31/2020, Thumbprint=xq4mEGikJ5Bkblfw;
Key2:Start=11/16/2016, End=11/16/2018, Thumbprint=i1DVz66b9dfpPV3Z;
]
]
Trace ID: b439ed2f-8a91-401e-91e8-133b57532b00
Correlation ID: cd8ebc72-5173-4725-9c79-e8dc0ef7634b
Timestamp: 2018-04-10 08:27:05Z,
"error": "invalid_grant"
}
This is my JS code to receive a message from Azure Service Bus
function receiveMessage(serviceBusTopic, serviceBusSubscriber, callback) {
serviceBus.receiveSubscriptionMessage(serviceBusTopic, serviceBusSubscriber,
{ isPeekLock: true }, function (error, lockedMessage) {
if (!error) {
try {
const receivedMessage = JSON.parse(lockedMessage.body);
console.log('receivedMessage', receivedMessage);
if (!_.isEqual(receivedMessage.Type, messageType.USERPROFILES_USER_UPDATED)) {
return;
}
//Message received and locked
callback(receivedMessage);
serviceBus.deleteMessage(lockedMessage, function (deleteError) {
if (!deleteError) {
// Message deleted
console.log('message has been deleted.');
}
});
}
catch (error) {
console.log('Start debugging');
console.log(lockedMessage.body);
}
When I receive a message it has strange encoding and JSON.parse throws an exception.
The lockedMessage output is:
{ body: '#\fbase64Binary\b3http://schemas.microsoft.com/2003/10/Serialization/�s\u0002{"Type":"SomeEvent"�\u0001}',
brokerProperties:
{ DeliveryCount: 9,
EnqueuedSequenceNumber: 0,
EnqueuedTimeUtc: 'Thu, 16 Nov 2017 23:50:16 GMT',
LockToken: '6e3e311f-0fe9-4366-844d-18046fd000db',
LockedUntilUtc: 'Fri, 17 Nov 2017 00:10:46 GMT',
MessageId: 'nil',
PartitionKey: '1d84084f-65af-4a33-bb30-62d97d85557d',
SequenceNumber: 61643019899633670,
SessionId: '1d84084f-65af-4a33-bb30-62d97d85557d',
State: 'Active',
TimeToLive: 1566804.069 },
location: '',
contentType: 'application/xml; charset=utf-8',
customProperties: { 'strict-transport-security': NaN, connection: NaN } }
The message is coming from a .NET Core service and that service sends with this code:
var payload = JsonConvert.SerializeObject(SomeEvent);
var serviceBusMessage = new Message(Encoding.UTF8.GetBytes(payload));
serviceBusMessage.SessionId = Guid.NewGuid().ToString("D");
topicClient.SendAsync(serviceBusMessage).Wait();
Why is Node.js not able to parse the message? Another .NET app can receive the same message without any issues.
To avoid this, you need to set ContentType to text/plain when sending a message from .NET Core service. So it should be something like this:
var payload = JsonConvert.SerializeObject(SomeEvent);
var serviceBusMessage = new Message(Encoding.UTF8.GetBytes(payload))
{
ContentType = "text/plain"
};
serviceBusMessage.SessionId = Guid.NewGuid().ToString("D");
topicClient.SendAsync(serviceBusMessage).Wait();
In this article, they explained the problem and the solution for .NET.
Update:
After some diving, this would not happen to me when I either use .NET Core or .NET to send a message with the standard library Microsoft.Azure.ServiceBus whether ContentType is specified or not.
This is my C# code to send a message:
class Program
{
static void Main(string[] args)
{
string connectionString = "Endpoint=sb://...";
var client = new TopicClient(connectionString, "MyTopic");
var payload = JsonConvert.SerializeObject(new DemoMessage() { Title = $"hello core!!! {DateTime.Now}" });
var serviceBusMessage = new Message(Encoding.UTF8.GetBytes(payload));
serviceBusMessage.SessionId = Guid.NewGuid().ToString("D");
client.SendAsync(serviceBusMessage).Wait();
}
private class DemoMessage
{
public DemoMessage()
{
}
public string Title { get; set; }
}
}
This is my Node.js code to receive a message:
var azure = require('azure');
var serviceBusService = azure.createServiceBusService("Endpoint=sb://...");
serviceBusService.receiveSubscriptionMessage('MyTopic', 'sub1', { isPeekLock: true }, function(error, lockedMessage) {
if(!error) {
console.log(lockedMessage);
serviceBusService.deleteMessage(lockedMessage, function (deleteError){
if(!deleteError){
// Message deleted
console.log('message has been deleted.');
}
})
}
});
The lockedMessage output is:
This only happens when I use .NET and the SDK WindowsAzure.ServiceBus with this code:
class Program
{
static void Main(string[] args)
{
string connectionString = "Endpoint=sb://...";
var client = TopicClient.CreateFromConnectionString(connectionString, "MyTopic");
var payload = JsonConvert.SerializeObject(new DemoMessage() { Title = $"hello core!!! {DateTime.Now}" });
var serviceBusMessage = new BrokeredMessage(Encoding.UTF8.GetBytes(payload));
serviceBusMessage.SessionId = Guid.NewGuid().ToString("D");
client.Send(serviceBusMessage);
}
private class DemoMessage
{
public DemoMessage()
{
}
public string Title { get; set; }
}
}
Now, the lockedMessage output is:
So, I think the message you received is sent from another .NET client and I suggest you clear all messages from the topic before you test it in Node.js.
I ran into this issue as well. If you are using Stream Analytics then its compatibility level may be the cause of this issue. Stream Analytics compatibility level 1.0 uses an XML serializer producing the XML tag you are seeing. Compatibility level 1.1 "fixes" this issue.
See my previous answer here: https://stackoverflow.com/a/49307178/263139.
I'm trying to send consumed Kafka data to frontend (JavaScript) via Spring-Websockets in a Spring MVC project.
To establish the communication between the server and client, I have the following.
Client (app.js)
function connect() {
var socket = new SockJS('/kafka-data-websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
stompClient.send("/app/fetchData");
stompClient.subscribe('/data/records', function (message) {
console.log(JSON.parse(message.body).content);
});
});
}
Server (KafkaController.java)
#Controller
public class KafkaController {
#MessageMapping("/fetchData")
#SendTo("/data/records")
public String fetchMetrics() {
//...
}
}
To consume data from a specific Kafka topic I'm using #KafkaListener annotation as follows:
public class KafkaReceiver {
#KafkaListener(topics = "mytopic")
public void receive(ConsumerRecord<?, ?> record) {
MyRecord m = new MyRecord(new Long(record.offset()), record.key().toString(), record.value().toString());
//...
}
}
And I have a proper KafkaConfig class with all the necessary beans (like explained here).
How can I send data from receive method to KafkaController's fetchMetrics (and consequently to the websocket) on each incoming/consumed message?
You should inject SimpMessagingTemplate into the KafkaReceiver and use it from the receive() method:
this.template.convertAndSend("/data/records", m);
See more info in the Spring Framework Reference Manual.