Netty 4.x.x WebSocket handshake for streaming server - java

I have a Echo server example from Official Netty
Echo Server
How to add ability to connect and streaming to it from websocket?
here is my ServerHandler code:
public class ServerHandler extends ChannelInboundHandlerAdapter
{
#Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
super.channelRegistered(ctx);
// !!!!! Think here should be WebSocket Handshake?
}
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
{
System.out.println(msg);
ctx.write(msg);
}
#Override
public void channelReadComplete(ChannelHandlerContext ctx)
{
ctx.flush();
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
{
// Close the connection when an exception is raised.
cause.printStackTrace();
}
}
Right now Chrome connection says: WebSocket connection to 'ws://127.0.0.1:8080/' failed: Error during WebSocket handshake: Invalid status line

Netty servers do not automatically handle all protocols, so you would need to add support for WebSockets.
I find the best place to start is to examine the relevant examples in Netty's xref page. Scroll down the package list until you get to the io.netty.example packages. In that list you will find a package called io.netty.example.http.websocketx.server. There is a fairly simple and well laid out example on how to implement a websocket server, or just the handler.
Websocket servers are slightly more complicated than other servers in that they must start life as an HTTP server because the protocol specifies that websockets must be initiated by "upgrading" an HTTP connection, but as I said, the example referenced above makes this fairly clear.

So, I found solution! It is not compliant to native documentation of web-socket, but who cares it works as I expected!
public void channelRead(ChannelHandlerContext ctx, Object msg)
{
DefaultHttpRequest httpRequest = null;
if (msg instanceof DefaultHttpRequest)
{
httpRequest = (DefaultHttpRequest) msg;
// Handshake
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://127.0.0.1:8080/", null, false);
final Channel channel = ctx.channel();
final WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(httpRequest);
if (handshaker == null) {
} else {
ChannelFuture handshake = handshaker.handshake(channel, httpRequest);
}
}
}
Do not forget to add
p.addLast(new HttpRequestDecoder(4096, 8192, 8192, false));
p.addLast(new HttpResponseEncoder());
to your pipeline.

Related

How to continue stream if client is restart in GRPC Server Side Streaming?

#Override
public void response(RequestApp request, Grpc.Stub asyncStub) {
StreamObserver<Dto> streamObserver = new StreamObserver<Dto>() {
#Override
public void onNext(Response response) {
LOGGER.info("Response is received.");
}
#Override
public void onError(Throwable thrwbl) {
LOGGER.error("Get error.");
}
#Override
public void onCompleted() {
LOGGER.info("Stream is completed.");
}
};
asyncStub.method(request, streamObserver);
}
The Grpc Client code is like that above. There is server side streaming in here. If the client is restart, after server side stream is started, how to continue this stream ?
You cannot resume a stream in progress if the connection is lost for any reason. If you want the server to start sending data from a previous point in the data stream, you can have the client include a token in the request that the server can use as an indication of where to begin.

How to write a http proxy using netty

I want to write a simple program using netty to proxy http request send by browser.
I think it can be divided into 3 steps
get request send by browser
send it to the website
receive data from website and send it back to the browser.
Question:
How to translate url into host and port when I using Bootstrap.connect(host, port);
When I using HttpServerResponseHandler.connect and ChannelHandlerContext.writeAndFlush(httpMessage); to send data to website, how can I get the response data from the website and send it back to the browser?
It's my first day studying netty, so please try to answer as easy as possible. Thank you very much.
public class Server {
public static void main(String[] args) throws InterruptedException {
final int port = 8888;
// copy from https://github.com/netty/netty/wiki/User-guide-for-4.x
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
#Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new HttpRequestDecoder(), new HttpServerRequestHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync();
// Wait until the server socket is closed.
// In this example, this does not happen, but you can do that to gracefully
// shut down your server.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
public class HttpServerRequestHandler extends ChannelInboundHandlerAdapter {
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// step 1 get data from browser
if (msg instanceof LastHttpContent) {
ctx.close();
return;
}
DefaultHttpRequest httpMessage = (DefaultHttpRequest) msg;
System.out.println("浏览器请求====================");
System.out.println(msg);
System.out.println();
doWork(ctx, httpMessage);
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
private void doWork(ChannelHandlerContext ctx, final DefaultHttpRequest msg) {
// step 2 send data to website
// translate url into host and port
String host = msg.uri();
int port = 80;
if (host.startsWith("https://")) {
host = host.replaceFirst("https://", "");
port = 443;
} else if (host.startsWith("http://")) {
host = host.replaceFirst("http://", "");
port = 80;
}
if (host.contains(":443")) {
host = host.replace(":443", "");
port = 443;
}
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
//b.option(ChannelOption.AUTO_READ, true);
b.handler(new ChannelInitializer<SocketChannel>() {
#Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new HttpServerResponseHandler(msg), new HttpRequestEncoder());
}
});
// question 1
ChannelFuture f = b.connect(host, port).sync();
//ChannelFuture f = b.connect("www.baidu.com", 443).sync();
f.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
}
public class HttpServerResponseHandler extends ChannelOutboundHandlerAdapter {
private Object httpMessage;
public HttpServerResponseHandler(Object o) {
this.httpMessage = o;
}
#Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
System.out.println("网页请求结果=========================");
System.out.println(httpMessage);
System.out.println();
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
cause.printStackTrace();
ctx.close();
}
#Override
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
SocketAddress localAddress, ChannelPromise promise) throws Exception {
System.out.println("connect !!!!!!!!!!!");
// question 2
ctx.writeAndFlush(httpMessage);
}
}
Coincidentally, I've also been working on a Netty proxy server for the purposes of learning. I've a fully working code that you can find on my GitHub, but I'll answer your questions here. Netty also has an official proxy server example here but unlike my code, they don't have unit tests.
(FYI, My code is in Kotlin).
Core Idea:
When creating a proxy server, you need a server to accept client requests, as well as a client for the remote than you are proxying. You created the server, but not the client. It is best to reuse the EventLoop created by the server rather than creating a new one for the client. Each event loop runs on a dedicated thread, so creating more would produce additional threads, necessitating context switching when exchanging data between the accepted Channel and the client Channel.
How to translate url into host and port
To keep things simple, I've used a HttpObjectAggregator that aggregates an HttpMessage and its following HttpContents into a single FullHttpRequest or FullHttpResponse (depending on if it used to handle requests or responses). Setting the URL is trivial: just call FullHttpRequest.setUri.
To get the host and port, call Channel.remoteAddress() on the client channel and cast the resulting SocketAddress to an InetSocketAddress, from which you can get the host and port. Don't forget to similarly reset the Host header if present.
how can I get the response data
After establishing a client channel (that you're missing), you need to make a request on that channel. The client channel has a handler with a reference to the original server channel. Once the handler receives a response, it writes it to the server channel.

Server sending a greeting message with websocket and netty -> causing exception

I have a websocket server using Netty (4.0.17) that answers requests from a JavaScript client.
The communication works fine but I have an exception when I try to immediately send a greeting message when a client connects.
My code looks like that :
public class LiveOthelloWSHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
#Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
ChannelFuture f = ctx.channel().writeAndFlush(new TextWebSocketFrame("(gameID=0)[LiveOthelloServer="+ VERSION_NUMBER + "]\n"));
}
// ...
#Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame) throws Exception {
final String request = frame.text();
Channel thisChannel = ctx.channel();
// Do something with request
// Write back
thisChannel.writeAndFlush(new TextWebSocketFrame(response + "\n"));
}
}
The channelRead0() is ok, the client sends messages and the server answers back without any issue.
What doesn't work is the "greetings" part. I would like to send a welcoming message to the client (the string using VERSION_NUMBER in the ChannelActive() method) but I always get an exception :
java.lang.UnsupportedOperationException: unsupported message type: TextWebSocketFrame
I guess this is maybe because the channelActive() gets invoked as soon as the connection is established but before the websocket handshake is complete. How can I wait for the handshake to be finished and then send the greeting message (without the client having sent any request yet)?
For information, my initialization is:
#Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(
new HttpRequestDecoder(),
new HttpObjectAggregator(65536),
new HttpResponseEncoder(),
new WebSocketServerProtocolHandler("/websocket"),
myLiveOthelloWSHandler);
Just RTFM...
http://netty.io/4.0/api/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.html
Best way to detect handshake is to override ChannelInboundHandler.userEventTriggered...
So I just had to add:
#Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
super.userEventTriggered(ctx, evt);
if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {
ChannelFuture f = ctx.channel().writeAndFlush(new TextWebSocketFrame("(gameID=0)[LiveOthelloServer="+ VERSION_NUMBER + "]\n"));
}
}

Simple way to use Netty to build an http proxy server?

I'm new to Netty, and am looking at using it to make a simple http proxy server that receives requests from a client, forwards the requests to another server, and then copies the response back to the response for the original request. One extra requirement is that I be able to support a timeout, so that if the proxied server takes too long to respond the proxy will respond by itself and close the connection to the proxied server.
I've already implemented such an application using Jetty, but with Jetty I need to use too many threads to keep inbound requests from getting blocked (this is a lightweight app that uses very little memory or cpu, but the latency of the proxied server is high enough that bursts in traffic cause either queueing in the proxy server, or require too many threads).
According to my understanding, I can use Netty to build a pipeline in which each stage performs a small amount of computation, then releases it's thread and waits until data is ready for the next stage in the pipeline to be executed.
My question is, is there a simple example of such an application? What I have so far is a simple modification of the server code for the basic Netty tutorial, but it lacks all support for a client. I saw the netty client tutorial, but am not sure how to mix code from the two to create a simple proxy app.
public static void main(String[] args) throws Exception {
ChannelFactory factory =
new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool());
ServerBootstrap bootstrap = new ServerBootstrap(factory);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() {
return Channels.pipeline(
new HttpRequestDecoder(),
new HttpResponseEncoder(),
/*
* Is there something I can put here to make a
* request to another server asynchronously and
* copy the result to the response inside
* MySimpleChannelHandler?
*/
new MySimpleChannelHandler()
);
}
});
bootstrap.setOption("child.tcpNoDelay", true);
bootstrap.setOption("child.keepAlive", true);
bootstrap.bind(new InetSocketAddress(8080));
}
private static class MySimpleChannelHandler extends SimpleChannelHandler {
#Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
HttpRequest request = (HttpRequest) e.getMessage();
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
response.setContent(request.getContent());
Channel ch = e.getChannel();
ChannelFuture f = ch.write(response);
f.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) {
Channel ch = future.getChannel();
ch.close();
}
});
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
e.getCause().printStackTrace();
Channel ch = e.getChannel();
ch.close();
}
}
you would have to look at LittleProxy to see how they did it as it is written on top of Netty.

How to use Netty to handle Http Keep-Alive connections

I'm trying to write a HTTP client that uses HTTP keep-alive connections. When I connection from the ClientBoostrap I get the channel. Can I reuse this for sending multiple HTTP requests? Is there any examples demonstrating the HTTP Keep Alive functionality?
Also I have another question. Now my client works without keep-alive connections. I'm calling the channel.close in the messageReceived method of the ClientHandler. But it seems the connections are not getting closed and after some time the sockets run out and I get a BindException. Any pointers will be really appreciated.
Thanks
As long as the Connection header is not set to CLOSE (and possible the HttpVersion is 1.1, though uncertain) by a line of code similar to this...
request.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE);
...your channel should remain open for multiple request/response pairs.
Here is some example code that I whipped up today to test it. You can bounce any number of requests off of Google prior to the channel closing:
public class TestHttpClient {
static class HttpResponseReader extends SimpleChannelUpstreamHandler {
int remainingRequests = 2;
#Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
HttpResponse response = (HttpResponse) e.getMessage();
System.out.println("Beginning -------------------");
System.out.println(new String(response.getContent().slice(0, 50).array()));
System.out.println("End -------------------\n");
if(remainingRequests-- > 0)
sendRequest(ctx.getChannel());
else
ctx.getChannel().close();
}
}
public static void main(String[] args) {
ClientBootstrap bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory());
bootstrap.setPipeline(Channels.pipeline(
new HttpClientCodec(),
new HttpResponseReader()));
// bootstrap.setOption("child.keepAlive", true); // no apparent effect
ChannelFuture future = bootstrap.connect(new InetSocketAddress("google.com", 80));
Channel channel = future.awaitUninterruptibly().getChannel();
channel.getCloseFuture().addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
// this winds up getting called immediately after the receipt of the first message by HttpResponseReader!
System.out.println("Channel closed");
}
});
sendRequest(channel);
while(true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static void sendRequest(Channel channel) {
// Prepare the HTTP request.
HttpRequest request = new DefaultHttpRequest(
HttpVersion.HTTP_1_1, HttpMethod.GET, "http://www.google.com");
request.setHeader(HttpHeaders.Names.HOST, "google.com");
request.setHeader(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP);
channel.write(request);
}
}

Categories