How to add Channel Handler to TCPServer on Channel initialization - project-reactor

I have the following code snipped that creates a TCPServer, and attaches a ChannelHandler to the channel in the doOnChannelInit() function. The server is to process byte data from an embedded device.
#Component
#RequiredArgsConstructor
public class NettyServer {
Logger log = LoggerFactory.getLogger(NettyServer.class);
private final NettyProperties nettyProperties;
private final NettyServerHandler nettyServerHandler;
private TcpServer server;
public void run() {
server = TcpServer
.create()
.host("localhost")
.port(nettyProperties.getTcpPort())
.doOnChannelInit((connectionObserver, channel, remoteAddress) -> {
log.info("Connection from " + remoteAddress);
channel.pipeline()
.addLast("idleStateHandler", new IdleStateHandler(0, 0, 4, TimeUnit.MINUTES))
.addLast(new ByteArrayDecoder())
.addLast(new ByteArrayEncoder())
.addLast(nettyServerHandler);
});
server.bindNow();
log.info("Server running");
}
}
Channel handler
#Component
#RequiredArgsConstructor
#ChannelHandler.Sharable
public class NettyServerHandler extends SimpleChannelInboundHandler<byte[]> {
Logger log = LoggerFactory.getLogger(NettyServerHandler.class);
private final AttributeKey<byte[]> dataKey = AttributeKey.valueOf("dataBuf");
private final AttributeKey<Integer> dataLen = AttributeKey.valueOf("dataBufLen");
#Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("New Meter connection from : " + ctx.channel());
}
#Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel() != null) {
log.info(String.format("Meter/Client Disconnected. No: %s ; Channel : %s", meterNo, ctx.channel()));
}
ctx.close();
}
#Override
protected void channelRead0(ChannelHandlerContext ctx, byte[] msg) throws Exception {
log.info("Message received: " + new String(msg);
ctx.channel().read();
}
}
I'm able to connect to the server, but when i send a message, nothing happens, the log statements are not triggered.
I'm not sure what I'm missing here, would appreciate some help.
Thanks

There is no need to add custom handlers to the Netty pipeline. The example above can be written like this:
#Component
#RequiredArgsConstructor
public class NettyServer {
Logger log = LoggerFactory.getLogger(NettyServer.class);
private final NettyProperties nettyProperties;
private TcpServer server;
public void run() {
server = TcpServer
.create()
.host("localhost")
.port(nettyProperties.getTcpPort())
.doOnChannelInit((connectionObserver, channel, remoteAddress) -> {
log.info("Connection from " + remoteAddress);
channel.pipeline()
.addLast("idleStateHandler", new IdleStateHandler(0, 0, 4, TimeUnit.MINUTES));
})
.handle((in, out) ->
in.receive()
.asString()
.doOnNext(s -> log.info("Message received: " + s))
.then());
server.bindNow();
log.info("Server running");
}
}
Consider checking the Reference Documentation
The incoming data can be transformed to String with (asString), to byte[] with (asByteArray) etc. If there is no suitable transformation you can use map(byteBuf -> ...) and transform the ByteBuf to the needed abstraction.

Related

How to implement auto-reply between MQTT client

I want to implement a MQTT client which can auto-reply when it get a message.
for example:
client1 send a "request" message to client2 (by topic "/toClient2").
client2 get the "request" message and reply a "response" message to client1 (by topic "/toClient1").
I use org.eclipse.paho.client.mqttv3 v1.2.5 and mosquitto as broker to implement it. And it works when client1 send 1 request to client2 in one time. (client2 can reply "response" correctly)
But I found a problem that if client1 sent a lot (e.g. 100, 10000) "request" Continuously to client2, client2 can't publish message or receive message.
Then client2 would disconnect.
my code is here:
package test;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
public class Client implements MqttCallback {
private final int id;
private MqttClient mqttClient;
private int qos = 1;
private int numGet = 0;
private int numSend = 0;
public Client(int id) {
this.id = id;
String HOST = "tcp://127.0.0.1:1883";
MqttConnectOptions options = new MqttConnectOptions();
try {
mqttClient = new MqttClient(HOST, String.valueOf(id), new MemoryPersistence());
options.setCleanSession(true);
options.setConnectionTimeout(10);
options.setKeepAliveInterval(20);
options.setMaxInflight(1000);
mqttClient.setCallback(this);
mqttClient.connect(options);
System.out.println("Connected to MQTT Broker");
} catch (MqttException e) {
e.printStackTrace();
}
}
private void subscribe(String topic) {
try {
mqttClient.subscribe(topic, qos);
} catch (Exception e) {e.printStackTrace();}
}
public void publish(String topic, String payload) {
try {
boolean retained = false;
mqttClient.publish(topic, payload.getBytes(), qos, retained);
} catch (Exception e) {e.printStackTrace();}
}
#Override
public void connectionLost(Throwable cause) {}
#Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
System.out.println(topic + " get: " + message);
if (id == 2)
publish("/toClient1", "Response");
numGet++;
}
#Override
public void deliveryComplete(IMqttDeliveryToken token) { numSend++; }
public static void main(String[] args) throws InterruptedException {
Client client1 = new Client(1);
Client client2 = new Client(2);
String topic1 = "/toClient1";
String topic2 = "/toClient2";
client1.subscribe(topic1);
client2.subscribe(topic2);
int n = 100;
for (int i = 0; i < n; i++) {
client1.publish(topic2, "request");
}
Thread.sleep(1000);
System.out.println("Client1 sent messages: " + client1.numSend);
System.out.println("Client2 sent messages: " + client2.numSend);
}
}
I got the output like this:
Connected to MQTT Broker
Connected to MQTT Broker
/toClient2 get: request
/toClient1 get: Response
Client1 sent messages: 100
Client2 sent messages: 0
1月 29, 2023 1:02:10 下午 org.eclipse.paho.client.mqttv3.internal.ClientState checkForActivity
严重: 2: Timed out as no activity, keepAlive=20,000,000,000 lastOutboundActivity=6,831,960,648,000 lastInboundActivity=6,811,951,900,600 time=6,851,960,579,800 lastPing=6,831,960,650,700
等待来自服务器的响应时超时 (32000)
at org.eclipse.paho.client.mqttv3.internal.ExceptionHelper.createMqttException(ExceptionHelper.java:31)
at org.eclipse.paho.client.mqttv3.internal.ClientState.checkForActivity(ClientState.java:747)
at org.eclipse.paho.client.mqttv3.internal.ClientComms.checkForActivity(ClientComms.java:818)
at org.eclipse.paho.client.mqttv3.internal.ClientComms.checkForActivity(ClientComms.java:804)
at org.eclipse.paho.client.mqttv3.TimerPingSender$PingTask.run(TimerPingSender.java:79)
at java.base/java.util.TimerThread.mainLoop(Timer.java:556)
at java.base/java.util.TimerThread.run(Timer.java:506)
I suspect that I should not do publish() in public void messageArrived(String topic, MqttMessage message) callback, so I try to use a queue to enqueue the "request" message and use a new Thread to consume the queue and do publish(). But it get the same result.

mosquito MQTT message handler getting messages with some delay not real time

Following is the MQTT configuration to listening event.
For high message load around 100 message per second I noticed messages not received realtime on handler.
public class VehicleEventMqttConfig {
#Value("${mqtt.auto-startup.vehicleEvent:false}")
private boolean autoStartup;
#Value("${mqtt.completion-timeout.vehicleEvent:30000}")
private int completionTimeout;
#Bean
public MessageChannel vehicleMqttInputChannel() {
return new DirectChannel();
}
#Bean
public MessageProducer inboundVehicleEvent(
final MqttPahoClientFactory mqttPahoClientFactory,
final MqttAdapters adapters,
#Value("${mqtt.topic.vehicleEvent}") final String topic) {
log.info("Register vehicleEvent mqtt");
if (StringUtils.isEmpty(topic)) {
log.warn("vehicleEvent disabled!");
return null;
}
final MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter(
getClientIdWithHost("inboundVehicleEvent"), mqttPahoClientFactory, topic);
adapter.setCompletionTimeout(completionTimeout);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setOutputChannel(vehicleMqttInputChannel());
adapter.setAutoStartup(autoStartup);
adapter.setQos(1);
adapters.add(adapter);
return adapter;
}
#Bean
#ServiceActivator(inputChannel = "vehicleMqttInputChannel")
public MessageHandler vehicleEventHandler() {
return new VehicleEventMessageHandler();
}
}

How do I connect to a UNIX domain socket running an HTTP server using Netty?

I am trying to connect to a Docker UNIX domain socket using Netty. Here's my attempt so far.
#PostConstruct
public void init() throws Exception {
io.netty.bootstrap.Bootstrap bootstrap = new io.netty.bootstrap.Bootstrap();
bootstrap
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.remoteAddress(new DomainSocketAddress("/var/run/docker.sock"))
.handler(new ChannelInitializer<SocketChannel>() {
#Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel
.pipeline()
.addLast(new SimpleChannelInboundHandler<HttpObject>() {
#Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject httpObject) throws Exception {
System.out.println(httpObject);
}
});
}
});
final Channel channel = bootstrap.connect().sync().channel();
final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/services", Unpooled.EMPTY_BUFFER);
request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
channel.writeAndFlush(request);
channel.closeFuture().sync();
System.out.println("DONE");
}
At the moment I am getting
Caused by: java.nio.channels.UnsupportedAddressTypeException: null
Is there an example on how to do HTTP connections to UDS using Netty? So far I only found raw UDS and TCP HTTP but not combined.
Here's a working implementation.
io.netty.bootstrap.Bootstrap bootstrap = new io.netty.bootstrap.Bootstrap();
final EpollEventLoopGroup epollEventLoopGroup = new EpollEventLoopGroup();
try {
bootstrap
.group(epollEventLoopGroup)
.channel(EpollDomainSocketChannel.class)
.handler(new ChannelInitializer<UnixChannel>() {
#Override
public void initChannel(UnixChannel ch) throws Exception {
ch
.pipeline()
.addLast(new HttpClientCodec())
.addLast(new HttpContentDecompressor())
.addLast(new SimpleChannelInboundHandler<HttpObject>() {
private StringBuilder messageBuilder = new StringBuilder();
#Override
public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
if (msg instanceof HttpContent) {
HttpContent content = (HttpContent) msg;
messageBuilder.append(content.content().toString(StandardCharsets.UTF_8));
if (msg instanceof LastHttpContent) {
System.out.println(messageBuilder);
}
} else {
System.out.println(msg.getClass());
}
}
});
}
});
final Channel channel = bootstrap.connect(new DomainSocketAddress("/var/run/docker.sock")).sync().channel();
final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/services", Unpooled.EMPTY_BUFFER);
request.headers().set(HttpHeaderNames.HOST, "daemon");
channel.writeAndFlush(request);
channel.closeFuture().sync();
} finally {
epollEventLoopGroup.shutdownGracefully();
}
Few things to note:
Use the EpollEventLoopGroup and EpollDomainSocketChannel with a ChannelInitializer<UnixChannel>.
HTTP requires new HttpCodec() in the pipeline to use the Netty HTTP objects.
The data may be chunked so you need to assemble it and wait for the LastHttpContent object
https://github.com/trajano/netty-docker-daemon-socket

Custom JavaFX WebView Protocol Handler

I am trying to write my own protocol handler for a JavaFX application that uses webview to access a single website. What I have done so far
My custom URLStreamHandlerFactory
public class MyURLStreamHandlerFactory implements URLStreamHandlerFactory {
public URLStreamHandler createURLStreamHandler(String protocol) {
System.out.println("Protocol: " + protocol);
if (protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https")) {
return new MyURLStreamHandler();
} else {
return new URLStreamHandler() {
#Override
protected URLConnection openConnection(URL u) throws IOException {
return new URLConnection(u) {
#Override
public void connect() throws IOException {
}
};
}
};
}
}
}
My custom URLStreamHandler
public class MyURLStreamHandler extends java.net.URLStreamHandler{
protected HttpURLConnection openConnection(URL u){
MyURLConnection q = new MyURLConnection(u);
return q;
}
}
My custom HttpURLConnection
public class MyURLConnection extends HttpURLConnection {
static int defaultPort = 443;
InputStream in;
OutputStream out;
Socket s;
publicMyURLConnection(URL url) {
super(url);
try {
setRequestMethod("POST");
} catch (ProtocolException ex) {
ex.printStackTrace();
}
}
public void setRequestProperty(String name, String value){
super.setRequestProperty(name, value);
System.out.println("Namee: " + name);
System.out.println("Value: " + value);
}
public String getRequestProperty(String name){
System.out.println("GET REQUEST: ");
return super.getRequestProperty(name);
}
public OutputStream getOutputStream() throws IOException {
OutputStream os = super.getOutputStream();
System.out.println("Output: " + os);
return os;
}
public InputStream getInputStream() throws IOException {
InputStream is = super.getInputStream();
System.out.println("INout stream: " + is);
return is;
}
#Override
public void connect() throws IOException {
}
#Override
public void disconnect() {
throw new UnsupportedOperationException("Not supported yet.");
}
#Override
public boolean usingProxy() {
throw new UnsupportedOperationException("Not supported yet.");
}
When I run the application I get the following error althouhg it seems to set some headers
Jul 08, 2013 11:09:04 AM com.sun.webpane.webkit.network.URLLoader doRun
WARNING: Unexpected error
java.net.UnknownServiceException: protocol doesn't support input
at java.net.URLConnection.getInputStream(URLConnection.java:839)
at qmed.QMedURLConnection.getInputStream(MyURLConnection.java:67)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:468)
at com.sun.webpane.webkit.network.URLLoader.receiveResponse(URLLoader.java:383)
at com.sun.webpane.webkit.network.URLLoader.doRun(URLLoader.java:142)
at com.sun.webpane.webkit.network.URLLoader.access$000(URLLoader.java:44)
at com.sun.webpane.webkit.network.URLLoader$1.run(URLLoader.java:106)
at com.sun.webpane.webkit.network.URLLoader$1.run(URLLoader.java:103)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.webpane.webkit.network.URLLoader.run(URLLoader.java:103)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:724)
All I want to do is get the response back for a given request and reads its binary data. I want the protocol to behave exactly the same way as the default one and only check the binary data of a given respone. What am I doing wrong?
The application is doing all shorts of URLConnections. Is it correct to use a HTTPURLConnection as my custom URLConnection class when the protocol is http or https and start a default URLStreamHandler when other protocols are used like I am doing in MyURLStreamHandlerFactory? Should I just extend the default URLConnection class in MYURLConnection to handle all protocols the same?
Any help would be much appreciated as this is a project threatening problem
Thank you
It might be that all you are missing is a setDoInput(true) or override getDoInput() and return true (that's what i did).
If that does not help check out my working solution:
MyURLStreamHandlerFactory:
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
public class MyURLStreamHandlerFactory implements URLStreamHandlerFactory
{
public URLStreamHandler createURLStreamHandler(String protocol)
{
if (protocol.equals("myapp"))
{
return new MyURLHandler();
}
return null;
}
}
Register Factory:
URL.setURLStreamHandlerFactory(new MyURLStreamHandlerFactory());
MyURLHandler :
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
public class MyURLHandler extends URLStreamHandler
{
#Override
protected URLConnection openConnection(URL url) throws IOException
{
return new MyURLConnection(url);
}
}
MyURLConnection:
import java.io.*;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
/**
* Register a protocol handler for URLs like this: <code>myapp:///pics/sland.gif</code><br>
*/
public class MyURLConnection extends URLConnection
{
private byte[] data;
#Override
public void connect() throws IOException
{
if (connected)
{
return;
}
loadImage();
connected = true;
}
public String getHeaderField(String name)
{
if ("Content-Type".equalsIgnoreCase(name))
{
return getContentType();
}
else if ("Content-Length".equalsIgnoreCase(name))
{
return "" + getContentLength();
}
return null;
}
public String getContentType()
{
String fileName = getURL().getFile();
String ext = fileName.substring(fileName.lastIndexOf('.'));
return "image/" + ext; // TODO: switch based on file-type
}
public int getContentLength()
{
return data.length;
}
public long getContentLengthLong()
{
return data.length;
}
public boolean getDoInput()
{
return true;
}
public InputStream getInputStream() throws IOException
{
connect();
return new ByteArrayInputStream(data);
}
private void loadImage() throws IOException
{
if (data != null)
{
return;
}
try
{
int timeout = this.getConnectTimeout();
long start = System.currentTimeMillis();
URL url = getURL();
String imgPath = url.toExternalForm();
imgPath = imgPath.startsWith("myapp://") ? imgPath.substring("myapp://".length()) : imgPath.substring("myapp:".length()); // attention: triple '/' is reduced to a single '/'
// this is my own asynchronous image implementation
// instead of this part (including the following loop) you could do your own (synchronous) loading logic
MyImage img = MyApp.getImage(imgPath);
do
{
if (img.isFailed())
{
throw new IOException("Could not load image: " + getURL());
}
else if (!img.hasData())
{
long now = System.currentTimeMillis();
if (now - start > timeout)
{
throw new SocketTimeoutException();
}
Thread.sleep(100);
}
} while (!img.hasData());
data = img.getData();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
public OutputStream getOutputStream() throws IOException
{
// this might be unnecessary - the whole method can probably be omitted for our purposes
return new ByteArrayOutputStream();
}
public java.security.Permission getPermission() throws IOException
{
return null; // we need no permissions to access this URL
}
}
Some parts of MyURLConnection might not be necessary for it to work, but like this it works for me.
Usage in JavaFX WebView:
<img src="myapp:///pics/image.png"/>
Note about permissions:
I used an applet with AllPermissions for my test with the above code.
In a Sandbox-Applet this won't work, as the setFactory permission is missing.
This is not directly related to the question asked, but might make the question itself obsolete.
With Java SE 6 Update 10 Java Applets support to access resources on any domain and port which is correctly set up with a crossdomain.xml.
With this the reason to register your own protocol might become obsolete, as you can access all resources that you need.
Another idea is: If you are trying to create a kind of network sniffer, why not directly use a network sniffer/analyzer program designed for such a task?
By activating Logging and Tracing in the Java Control-Panel your Java-Console will print all attempts and executed network calls including those from the WebView.
You can see all HTTP & HTTPS calls and their return-code + cookie data.
You might also see other protocol connections, but probably not any data sent over them.
This applies to Applets in a Browser.
If you need this in a different context maybe there is a way to activate the same options by passing command line parameters.

Tried to read incoming SMS content but getting Error in Blackberry

Hi friends i am trying to read incoming sms but getting warning like this . Invocation of questionable method: java.lang.String.(String) found in: mypackage.MyApp$ListeningThread.run()
Here is my code is
public class MyApp extends UiApplication {
//private ListeningThread listener;
public static void main(String[] args) {
MyApp theApp = new MyApp();
theApp.enterEventDispatcher();
}
public MyApp() {
invokeAndWait(new Runnable() {
public void run() {
ListeningThread listener = new ListeningThread();
listener.start();
}
});
pushScreen(new MyScreen());
}
private static class ListeningThread extends Thread {
private boolean _stop = false;
private DatagramConnection _dc;
public synchronized void stop() {
_stop = true;
try {
_dc.close(); // Close the connection so the thread returns.
} catch (IOException e) {
System.err.println(e.toString());
}
}
public void run() {
try {
_dc = (DatagramConnection) Connector.open("sms://");
for (;;) {
if (_stop) {
return;
}
Datagram d = _dc.newDatagram(_dc.getMaximumLength());
_dc.receive(d);
String address = new String(d.getAddress());
String msg = new String(d.getData());
if(msg.startsWith("START")){
Dialog.alert("hello");
}
System.out.println("Message received: " + msg);
System.out.println("From: " + address);
System.exit(0);
}
} catch (IOException e) {
System.err.println(e.toString());
}
}
}
}
Please correct me where i am wrong.Is possible give me some code to read incoming sms content in blackberry.
A few points about your code:
That invokeAndWait call to launch a thread makes no sense. It doesn't harm, but is kind of waste. Use that method only to perform UI related operations.
You should try using "sms://:0" as param for Connector.open. According to the docs, a parameter with the form {protocol}://[{host}]:[{port}] will open the connection in client mode (which makes sense, since you are on the receiving part), whereas not including the host part will open it in server mode.
Finally, if you can't get it working, you could use instead the third method specified in this tutorial, which you probably have already read.
The error you quoted is complaining about the use of the String constructor that takes a string argument. Since strings are immutable in Java-ME, this is just a waste. You can use the argument string directly:
Invocation of questionable method: java.lang.String.(String) found in: mypackage.MyApp$ListeningThread.run()
//String address = new String(d.getAddress());
String address = d.getAddress();
// getData() returns a byte[], so this is a different constructor
// However, this leaves the character encoding unspecified, so it
// will default to cp1252, which may not be what you want
String msg = new String(d.getData());

Resources