I'm very new to reactive programing. I have watch mutiple tutorials on reactive programing, reactive spring and project reactor. I understand the concepts and 4 interfaces of the reactive stream specification. However searching around the internet there are not enough examples that can help me wrap my head around applying all of the theory and concepts I learnt into practices.
I'm trying to create a backend TCP server for a game with spring boot and reactor netty. My server application will potentially have multiple TCP server that listening on multiple port. I should probably use reactor kafka and have my servers just talk to each other thru kafka. However, at this stage I don't want to deal with all that yet because it is will just make my learning curve steeper.
This is what I'm doing to create and start the TCP server. It is a class that have a function that I would be able to pass in the port to spin up different TCP servers listening on different port:
public void accept(String host, int port) {
TcpServer server =
TcpServer.create()
.port(port)
.option(ChannelOption.SO_BACKLOG, 1024)
.handle(acceptorObserver::onChannelMessage)
.doOnChannelInit(acceptorObserver::onChannelInit)
.doOnConnection(acceptorObserver::onClientConnect)
.doOnBound(acceptorObserver::onServerBound)
.doOnUnbound(acceptorObserver::onServerUnbound);
server.bindUntilJavaShutdown(Duration.ofSeconds(30), null);
}
my acceptorObserver is a class that just have all the function to handle the lifecycle of the TCP server created. The only method I'm interested in is the handle method where all inbound packets would reach here after all Netty pipelines.
public Publisher<Void> onChannelMessage(NettyInbound nettyInbound, NettyOutbound nettyOutbound) {
Mono<IPacketWriter> handshake =
Mono.create(sink ->
nettyOutbound.withConnection(conn -> {
Channel channel = conn.channel();
if (!channel.hasAttr(NettyAttributes.SOCKET)) {
logger.info("Client connected from {}", channel.remoteAddress().toString());
conn.onDispose().subscribe(null, null, () -> onClientDisconnected(conn));
NettySocket newSocket = new NettySocket(conn, (int) (Math.random() * Integer.MAX_VALUE), (int) (Math.random() * Integer.MAX_VALUE), version, patch, locale);
IPacketWriter handshakePacket = new PacketWriter().writeShort(version)
.writeString(patch)
.writeInt(newSocket.getIntSeqRecv())
.writeInt(newSocket.getIntSeqSend())
.writeByte(locale);
sink.success(handshakePacket);
channel.attr(NettyAttributes.SOCKET).set(newSocket);
sockets.put(newSocket.getId(), newSocket);
}
else {
logger.info("Received message from client {}", channel.remoteAddress().toString());
sink.success();
}
}));
return nettyOutbound.sendObject(handshake)
.then(nettyInbound.receiveObject().cast(PacketReader.class).mapNotNull(in -> {
logger.info("nettyInbound {}", BitOperator.readableByteArray(in.getBuffer()));
return in;
}).then());
}
Thanks Violeta she helped me with the above function. So everytime I received an inbound message I will check if the SocketChannel of Netty has my attributes if it doesnt that means it is a newly connected client and I would send my handshake package other wise the handshake Mono will be empty.
My goal is to aggregate all of the NettyInbound received objects into a stream on a different class something like a CentralInboundPacketDispatcher class and I would have different handlers subscribe to this stream and filter out the packets that they care about and handle that. Whenever a new client connected there would be a new NettyInbound stream. My problem is I have no idea how to achieved the above goal. Through my googling It seems that I would need to have an "infinite stream" aka hot stream? There are really not alot of example around or I'm very bad at googling. I would appreciated it if someone could point me to the right direction.
It would be very cool if I could do something like HTTP REST server with Spring Boot where I can use anotation like #GetMapping to tell it if the packet have this header use this function.
Related
Intro
My code can process all bytes that are sent to a server and decide whether or not to let them go through and eventually send responses. I would like to use this to use the server as web server, web socket server and tcp server in one.
Although my code is written for Minecraft, I am not asking this on a Minecraft forum because answering this question doesn't require any prior knowledge about Minecraft or its codebase.
All you need to know about Minecraft
Minecraft is a Java game that can be played online. When it is being played online, there is a server that opens a ServerSocket and all players have their own client that opens a Socket that will communicate with the ServerSocket of the server.
Anyone can create a Minecraft server and install server-side modifications on their server (for those who know Minecraft, these are usually called plug-ins). My application is such a server-side modification. Most Minecraft servers are being hosted by Minecraft host companies. The owners of the servers have some kind of access to the part of the host computer that manages the server files.
Goal
The goal of my modification is to let the Minecraft server serve more clients than just the Minecraft clients. I would like the same server to also work as web server (for http and https requests) as well as (secure) web socket server and tcp server.
Why no multiple server sockets
The most common solution would be to just create a ServerSocket for the other server types and assign a different port to all of them. However, that is not an option in my case. Most hosts forbid you to open other ports or ask extra money for it. So I need to do it all with just the Minecraft ServerSocket.
What I achieved so far
So far, I have managed to let all bytes that are sent to the minecraft server first go through my code. My code can choose whether or not to let the bytes continue to the Minecraft server code. It can also send responses on its own without the need to inform the Minecraft server code at all.
In principle, what I have managed to do is sufficient to accomplish my goal, but I would like some help with how to continue. I will explain below what I have and have not accomplished so far.
The first byte that is sent by a Minecraft client to the server is always the same, namely 16. This is great because it allows me to easily distinguish Minecraft clients from web browsers and tcp clients.
HTTP requests and websocket connections always start with the same byte, namely 71. HTTPS and secure websockets always start with the byte 22. The TCP connections I was talking about will be sent by my own applications, so I can choose exactly what bytes they will send and I can simply program my modification to respond to that.
I managed to distinguish http requests and websocket connections by their connection property. Http requests always send 'Connection: keep-alive' while websocket connections always send 'Connection: upgrade'. (Although some browsers do the k, a and u in uppercase and others do not.)
Handling normal http requests wasn't very hard to do. Handling TCP connections won't be hard either because I will control everything. But I have issues with the remaining connection types:
Problems I need help with
The web socket protocol is quite large and I would prefer not to handle it completely with only my code. (I tried this before, but I kept having problems with the parts that were rarely used and thus not tested.) So I would like to use some library that allows me to only worry about the payload rather than the entire protocol. Unfortunately, web socket libraries generally want to create the ServerSocket, which is not possible in my case. So does anyone have advice on what to do here?
I haven't found any info on how to read https requests properly. Could someone tell me where to look for the details of this protocol or provide a nice link?
For secure web sockets, I will face the same problems as with the 'normal' web socket connections after I find out how to read the requests.
Code
All my code so far can be found at https://github.com/knokko/Multi-Purpose-Server. The most interesting part is probably the part where my code gets the opportunity to process all bytes before they arrive at the Minecraft code, that code is shown below.
Short question(s)
For those who didn't understand exactly what my question (you could see it as 2 questions that are strongly related) is:
-How I should read https requests and secure web socket handshakes?
-Does anyone know a library that can handle web socket input that doesn't require to create the ServerSocket itself?
// This channel handler will be registered for every connection client that will
// inspect
// any message before it reaches the Minecraft code.
pipeline.addFirst("multipurpose_handler_inspector", new ChannelInboundHandlerAdapter() {
private boolean deactivated;
private ChannelListener listener;
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//super.channelRead will send the content to the minecraft code
try {
if (!deactivated) {
ByteBuf message = (ByteBuf) msg;
if (listener != null) {
listener.read(ctx, message);
} else {
byte firstByte = message.getByte(0);
// All Minecraft connections start with the byte 16
if (firstByte == 16) {
deactivated = true;
super.channelRead(ctx, msg);
}
// All insecure web connections start with the byte 71
else if (firstByte == 71) {
byte[] data = new byte[message.readableBytes()];
message.getBytes(0, data);
WebHandler.Type type = WebHandler.determineConnectionType(data);
if (type == WebHandler.Type.HTTP) {
listener = new HTTPListener();
listener.readInitial(ctx, message);
} else if (type == WebHandler.Type.WEBSOCKET) {
// TODO Find a nice way to handle web socket connections
listener = new WebSocketListener();
listener.readInitial(ctx, message);
} else {
deactivated = true;
super.channelRead(ctx, msg);
}
}
// All secure web connections start with the byte 22
else if (firstByte == 22) {
// TODO implement the secure web protocols and find a way to read this stuff
// and find the difference
System.out.println(
"We are dealing with a secure websocket or https connection");
byte[] data = new byte[message.readableBytes()];
message.getBytes(0, data);
System.out.println(new String(data));
}
// My applications
else if (firstByte == 31) {
listener = new TCPListener();
listener.readInitial(ctx, message);
} else {
System.out.println("Unknown connection type");
deactivated = true;
super.channelRead(ctx, msg);
}
}
} else {
super.channelRead(ctx, msg);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
If you can ALWAYS identify the Minecraft traffic, your best bet might be to run an apache/httpd and/or tomcat server on the same box and forward all non-Minecraft traffic to it. If you do this, the HTTPS stuff might just be a matter of correctly configuring the http server for https traffic.
You may have to configure your code as an http proxy--in fact (Just thought of this) you might want to go out and look for an open source http proxy and just tweak it with your code to extract Minecraft traffic and forward it before doing the rest of the proxy stuff.
I wouldn't do the HTTPs stuff from scratch, it's not terribly difficult but I'd call it non-trivial.
Oh and if your problem is "differentiating Minecraft HTTPS traffic from other HTTPS connections on the same port" I can't help except to say that this might be a good subject for your question :)
To convince some people to switch from old school tech, I need to build a chat demo application that manages more than 10K concurrent connections using Java (like Node.Js stuff).
I have tested Netty 5.0 which is awesome but requires lot of work to be done; on the other hand Jetty 9.3 is great but is slow compared to other competitors.
After some search I found the Vert.x 3 toolkit which is based on Netty with a plethora of great tools (no need to reinvent the wheel), I have seen the examples in git and I was able to build a websocket server, etc.
public void start() throws Exception {
vertx.createHttpServer().websocketHandler(new Handler<ServerWebSocket>() {
#Override
public void handle(ServerWebSocket e) {
// business stuff in the old style not yet lambda
}
}).listen(port);
}
Being new to the Vert.x world, I could not figure out how to manage connected users using it, normally the old fashion way is to use something like:
HashMap<UUID,ServerWebSocket> connectedUsers;
When a connection is established I check if it exists; if not I add it as a new entry and do some functions to send, broadcast, retrieve through the collection and so on.
My question is does Vert.x 3 have something to deal with connections to track them and remove those who left (ping pong), broadcast, etc. or should I implement them from scratch using cookies, session, ....)
I could not find any real example using Vert.x 3.
Basically, the scope of the websocketHandler represents a connection. In your example this is your anonymous class. I created a little websocket chat example where I use the Vert.x event bus to distribute the messages to all the clients.
In the start method of the server we handle the websocket connections. You can implement the closeHandler to monitor client disconnection. There are also handlers for exceptions, ping-pong, etc. You can identify a specific connection by using the textHandlerID, but you have also access to the remote address.
public void start() throws Exception {
vertx.createHttpServer().websocketHandler(handler -> {
System.out.println("client connected: "+handler.textHandlerID());
vertx.eventBus().consumer(CHAT_CHANNEL, message -> {
handler.writeTextMessage((String)message.body());
});
handler.textMessageHandler(message -> {
vertx.eventBus().publish(CHAT_CHANNEL,message);
});
handler.closeHandler(message ->{
System.out.println("client disconnected "+handler.textHandlerID());
});
}).listen(8080);
}
The client example is also written in Java. It just prints all the received messages on the websocket connection to the console. After connection it sends a message.
public void start() throws Exception {
HttpClient client = vertx.createHttpClient();
client.websocket(8080, "localhost", "", websocket -> {
websocket.handler(data -> System.out.println(data.toString("ISO-8859-1")));
websocket.writeTextMessage(NAME+ ":hello from client");
});
}
I'm trying to write a non-blocking proxy with netty 4.1. I have a "FrontHandler" which handles incoming connections, and then a "BackHandler" which handles outgoing ones. I'm following the HexDumpProxyHandler (https://github.com/netty/netty/blob/ed4a89082bb29b9e7d869c5d25d6b9ea8fc9d25b/example/src/main/java/io/netty/example/proxy/HexDumpProxyFrontendHandler.java#L67)
In this code I have found:
#Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
if (outboundChannel.isActive()) {
outboundChannel.writeAndFlush(msg).addListener(new ChannelFutureListener() {, I've seen:
Meaning that the incoming message is only written if the outbound client connection is already ready. This is obviously not ideal in a HTTP proxy case, so I am thinking what would be the best way to handle it.
I am wondering if disabling auto-read on the front-end connection (and only trigger reads manually once the outgoing client connection is ready) is a good option. I could then enable autoRead over the child socket again, in the "channelActive" event of the backend handler. However, I am not sure about how many messages would I get in the handler for each "read()" invocation (using HttpDecoder, I assume I would get the initial HttpRequest, but I'd really like to avoid getting the subsequent HttpContent / LastHttpContent messages until I manually trigger the read() again and enable autoRead over the channel).
Another option would be to use a Promise to get the Channel from the client ChannelPool:
private void setCurrentBackend(HttpRequest request) {
pool.acquire(request, backendPromise);
backendPromise.addListener((FutureListener<Channel>) future -> {
Channel c = future.get();
if (!currentBackend.compareAndSet(null, c)) {
pool.release(c);
throw new IllegalStateException();
}
});
}
and then do the copying from input to output thru that promise. Eg:
private void handleLastContent(ChannelHandlerContext frontCtx, LastHttpContent lastContent) {
doInBackend(c -> {
c.writeAndFlush(lastContent).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
future.channel().read();
} else {
pool.release(c);
frontCtx.close();
}
});
});
}
private void doInBackend(Consumer<Channel> action) {
Channel c = currentBackend.get();
if (c == null) {
backendPromise.addListener((FutureListener<Channel>) future -> action.accept(future.get()));
} else {
action.accept(c);
}
}
but I'm not sure about how good it is to keep the promise there forever and do all the writes from "front" to "back" by adding listeners to it. I'm also not sure about how to instance the promise so that the operations are performed in the right thread... right now I'm using:
backendPromise = group.next().<Channel> newPromise(); // bad
// or
backendPromise = frontCtx.channel().eventLoop().newPromise(); // OK?
(where group is the same eventLoopGroup as used in the ServerBootstrap of the frontend).
If they're not handled thru the right thread, I assume it could be problematic to have the "else { }" optimization in the "doInBackend" method to avoid using the Promise and write to the channel directly.
The no-autoread approach doesn't work by itself, because the HttpRequestDecoder creates several messages even if only one read() was performed.
I have solved it by using chained CompletableFutures.
I have worked on a similar proxy application based on the MQTT protocol. So it was basically used to create a real-time chat application. The application that I had to design however was asynchronous in nature so I naturally did not face any such problem. Because in case the
outboundChannel.isActive() == false
then I can simply keep the messages in a queue or a persistent DB and then process them once the outboundChannel is up. However, since you are talking about an HTTP application, so this means that the application is synchronous in nature meaning that the client cannot keep on sending packets until the outboundChannel is up and running. So the option you suggest is that the packet will only be read once the channel is active and you can manually handle the message reads by disabling the auto read in ChannelConfig.
However, what I would like to suggest is that you should check if the outboundChannel is active or not. In case the channel is active, send he packet forward for processing. In case the channel is not active, you should reject the packet by sending back a response similar to Error404
Along with this you should configure your client to keep on retrying sending the packets after certain intervals and accordingly handle what needs to be done in case the channel takes too long a time to become active and become readable. Manually handling channelRead is generally not preferred and is an anti pattern. You should let Netty handle that for you in the most efficient way.
We need to design a server that will serve a webpage to several clients but also query a remote database for these clients. One of the requirement for this project is that the whole system must be compliant with the REST architecture style. We need use Java as programming language but many questions arised while we were designing it.
We want to have a main thread that will get connections, as shown in this example:
// System.out.println("Starting a new web server using port " + port)
try {
ServerSocket reciever = new ServerSocket(port);
while (true) {
try {
Socket s = reciever.accept();
Client c = new Client(s);
} catch (IOException e) {
System.err.println("New item creation failed.");
IOUtil.close(reciever);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
System.err.println("ServerSocket problem.");
}
Then each connection will be created as a new thread (the Client object in the code) that will take care of reading ONE request. If the request is a GET, then the thread will serve the resource to the client. If it is a POST, then it will add the request to a buffer and let another thread handle the query to the database and also the answer back to the client. After handling this only request, the thread closes the socket and terminates.
Is the use of sockets violating the REST principle? In order to respect the REST architecture, do we need to destroy every Client object (thread & socket) after each HTTP message? Is there another way of client-server communication that does not use sockets?
Ok, I think you are confusing a whole bag of junk together.
Firstly, different between low level IP sockets that allow data to go from A to B and 'websockets' that use HTTP to bootstrap a connection from a client to a server that can be kept open for TWO WAY communication.
Based on your requirements, you just want a 'standard' JEE container. Using something like JAX-RS you can apply some basic annotations to functions such as #PATH('/MyResource/Cars/') and have that function be called for that path.
Using a container will free you from all that boring boilerplate rubbish. No need to manual setup threads to listen, and spawn other threads to handle requests.
Using IP sockets is (indirectly) a mandate of REST; REST has to (according to Fielding, but strictly speaking it is protocol agnostic) be over HTTP, thus over TCP/IP sockets (though obviously you could do HTTP over any other transport protocol). Websockets however are using HTTP to form a persistent stateful connection between client and server, which is fundamentally opposed to REST. Basic HTTP would (and you would do this via the container doing it for you) fully open and close the connection for each isolated request, in practice however HTTP (and thus REST) will allow for the low level connection (the TCP connection that is slow to start) to be maintained for a series of request. This functionality is intended for the scope of loading a HTML page, and all resources in one TCP connection, but over many HTTP requests.
Sockets move bytes over TCP/IP. That's a lower level protocol, you don't want to worry about that. You care about the higher up protocol (which in this case is HTTP).
Sockets are closed in HTTP after every request, so what you're thinking sounds reasonable. Although I'm not sure why you would create a separate thread for a POST request. I'm assuming that your Client implementation already runs in its own thread (if it doesn't, then your server isn't very efficient).
I'm on the dev team for a socket server which uses Netty. When a client sends a request, and the server sends a single response, the round trip time is quite fast. (GOOD) We recently noticed that if the request from the client triggers two messages from the server, even though the server writes both messages to the client at about the same time, there is a delay of more than 200ms between the first and second message arriving on the remote client. When using a local client the two messages arrive at the same time. If the remote client sends another request before the second message from the server arrives, that second message is sent immediately, but then the two messages from the new request are both sent with the delay of over 200ms.
Since it was noticed while using Netty 3.3.1, I tried upgrading to Netty 3.6.5 but I still see the same behavior. We are using NIO, not OIO, because we need to be able to support large numbers of concurrent clients.
Is there a setting that we need to configure that will reduce that 200+ ms delay?
editing to add a code snippet. I hope these are the most relevant parts.
#Override
public boolean openListener(final Protocol protocol,
InetSocketAddress inetSocketAddress) throws Exception {
ChannelFactory factory = new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool(),
threadingConfiguration.getProcessorThreadCount());
ServerBootstrap bootstrap = new ServerBootstrap(factory);
final ChannelGroup channelGroup = new DefaultChannelGroup();
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
.... lots of pipeline setup snipped ......
});
Channel channel = bootstrap.bind(inetSocketAddress);
channelGroup.add(channel);
channelGroups.add(channelGroup);
bootstraps.add(bootstrap);
return true;
}
The writer factory uses ChannelBuffers.dynamicBuffer(defaultMessageSize) for the buffer, and when we write a message it's Channels.write(channel, msg).
What else would be useful? The developer who migrated the code to Netty is not currently available, and I'm trying to fill in.
200ms strikes me as the magic number of the Nagle's algorithm. Try setting the TcpNoDelay to true on both sides.
This is how you set the option for the server side.
serverBootstrap.setOption("child.tcpNoDelay", true);
This is for the client side.
clientBootStrap.setOption("tcpNoDelay", true);
Further reading: http://www.stuartcheshire.org/papers/NagleDelayedAck/