I'm implementing some simple netty server and client to send and revieve files. Something which is similar to cloud storage.
I have a server which handles incoming requests and sends the files back to the client. I also want my apps to be able to handle with big files, that's why I divide such files into chunks and send them chunk by chunk. But there's an issue I can't resolve.
Let's say:
We have a 4 gb file on a server.
It is divided into 40 000 chunks.
Then they are sent to a client application, and I can see that all the chunks at the server are written into socket, as I use int field as a message number (chunk number) and put into log a message number which is being written.
But then when a client receives messages (chunks), in the case of large files the process doesn't finish successfully and only some (it depends on the size of a file) of the chunks are received by a client.
A client starts receiving consecutive messages - 1, 2, 3, 4 ... 27878, 27879 and then stops with no exception, although the last message from server was, for example 40000.
Almost forgot to say that I use JavaFX for the client app.
So, I tried to play with xms xmx java vm options but it didn't help.
Server
public class Server {
public void run() throws Exception {
EventLoopGroup mainGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(mainGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(
new ObjectDecoder(Constants.FRAME_SIZE, ClassResolvers.cacheDisabled(null)),
new ObjectEncoder(),
new MainHandler()
);
}
})
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = b.bind(8189).sync();
future.channel().closeFuture().sync();
} finally {
mainGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new Server().run();
}
}
Server handler
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
if (msg == null) {
return;
}
if (msg instanceof FileRequest) {
FileRequest fr = (FileRequest) msg;
switch (fr.getFileCommand()) {
case DOWNLOAD:
sendFileToClient(ctx, fr.getFilename());
break;
case LIST_FILES:
listFiles(ctx);
break;
case DELETE:
deleteFileOnServer(fr);
listFiles(ctx);
break;
case SEND:
saveFileOnServer(fr);
listFiles(ctx);
break;
case SEND_PARTIAL_DATA:
savePartialDataOnServer(fr);
break;
}
}
} finally {
ReferenceCountUtil.release(msg);
}
}
Methods for sending files in chunks
private void sendFileToClient(ChannelHandlerContext ctx, String fileName) throws IOException {
Path path = Paths.get("server_storage/" + fileName);
if (Files.exists(path)) {
if (Files.size(path) > Constants.FRAME_SIZE) {
sendServerDataFrames(ctx, path);
ctx.writeAndFlush(new FileRequest(FileCommand.LIST_FILES));
} else {
FileMessage fm = new FileMessage(path);
ctx.writeAndFlush(fm);
}
}
}
private void sendServerDataFrames(ChannelHandlerContext ctx, Path path) throws IOException {
byte[] byteBuf = new byte[Constants.FRAME_CHUNK_SIZE];
FileMessage fileMessage = new FileMessage(path, byteBuf, 1);
FileRequest fileRequest = new FileRequest(FileCommand.SEND_PARTIAL_DATA, fileMessage);
FileInputStream fis = new FileInputStream(path.toFile());
int read;
while ((read = fis.read(byteBuf)) > 0) {
if (read < Constants.FRAME_CHUNK_SIZE) {
byteBuf = Arrays.copyOf(byteBuf, read);
fileMessage.setData(byteBuf);
}
ctx.writeAndFlush(fileRequest);
fileMessage.setMessageNumber(fileMessage.getMessageNumber() + 1);
}
System.out.println("server_storage/" + path.getFileName() + ", server last frame number: " + fileMessage.getMessageNumber());
System.out.println("server_storage/" + path.getFileName() + ": closing file stream.");
fis.close();
}
client handlers
#Override
public void initialize(URL location, ResourceBundle resources) {
Network.start();
Thread t = new Thread(() -> {
try {
while (true) {
AbstractMessage am = Network.readObject();
if (am instanceof FileMessage) {
FileMessage fm = (FileMessage) am;
Files.write(Paths.get("client_storage/" + fm.getFilename()), fm.getData(), StandardOpenOption.CREATE);
refreshLocalFilesList();
}
if (am instanceof FilesListMessage) {
FilesListMessage flm = (FilesListMessage) am;
refreshServerFilesList(flm.getFilesList());
}
if (am instanceof FileRequest) {
FileRequest fr = (FileRequest) am;
switch (fr.getFileCommand()) {
case DELETE:
deleteFile(fr.getFilename());
break;
case SEND_PARTIAL_DATA:
receiveFrames(fr);
break;
case LIST_FILES:
refreshLocalFilesList();
break;
}
}
}
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
} finally {
Network.stop();
}
});
t.setDaemon(true);
t.start();
refreshLocalFilesList();
Network.sendMsg(new FileRequest(FileCommand.LIST_FILES));
}
private void receiveFrames(FileRequest fm) throws IOException {
Utils.processBytes(fm.getFileMessage(), "client_storage/");
}
public final class Utils {
public static void processBytes(FileMessage fm, String pathPart) {
Path path = Paths.get(pathPart + fm.getFilename());
byte[] data = fm.getData();
System.out.println(pathPart + path.getFileName() + ": " + fm.getMessageNumber());
try {
if (fm.getMessageNumber() == 1) {
Files.write(path, data, StandardOpenOption.CREATE_NEW);
} else {
Files.write(path, data, StandardOpenOption.WRITE, StandardOpenOption.APPEND);
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
That what I see on server.
server_storage/DVD5_OFFICE_2010_SE_SP2_VOLUME_X86_RU-KROKOZ.iso: 42151
server_storage/DVD5_OFFICE_2010_SE_SP2_VOLUME_X86_RU-KROKOZ.iso: 42152
server_storage/DVD5_OFFICE_2010_SE_SP2_VOLUME_X86_RU-KROKOZ.iso, server last frame number: 42153
server_storage/DVD5_OFFICE_2010_SE_SP2_VOLUME_X86_RU-KROKOZ.iso: closing file stream.
And this one is on a client.
client_storage/DVD5_OFFICE_2010_SE_SP2_VOLUME_X86_RU-KROKOZ.iso: 29055
client_storage/DVD5_OFFICE_2010_SE_SP2_VOLUME_X86_RU-KROKOZ.iso: 29056
client_storage/DVD5_OFFICE_2010_SE_SP2_VOLUME_X86_RU-KROKOZ.iso: 29057
And there is no issue when sending files from the client to the the server. I can see in debugger and in the windows task manager that both processes are working simultaniously but it's not like this when a file is sent from the server to the client. First all the chunks are read and then they are sent to a client and it starts to receive them but failed to get all of them.
Please help. I have no idea what it could be. Thanks in advance.
Related
I this is my java HTTP server:
public class WebServer implements Runnable {
public static final int PORT = 80;
#Override
public void run() {
HttpServer $server;
try {
$server = HttpServer.create(new InetSocketAddress(80), 0);
} catch (IOException _e) {
throw new RuntimeException(_e);
}
$server.createContext("/", _httpExchange ->
{
String $uri = _httpExchange.getRequestURI().toString();
$uri = $uri.startsWith("/") ? $uri.replaceFirst("/", "") : $uri;
if ($uri.equals("")) {
sendFile("test.html", _httpExchange);
}
else if ($uri.matches(".*\\.[^/.]+")) {
sendFile($uri, _httpExchange);
}
else {
sendFile($uri + ".html", _httpExchange);
}
});
$server.start();
System.out.println("Server started at " + getPrivateIp() + " on port " + PORT);
}
private static String getPrivateIp() {
try (final DatagramSocket datagramSocket = new DatagramSocket()) {
datagramSocket.connect(InetAddress.getByName("8.8.8.8"), 12345);
return datagramSocket.getLocalAddress().getHostAddress();
} catch (UnknownHostException | SocketException _e) {
throw new RuntimeException(_e);
}
}
public static void sendFile(String _name, HttpExchange _exchange) throws IOException {
try {
InputStream $stream = WebServer.class.getResourceAsStream(_name);
if ($stream == null) {
_exchange.sendResponseHeaders(404, 0);
_exchange.close();
return;
}
Scanner $scanner = new Scanner($stream).useDelimiter("\\A");
String $response = $scanner.next();
_exchange.getResponseBody();
_exchange.sendResponseHeaders(200, $response.getBytes().length);
_exchange.getResponseBody().write($response.getBytes());
_exchange.close();
} catch (Exception _ex) {
throw new RuntimeException(_ex);
}
}
}
When I run it, and then open my website, everything is ok, but I cannot see any images. In the network tab, it says that the image was accepted, but it's not shown. I tried using Files.copy() in sendFile() method, but it didn't work - it didn't show the website, nor the image! (Not even when I did localhost/image.jpg).
In the network tab, it also shows that the MIME type is img/jpeg, which is correct, so it's not because of that...
Using wget, I get a normal looking .jpg file, but if I open it, it's corrupted...
Does someone know how to fix this?
Thanks.
Solved it!
You just check if the request wants .png or .jpg file (or you can just check the MIME type), and if it does, then you have to use ImageIO class
public static void sendFile(String _name, HttpExchange _exchange) {
try {
InputStream $stream = WebServer.class.getResourceAsStream(_name);
if ($stream == null) {
_exchange.sendResponseHeaders(404, 0);
_exchange.close();
return;
}
if (_name.matches(".*?\\.(png|PNG|jpg|JPG|jpeg|JPEG)")) {
BufferedImage $image = ImageIO.read($stream);
if (_name.toLowerCase().endsWith("png")) {
_exchange.sendResponseHeaders(200, getImageSize($image, "png"));
ImageIO.write($image, "png", _exchange.getResponseBody());
}
else {
_exchange.sendResponseHeaders(200, getImageSize($image,"jpeg"));
ImageIO.write($image, "jpeg", _exchange.getResponseBody());
}
$stream.close();
_exchange.close();
return;
}
Scanner $scanner = new Scanner($stream).useDelimiter("$");
String $response = $scanner.next();
_exchange.getResponseBody();
_exchange.sendResponseHeaders(200, $response.length());
_exchange.getResponseBody().write($response.getBytes());
_exchange.close();
} catch (Exception _ex) {
throw new RuntimeException(_ex);
}
}
on sftp i have several files with following xyz names:
40_20200313_0cd6963f-bf5b-4eb0-b310-255a23ed778e_p.dat
123_20200313_0cd6963f-bf5b-4eb0-b310-255a23ed778e_p.dat
etc.
I want camel to download all files at once as currently it is downloading file one by one.
Following is camel route and query:
private static String regex() {
return "(22|23|24|25|26|28|29|32|35|40|41|46|52|70|85|88|123)_(?:.*)_p.dat";
}
private static String sftpComponent() {
return "sftp://transit.ergogroup.no/Eyeshare/From_Eyeshare_Test"
+ "?username=Eyeshare_test"
+ "&password=epw3ePOugG" // Stored on wildfly server
+ "&download=true" //Shall be read chunk by chunk to avoid heap space issues. Earlier download=true was used: Harpreet
+ "&useList=true"
+ "&stepwise=false"
+ "&disconnect=true"
+ "&passiveMode=true"
+ "&reconnectDelay=10000"
// + "&bridgeErrorHandler=true"
+ "&delay=300000"
//+ "&fileName=" + sftpFileName
// + "&include=kiki\\.txt"
// + "&include=40_*_p\\.dat"sss
+ "&include="+regex()
+ "&preMove=$simple{file:onlyname}.$simple{date:now:yyyy-MM-dd'T'hh-mm-ss}.processing"
+ "&move=$simple{file:onlyname.noext}.$simple{date:now:yyyy-MM-dd'T'hh-mm-ss}.success"
+ "&moveFailed=$simple{file:onlyname.noext}.$simple{date:now:yyyy-MM-dd'T'hh-mm-ss}.failed";
// + "&idempotentRepository=#infinispan"
// + "&readLockRemoveOnCommit=true";
}
from(sftpComponent()).log("CHU").to(archiveReceivedFile())
Code appears fine but output is not. Anyone kindly suggest
Here some example of aggregator:
from("file:///somePath/consume/?maxMessagesPerPoll=2&delay=5000")
.aggregate(constant(true), new ZipAggregationStrategy()).completion(exchange -> exchange.getProperty("CamelBatchComplete", Boolean.class))
.to("file:///somePath/produce/")
Here maxMessagesPerPoll defining how many files will be archived. But if number of them in folder is lower then maxMessagesPerPoll value it will wait for missing files for complete archive. Here example of ZipAggregationStrategy:
private static class ZipAggregationStrategy implements AggregationStrategy {
private ZipOutputStream zipOutputStream;
private ByteArrayOutputStream out;
#Override
public Exchange aggregate(final Exchange oldExchange, final Exchange newExchange) {
try {
if (oldExchange == null) {
out = new ByteArrayOutputStream();
zipOutputStream = new ZipOutputStream(out);
}
createEntry(newExchange);
return newExchange;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void createEntry(final Exchange exchange) throws Exception {
final ZipEntry zipEntry = new ZipEntry(exchange.getIn().getHeader(Exchange.FILE_NAME, String.class));
zipOutputStream.putNextEntry(zipEntry);
byte[] bytes = new byte[1024];
int length;
try (InputStream body = exchange.getIn().getBody(InputStream.class)) {
while ((length = body.read(bytes)) >= 0) {
zipOutputStream.write(bytes, 0, length);
}
}
}
#Override
public void onCompletion(final Exchange exchange) {
try {
zipOutputStream.close();
exchange.getIn().setBody(new ByteArrayInputStream(out.toByteArray()));
exchange.getIn().setHeader(Exchange.FILE_NAME, "someArchive.zip");
}catch (Exception e){
throw new RuntimeException(e);
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
It's in-memory example. You can improve it for example with using temporary file. And you can always create your own completion predicate based on your logic.
UPD: i think link for documentation is temporary unavailable
I have been stuggling with a configuration using Netty to stream bytes to a ClamAV service. I am running in an Apache Camel route.
Using Netty, I am unable to intercept the "INSTREAM size limit exceeded" message.
INSTREAM
It is mandatory to prefix this command with n or z.
Scan a stream of data. The stream is sent to clamd in chunks, after INSTREAM, on the same socket on which the command was sent. This avoids the overhead of establishing new TCP connections and problems with NAT. The format of the chunk is: '' where is the size of the following data in bytes expressed as a 4 byte unsigned integer in network byte order and is the actual chunk. Streaming is terminated by sending a zero-length chunk. Note: do not exceed StreamMaxLength as defined in clamd.conf, otherwise clamd will reply with INSTREAM size limit exceeded and close the connection.
Using a straight synchronous socket connection I have no issues. Can anyone point me in the right direction for how I should be using Netty to do this? Or should I just stick with a synchronous socket connection.
Implementation using synchronous sockets. Credit to https://github.com/solita/clamav-java "Antti Virtanen".
private class UseSocket implements Processor{
#Override
public void process(Exchange exchange) throws Exception{
try (BufferedInputStream message = new BufferedInputStream(exchange.getIn().getBody(InputStream.class));
Socket socket = new Socket("localhost", 3310);
BufferedOutputStream socketOutput = new BufferedOutputStream(socket.getOutputStream())){
byte[] command = "zINSTREAM\0".getBytes();
socketOutput.write(command);
socketOutput.flush();
byte[] chunk = new byte[2048];
int chunkSize;
try(BufferedInputStream socketInput = new BufferedInputStream(socket.getInputStream())){
for(chunkSize = message.read(chunk);chunkSize > -1;chunkSize = message.read(chunk)){
socketOutput.write(ByteBuffer.allocate(4).putInt(chunkSize).array());
socketOutput.write(chunk, 0, chunkSize);
socketOutput.flush();
if(processReply(socketInput, exchange)){
return;
}
}
socketOutput.write(ByteBuffer.allocate(4).putInt(0).array());
socketOutput.flush();
processReply(socketInput, exchange);
}
}
}
private boolean processReply(BufferedInputStream in, Exchange exchange) throws Exception{
if(in.available() > 0) {
logger.info("processing reply");
byte[] replyBytes = new byte[256];
int replySize = in.read(replyBytes);
if (replySize > 0) {
String reply = new String(replyBytes, 0, replySize, StandardCharsets.UTF_8);
String avStatus = "infected";
if ("stream: OK\0".equals(reply)) {
avStatus = "clean";
} else if ("INSTREAM size limit exceeded. ERROR\0".equals(reply)) {
avStatus = "overflow";
}
exchange.getIn().setHeader("av-status", avStatus);
return true;
}
}
return false;
}
}
Implementation using Netty with inbound and outbound channel handlers.
private class UseNetty implements Processor{
#Override
public void process(Exchange exchange) throws Exception{
logger.info(CLASS_NAME + ": Creating Netty client");
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try{
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.remoteAddress(new InetSocketAddress("localhost", 3310));
bootstrap.handler(new ClamAvChannelIntializer(exchange));
ChannelFuture channelFuture = bootstrap.connect().sync();
channelFuture.channel().closeFuture().sync();
}catch(Exception ex) {
logger.error(CLASS_NAME + ": ERROR", ex);
}
finally
{
eventLoopGroup.shutdownGracefully();
logger.info(CLASS_NAME + ": Netty client closed");
}
}
}
public class ClamAvChannelIntializer extends ChannelInitializer<SocketChannel> {
private Exchange exchange;
public ClamAvChannelIntializer(Exchange exchange){
this.exchange = exchange;
}
#Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ClamAvClientWriter());
socketChannel.pipeline().addLast(new ClamAvClientHandler(exchange));
}
}
public class ClamAvClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
String CLASS_NAME;
Logger logger;
private Exchange exchange;
public static final int MAX_BUFFER = 2048;
public ClamAvClientHandler(Exchange exchange){
super();
CLASS_NAME = this.getClass().getName();
logger = LoggerFactory.getLogger(CLASS_NAME);
this.exchange = exchange;
}
#Override
public void channelActive(ChannelHandlerContext channelHandlerContext) throws Exception{
logger.info(CLASS_NAME + ": Entering channelActive");
channelHandlerContext.write(exchange);
logger.info(CLASS_NAME + ": Exiting channelActive");
}
#Override
public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable cause){
cause.printStackTrace();
channelHandlerContext.close();
}
#Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) {
logger.info(CLASS_NAME + ": Entering channelRead0");
String reply = byteBuf.toString(CharsetUtil.UTF_8);
logger.info(CLASS_NAME + ": Reply = " + reply);
String avStatus = "infected";
if ("stream: OK\0".equals(reply)) {
avStatus = "clean";
} else if ("INSTREAM size limit exceeded. ERROR\0".equals(reply)) {
avStatus = "overflow";
} else{
logger.warn("Infected or unknown reply = " + reply);
}
exchange.getIn().setHeader("av-status", avStatus);
logger.info(CLASS_NAME + ": Exiting channelRead0");
channelHandlerContext.close();
}
}
public class ClamAvClientWriter extends ChannelOutboundHandlerAdapter {
String CLASS_NAME;
Logger logger;
public static final int MAX_BUFFER = 64000;//2^16
public ClamAvClientWriter(){
CLASS_NAME = this.getClass().getName();
logger = LoggerFactory.getLogger(CLASS_NAME);
}
#Override
public void write(ChannelHandlerContext channelHandlerContext, Object o, ChannelPromise channelPromise) throws Exception{
logger.info(CLASS_NAME + ": Entering write");
Exchange exchange = (Exchange)o;
try(BufferedInputStream message = new BufferedInputStream(exchange.getIn().getBody(InputStream.class))){
channelHandlerContext.writeAndFlush(Unpooled.copiedBuffer("zINSTREAM\0".getBytes()));
byte[] chunk = new byte[MAX_BUFFER];
for(int i=message.read(chunk);i>-1;i=message.read(chunk)){
byte[] chunkSize = ByteBuffer.allocate(4).putInt(i).array();
channelHandlerContext.write(Unpooled.copiedBuffer(chunkSize));
channelHandlerContext.writeAndFlush(Unpooled.copiedBuffer(chunk, 0, i));
}
channelHandlerContext.writeAndFlush(Unpooled.copiedBuffer(ByteBuffer.allocate(4).putInt(0).array()));
}
logger.info(CLASS_NAME + ": Exiting write");
}
}
I finally gave up on trying to use Netty for this. I created a new Camel Processor and packaged the socket stream in it. Code below in case anyone runs into a similar issue.
public class ClamAvInstream implements Processor {
Logger logger;
private final int MAX_BUFFER = 2048;
public ClamAvInstream() {
logger = LoggerFactory.getLogger(this.getClass().getName());
}
#Override
public void process(Exchange exchange) throws Exception {
try (BufferedInputStream message = new BufferedInputStream(exchange.getIn().getBody(InputStream.class));
Socket socket = new Socket("localhost", 3310);
BufferedOutputStream socketOutput = new BufferedOutputStream(socket.getOutputStream())) {
byte[] command = "zINSTREAM\0".getBytes();
socketOutput.write(command);
socketOutput.flush();
byte[] chunk = new byte[MAX_BUFFER];
int chunkSize;
try (BufferedInputStream socketInput = new BufferedInputStream(socket.getInputStream())) {
for (chunkSize = message.read(chunk); chunkSize > -1; chunkSize = message.read(chunk)) {
socketOutput.write(ByteBuffer.allocate(4).putInt(chunkSize).array());
socketOutput.write(chunk, 0, chunkSize);
socketOutput.flush();
receivedReply(socketInput, exchange);
}
socketOutput.write(ByteBuffer.allocate(4).putInt(0).array());
socketOutput.flush();
receivedReply(socketInput, exchange);
} catch(ClamAvException ex){ //close socketInput
logger.warn(ex.getMessage());
}
}//close message, socket, socketOutput
}
private class ClamAvException extends Exception{
private ClamAvException(String error){
super(error);
}
}
private void receivedReply(BufferedInputStream in, Exchange exchange) throws Exception{
if(in.available() > 0){
byte[] replyBytes = new byte[256];
int replySize = in.read(replyBytes);
if (replySize > 0) {
String reply = new String(replyBytes, 0, replySize, StandardCharsets.UTF_8);
logger.info("reply="+reply);
if(reply.contains("OK")){
exchange.getIn().setHeader("av-status", "clean");
}else if(reply.contains("ERROR")){
if(reply.equals("INSTREAM size limit exceeded. ERROR\0")){
exchange.getIn().setHeader("av-status", "overflow");
}else {
exchange.getIn().setHeader("av-status", "error");
}
throw new ClamAvException(reply);
}else if(reply.contains("FOUND")){
exchange.getIn().setHeader("av-status", "infected");
}else{
exchange.getIn().setHeader("av-status", "unknown");
}
}
}
}
}
I am trying to make a plugin that has a 'global' configuration file. Right now, I'm trying to use Plugin Messaging to send the entire configuration file through a string, to another server. I have followed the guide at https://www.spigotmc.org/wiki/bukkit-bungee-plugin-messaging-channel/ and have put my own little twist on what is sent. I'm trying to send the plugin message within a spigot plugin so maybe that is the problem. Here is the code is a summary of the code I use to send it (I took out readFile(), clearFile() and writeFile(), let me know if you want those):
public class Main extends JavaPlugin implements PluginMessageListener {
public void onEnable() {
this.getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
this.getServer().getMessenger().registerIncomingPluginChannel(this, "BungeeCord", this);
}
public void onDisable() {}
public void updateConfig() {
String updateConfig = "";
for (String s : readFile(this.getDataFolder() + "/config.yml")) {
if (updateConfig.equals("")) {
updateConfig = s;
} else {
updateConfig = updateConfig + " |n| " + s;
}
}
Bukkit.getLogger().info("Sending config update...");
sendUpdateconfig(updateConfig);
}
public void sendUpdateconfig(String update) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
try {
out.writeUTF("Forward");
out.writeUTF("ALL");
out.writeUTF("FooServer");
ByteArrayOutputStream msgbytes = new ByteArrayOutputStream();
DataOutputStream msgout = new DataOutputStream(msgbytes);
msgout.writeUTF(update);
msgout.writeShort(295);
out.writeShort(msgbytes.toByteArray().length);
out.write(msgbytes.toByteArray());
Player player = Iterables.getLast(Bukkit.getOnlinePlayers());
player.getServer().sendPluginMessage(this, "BungeeCord", out.toByteArray());
Bukkit.getLogger().info("Sent " + update);
Bukkit.getLogger().info("Short sent: 295");
Bukkit.getLogger().info("Sent through player " + player.getName());
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
Bukkit.getLogger().info("Recieved message...");
if (!channel.equals("BungeeCord")) {
return;
}
try {
Bukkit.getLogger().info("Recieved message...");
ByteArrayDataInput in = ByteStreams.newDataInput(message);
String subChannel = in.readUTF();
if (!subChannel.equals("FooServer")) {
Bukkit.getLogger().info("Loading message....");
short len = in.readShort();
byte[] msgbytes = new byte[len];
in.readFully(msgbytes);
DataInputStream msgin = new DataInputStream(new ByteArrayInputStream(msgbytes));
String somedata = msgin.readUTF();
short somenumber = msgin.readShort();
if (somenumber == 295) {
Bukkit.getLogger().info("Updating config...");
String[] toWrite = somedata.split(" |n| ");
String path = (this.getDataFolder() + "/config.yml");
clearFile(path);
for (String s : toWrite) {
writeFile(path, s);
}
Bukkit.getLogger().info("Config updated!");
}
} else {
Bukkit.getLogger().info("Message sent by this plugin.");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
The way I send the message is just by calling, updateConfig(); When that is called, onPluginMessageReceived is never run.
Is there something I'm doing wrong? Can plugin messages only be sent by a bungeecord plugin? Thanks in advance. If you have any questions about the code, let me know.
Don't work beacause it's write ( String server to send to, or ALL to send to every server (except the one sending the plugin message)) ! For use it you can use our own channel or redis
I am working on a project that requires real-time interaction between users. I want to have a HTML5 web client (simple enough) and also a local client (preferably Java) with both being able to connect to the server. I have done some research and have not found a conclusive answer to whether or not the local client can connect to the server without a browser.
Question: Is there any way to connect from a local Java client to a websocket server without a browse? I have seen some browser wrappers in other languages that might make this possible. If not, I am open to suggestions.
Thanks.
You might also consider using JSR 356 - Java API for WebSocket. It is part of Java EE 7, but client can be run from plain Java SE without any issues. There are multiple implementations available right now and following will work in all of them:
programmatic API:
final WebSocketContainer webSocketContainer = ContainerProvider.getWebSocketContainer();
Session session = webSocketContainer.connectToServer(new Endpoint() {
#Override
public void onOpen(Session session, EndpointConfig config) {
// session.addMessageHandler( ... );
}
}, URI.create("ws://some.uri"));
annotated API:
public static void main(String[] args) throws IOException, DeploymentException {
final WebSocketContainer webSocketContainer = ContainerProvider.getWebSocketContainer();
webSocketContainer.connectToServer(MyEndpoint.class, URI.create("ws://some.uri"));
}
#ClientEndpoint
public static class MyEndpoint {
// text
#OnMessage
void onMessage(Session session, String message) {
// ...
}
// binary
#OnMessage
void onMessage(Session session, ByteBuffer message) {
// ...
}
// #OnClose, #OnOpen, #OnError
}
please see linked page for further details (full specification).
There are various implementations out here, basically every Java container has one. I am working on Glassfish/WebLogic implementation and its called Tyrus, feel free to try it out (we provide easy to use all in one bundle, see http://search.maven.org/...).
You most certainly CAN utilize WebSockets from desktop applications in Java, outside the browser sandbox. The thinking behind this is that you can create thick clients that create TCP connections, so of course they should be able to create WebSocket connections on top of those TCP connections.
One of the newest and best APIs for doing so is written by Kaazing, taking the point of view that a WebSocket is just like a socket and can be created using simple "ws://" URIs.
The API is discussed in detail on the Kaazing Gateway 5.0 Java WebSocket Documentation site. You can download the plain Gateway from Kaazing here
Create a websocket:
import com.kaazing.net.ws.WebSocket;
import com.kaazing.net.ws.WebSocketFactory;
wsFactory = WebSocketFactory.createWebSocketFactory();
ws = wsFactory.createWebSocket(URI.create("ws://example.com:8001/path"));
ws.connect(); // This will block or throw an exception if failed.
To send messages, add a WebSocketMessageWriter object:
WebSocketMessageWriter writer = ws.getMessageWriter();
String text = "Hello WebSocket!";
writer.writeText(text); // Send text message
To receive or consume messages, add WebSocket and WebSocketMessageReader objects:
wsFactory = WebSocketFactory.createWebSocketFactory();
ws = wsFactory.createWebSocket(URI.create("ws://example.com:8001/path"));
ws.connect(); // This will block or throw an exception if failed.
WebSocketMessageReader reader = ws.getMessageReader();
WebSocketMessageType type = null; // Block till a message arrives
// Loop till the connection goes away
while ((type = reader.next()) != WebSocketMessageType.EOS) {
switch (type) { // Handle both text and binary messages
case TEXT:
CharSequence text = reader.getText();
log("RECEIVED TEXT MESSAGE: " + text.toString());
break;
case BINARY:
ByteBuffer buffer = reader.getBinary();
log("RECEIVED BINARY MESSAGE: " + getHexDump(buffer));
break;
}
}
(Full Disclosure: I used to work at Kaazing Corporation as a server engineer.)
Vert.x has a java websocket client:
VertxFactory.newVertx()
.createHttpClient()
.setHost("localhost")
.setPort(8080)
.connectWebsocket("/ws", new Handler<WebSocket>() {
#Override
public void handle(final WebSocket webSocket) {
// Listen
webSocket.dataHandler(new Handler<Buffer>() {
#Override
public void handle(Buffer buff) {
log.info("Received {}", buff.toString());
}
});
// Publish
webSocket.writeTextFrame("Heya");
}
});
Netty is a good choice for such task, it's a high performance network application framework and it supports SSL elegantly, here is netty websocket client example from netty github:
public final class WebSocketClient {
static final String URL = System.getProperty("url", "ws://127.0.0.1:8080/websocket");
public static void main(String[] args) throws Exception {
URI uri = new URI(URL);
String scheme = uri.getScheme() == null? "ws" : uri.getScheme();
final String host = uri.getHost() == null? "127.0.0.1" : uri.getHost();
final int port;
if (uri.getPort() == -1) {
if ("ws".equalsIgnoreCase(scheme)) {
port = 80;
} else if ("wss".equalsIgnoreCase(scheme)) {
port = 443;
} else {
port = -1;
}
} else {
port = uri.getPort();
}
if (!"ws".equalsIgnoreCase(scheme) && !"wss".equalsIgnoreCase(scheme)) {
System.err.println("Only WS(S) is supported.");
return;
}
final boolean ssl = "wss".equalsIgnoreCase(scheme);
final SslContext sslCtx;
if (ssl) {
sslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE).build();
} else {
sslCtx = null;
}
EventLoopGroup group = new NioEventLoopGroup();
try {
// Connect with V13 (RFC 6455 aka HyBi-17). You can change it to V08 or V00.
// If you change it to V00, ping is not supported and remember to change
// HttpResponseDecoder to WebSocketHttpResponseDecoder in the pipeline.
final WebSocketClientHandler handler =
new WebSocketClientHandler(
WebSocketClientHandshakerFactory.newHandshaker(
uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders()));
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
#Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc(), host, port));
}
p.addLast(
new HttpClientCodec(),
new HttpObjectAggregator(8192),
WebSocketClientCompressionHandler.INSTANCE,
handler);
}
});
Channel ch = b.connect(uri.getHost(), port).sync().channel();
handler.handshakeFuture().sync();
BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String msg = console.readLine();
if (msg == null) {
break;
} else if ("bye".equals(msg.toLowerCase())) {
ch.writeAndFlush(new CloseWebSocketFrame());
ch.closeFuture().sync();
break;
} else if ("ping".equals(msg.toLowerCase())) {
WebSocketFrame frame = new PingWebSocketFrame(Unpooled.wrappedBuffer(new byte[] { 8, 1, 8, 1 }));
ch.writeAndFlush(frame);
} else {
WebSocketFrame frame = new TextWebSocketFrame(msg);
ch.writeAndFlush(frame);
}
}
} finally {
group.shutdownGracefully();
}
}
}
public class WebSocketClientHandler extends SimpleChannelInboundHandler<Object> {
private final WebSocketClientHandshaker handshaker;
private ChannelPromise handshakeFuture;
public WebSocketClientHandler(WebSocketClientHandshaker handshaker) {
this.handshaker = handshaker;
}
public ChannelFuture handshakeFuture() {
return handshakeFuture;
}
#Override
public void handlerAdded(ChannelHandlerContext ctx) {
handshakeFuture = ctx.newPromise();
}
#Override
public void channelActive(ChannelHandlerContext ctx) {
handshaker.handshake(ctx.channel());
}
#Override
public void channelInactive(ChannelHandlerContext ctx) {
System.out.println("WebSocket Client disconnected!");
}
#Override
public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel ch = ctx.channel();
if (!handshaker.isHandshakeComplete()) {
handshaker.finishHandshake(ch, (FullHttpResponse) msg);
System.out.println("WebSocket Client connected!");
handshakeFuture.setSuccess();
return;
}
if (msg instanceof FullHttpResponse) {
FullHttpResponse response = (FullHttpResponse) msg;
throw new IllegalStateException(
"Unexpected FullHttpResponse (getStatus=" + response.status() +
", content=" + response.content().toString(CharsetUtil.UTF_8) + ')');
}
WebSocketFrame frame = (WebSocketFrame) msg;
if (frame instanceof TextWebSocketFrame) {
TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
System.out.println("WebSocket Client received message: " + textFrame.text());
} else if (frame instanceof PongWebSocketFrame) {
System.out.println("WebSocket Client received pong");
} else if (frame instanceof CloseWebSocketFrame) {
System.out.println("WebSocket Client received closing");
ch.close();
}
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
if (!handshakeFuture.isDone()) {
handshakeFuture.setFailure(cause);
}
ctx.close();
}
}