Constructing HTTP Chunks from ByteBuf in Netty 4 - java

I have a Netty 3 codebase with an HTTP Message decoder which extends ReplayingDecoder, and I need to migrate code which analyzes the chunks of the message, which means I need to get the chunks.
protected Object decode(ChannelHandlerContext ctx, Channel channel,
ByteBuf buffer, State state) {
//...
HttpChunk chunk = new DefaultHttpChunk(buffer.readBytes(toRead));
//...
}
From what I've gathered, I need to use HttpChunkedInput instead, but creating one is surprisingly difficult.
//requires an InputStream...
HttpChunkedInput hc = new HttpChunkedInput(new ChunkedStream(...));
//but this seems really clunky/too awkward to be right.
HttpChunkedInput hc = new HttpChunkedInput(new ChunkedStream(new ByteArrayInputStream(buffer.array()));
ByteBuf doesn't seem to have a way to dump out a stream directly. Am I missing a Util class API that would be better? I did find there's a new EmbeddedChannel class which can simply readInbound() like here, but I'm not sure I should be changing the types just for that or casting the bare Channel to an EmbeddedChannel to get out of the problem.

Netty 4.x onwards comes with an out of the box HTTP codec which unless you use an HTTP aggregation handler will give you HTTP chunks as HttpContent objects.
This example shows how to write a handler that received such chunks:
https://github.com/netty/netty/blob/a329857ec20cc1b93ceead6307c6849f93b3f101/example/src/main/java/io/netty/example/http/snoop/HttpSnoopServerHandler.java#L60

Related

Downloading large result

Is it possible to use the declarative client to download a large result by e.g. using an InputStream? I tried a client signature like
HttpResponse<InputStream> getQueryResult(String jobId, String resultId);
But it tries to download the whole body, which then leads to
io.micronaut.http.client.exceptions.ContentLengthExceededException: The received length exceeds the maximum content length
Thank you in advance.
What happens here is that your client requests a fully received (aggregated) HttpResponse, wrapping a byte array which is then converted into an InputStream. In order to get the response bytes without aggregation, you need to request one of the reactive types, such as a org.reactivestreams.Publisher (or a suitable subclass thereof) of ByteBuffers. Then you need to process those.
Example:
Flowable<ByteBuffer<?>> getQueryResult(String jobId, String resultId);
You can then run map, forEach, blockingForEach, etc. on that io.reactivex.Flowable - BUT REMEMBER TO FREE THE BUFFERS, or you'll generate a lot of garbage, and get nasty log messages. Example (in Groovy):
Flowable<ByteBuffer<?>> responseFlowable = myClient.getQueryResult("job1", "foo")
int sum = 0
responseFlowable.blockingForEach { ByteBuffer byteBuffer ->
sum += byteBuffer.toByteArray().count('!')
((ReferenceCounted)byteBuffer).release() // Let Netty do its thing!
}
(Obviously, blocking is bad for high throughput, but it's just an example)
I hope this helps.

How to send a message inside a netty decoder to the next handler/decoder in the pipeline?

My channel pipeline contains several decoders, all of them operating on TextWebSocketFrame messages. Now my problem is, that I have to choose the right decoder base on some content of the message.
Essentially, I have to parse a certain field in the message and then decide if I want to proceed handling the message or pass the message to the next encoder/handler.
Most people suggest to use a single decoder to decode all messages in such a case, but my problem is that some decoders are added dynamically and it would be a mess to put all logic in a single decoder.
Currently the code looks like this:
#Override
protected void decode(ChannelHandlerContext ctx, TextWebSocketFrame msg, List<Object> out) throws Exception {
String messageAsJson = msg.text();
JsonObject jsonObject = JSON_PARSER.fromJson(messageAsJson, JsonObject.class);
JsonObject messageHeader = jsonObject.getAsJsonObject(MESSAGE_HEADER_FIELD);
String serviceAsString = messageHeader.get(MESSAGE_SERVICE_FIELD).getAsString();
String inboundTypeAsString = messageHeader.get(MESSAGE_TYPE_FIELD).getAsString();
Service service = JSON_PARSER.fromJson(serviceAsString, Service.class);
InboundType inboundType = JSON_PARSER.fromJson(inboundTypeAsString, InboundType.class);
if (service == Service.STREAMING) {
out.add(decodeQuotesMessage(inboundType, messageAsJson));
} else {
}
}
So basically I'd need some logic in the else branch to pass the message to the next handler in the pipeline.
I am aware, that this approach is not the most efficient one but the architecture of my service has a slow path (running on a different thread pool), including this logic and a fast path. I can accept some slow code at this place.
In general, you need something like this:
if (service == Service.STREAMING) {
ctx.pipeline().addLast(new StreamingHandler());
} else {
ctx.pipeline().addLast(new OtherHandler());
}
out.add(decodeQuotesMessage(inboundType, messageAsJson));
ctx.pipeline().remove(this);
Logic behind is next:
You decoded header and you know now what flow you need to follow;
You add specific handler to the pipeline according to your header;
You add decoded message to 'out' list and thus you say "send this decoded message to next handler in pipeline, that would be handler defined in step 2 in case current handler is last in pipeline";
You remove the current handler from the pipeline to avoid handlers duplication in case your protocol will send the same header again and again. However, this is step is specific to your protocol and may be not necessary.
This is just the general approach, however, it really depends on your protocol flow.

Decoder, Encoder, ServerHandler pipeline in netty

Looking at the docs, it says this:
https://netty.io/4.0/api/io/netty/channel/ChannelPipeline.html
A user is supposed to have one or more ChannelHandlers in a pipeline
to receive I/O events (e.g. read) and to request I/O operations (e.g.
write and close). For example, a typical server will have the
following handlers in each channel's pipeline, but your mileage may
vary depending on the complexity and characteristics of the protocol
and business logic:
Protocol Decoder - translates binary data (e.g. ByteBuf) into a Java
object.
Protocol Encoder - translates a Java object into binary data.
Business Logic Handler - performs the actual business logic (e.g.
database access). and it could be represented as shown in the
following example: static final EventExecutorGroup group = new
DefaultEventExecutorGroup(16); ...
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", new MyProtocolDecoder());
pipeline.addLast("encoder", new MyProtocolEncoder());
// Tell the pipeline to run MyBusinessLogicHandler's event handler
methods // in a different thread than an I/O thread so that the I/O
thread is not blocked by // a time-consuming task. // If your
business logic is fully asynchronous or finished very quickly, you
don't // need to specify a group.
pipeline.addLast(group, "handler",
new MyBusinessLogicHandler());
In a lot of the examples on Github I see this same pattern. I was wondering if someone can explain why the businessHandler is not in between Decoder and Encoder. I would think that you would get your POJO, then do work on it in the business handler, then encode it.
The Decoder and encoder are usually at the beginning of the pipeline because of order in which handlers are called. For incoming data it's bottom-up and for outgoing top-down.
E.g.
pipeline.addLast(new MyEncoder());
pipeline.addLast(new MyDecoder());
pipeline.addLast(new MyBusiness());
In this case, for incoming data call order is: MyDecoder (transforming data to POJO) -> MyBusiness (the encoder is not called for incoming stream) and for outgoing data: MyBusiness -> MyEncoder (the decoder is not called for outgoing stream).
If you receive an incoming stream in the business handler (actually, the POJOs after decoder) work on it and write it back, it looks like MyBusiness is located between encoder and decoder because data is turning back to the encoder.
Of course the business handler is between the decoder and the encoder.Take the example of the Factorial example.
public void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
if (sslCtx != null) {
pipeline.addLast(sslCtx.newHandler(ch.alloc()));
}
// Enable stream compression (you can remove these two if unnecessary)
pipeline.addLast(ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP));
pipeline.addLast(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP));
// Add the number codec first,
pipeline.addLast(new BigIntegerDecoder());
pipeline.addLast(new NumberEncoder());
// and then business logic.
// Please note we create a handler for every new channel
// because it has stateful properties.
pipeline.addLast(new FactorialServerHandler());
}`
thought in the function of initChannelthe pipeline first add the encoder and the decoder, and finally add the handler. the execution flow is actually sorted by decoder,handler and encoder.
The handlers like decoder,handler and encoder are actually stored in AbstractChannelHandlerContextclass. There is a linked list of AbstractChannelHandlerContext in Netty. The list is arranged like decoder context-->handler context-->encoder context, and the execution is the same!
In fact, if you add 1. decoder, 2. businessHandler, 3. encoder in your server, and you write ctx.channel().writeAndFlush() or ctx.pipeline().writeAndFlush(), the encoder will be called then. It is bc in this case, it will go from the tail to look for the prev outboundChannel. However, if you write ctx.writeAndFlush(), it will look for the prev outboundChannel from the businessHandler's position.
Add an breakpoint in the first line of findContextOutbound() of AbstractChannelHandlerContext, you will get it.
private AbstractChannelHandlerContext findContextOutbound(int mask) {
AbstractChannelHandlerContext ctx = this;
EventExecutor currentExecutor = executor();
do {
ctx = ctx.prev;
} while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_OUTBOUND));
return ctx;
}

Netty: How can I reuse the same `FullHttpResponse`?

In my project I'd like to write the same FullHttpResponse to many clients for a performance boost.
Previously I have written the same ByteBuf for a custom protocol, and used retain to prevent the buf from being released after writing.
Unfortunately with FullHttpResponse (DefaultFullHttpResponse) my technique doesn't seem to work. The first time I write the response clients receive the response correctly, but the next write doesn't go through.
I did a simple System.out.println test to make sure nothing was blocking and that my code was executed entirely, and my test showed that yes, nothing is blocking and the request does seem to go through.
I am using the Netty 4.1.0.Final release from Maven Central.
The pipeline only has an HttpServerCodec(256, 512, 512, false, 64) and my SimpleChannelInboundHandler<HttpRequest>, which I send the FullHttpResponse from.
Here's a simplified version of my inbound handler:
class HTTPHandler extends SimpleChannelInboundHandler<HttpRequest> {
private static final FullHttpResponse response =
new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
Unpooled.buffer(8).writeLong(0),
false);
#Override
public void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) {
ctx.writeAndFlush(response.retain(), ctx.voidPromise());
}
}
You need to use response.duplicate().retain() or if using Netty 4.1.x you can also use response.retainedDuplicate().
This is needed to ensure you get separate reader/writer indices.

TTransportException when using TFramedTransport

I'm pretty puzzled with this issue. I have an Apache Thrift 0.9.0 client and server. The client code goes like this:
this.transport = new TSocket(this.server, this.port);
final TProtocol protocol = new TBinaryProtocol(this.transport);
this.client = new ZKProtoService.Client(protocol);
This works fine. However, if I try to wrap the transport in a TFramedTransport
this.transport = new TSocket(this.server, this.port);
final TProtocol protocol = new TBinaryProtocol(new TFramedTransport(this.transport));
this.client = new ZKProtoService.Client(protocol);
I get the following obscure (no explanation message whatsoever) exception in the client side. Server side shows no error.
org.apache.thrift.transport.TTransportException
at org.apache.thrift.transport.TIOStreamTransport.read(TIOStreamTransport.java:132)
at org.apache.thrift.transport.TTransport.readAll(TTransport.java:84)
at org.apache.thrift.transport.TFramedTransport.readFrame(TFramedTransport.java:129)
at org.apache.thrift.transport.TFramedTransport.read(TFramedTransport.java:101)
at org.apache.thrift.transport.TTransport.readAll(TTransport.java:84)
at org.apache.thrift.protocol.TBinaryProtocol.readAll(TBinaryProtocol.java:378)
at org.apache.thrift.protocol.TBinaryProtocol.readI32(TBinaryProtocol.java:297)
at org.apache.thrift.protocol.TBinaryProtocol.readMessageBegin(TBinaryProtocol.java:204)
at org.apache.thrift.TServiceClient.receiveBase(TServiceClient.java:69)
at com.blablabla.android.core.device.proto.ProtoService$Client.recv_open(ProtoService.java:108)
at com.blablabla.android.core.device.proto.ProtoService$Client.open(ProtoService.java:95)
at com.blablabla.simpleprotoclient.proto.ProtoClient.initializeCommunication(ProtoClient.java:411)
at com.blablabla.simpleprotoclient.proto.ProtoClient.doWork(ProtoClient.java:269)
at com.blablabla.simpleprotoclient.proto.ProtoClient.run(ProtoClient.java:499)
at java.lang.Thread.run(Thread.java:724)
It also fails if I use TCompactProtocol instead of TBinaryProtocol.
In the server side I have extended TProcessor with my own class since I need to reuse existing service handler (the service server-side IFace implementation) for this client:
#Override
public boolean process(final TProtocol in, final TProtocol out)
throws TException {
final TTransport t = in.getTransport();
final TSocket socket = (TSocket) t;
socket.setTimeout(ProtoServer.SOCKET_TIMEOUT);
final String clientAddress = socket.getSocket().getInetAddress()
.getHostAddress();
final int clientPort = socket.getSocket().getPort();
final String clientRemote = clientAddress + ":" + clientPort;
ProtoService.Processor<ProtoServiceHandler> processor = PROCESSORS
.get(clientRemote);
if (processor == null) {
final ProtoServiceHandler handler = new ProtoServiceHandler(
clientRemote);
processor = new ProtoService.Processor<ProtoServiceHandler>(
handler);
PROCESSORS.put(clientRemote, processor);
HANDLERS.put(clientRemote, handler);
ProtoClientConnectionChecker.addNewConnection(clientRemote,
socket);
}
return processor.process(in, out);
}
And this is how I start the server side:
TServerTransport serverTransport = new TServerSocket(DEFAULT_CONTROL_PORT);
TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(
serverTransport).processor(new ControlProcessor()));
Thread thControlServer = new Thread(new StartServer("Control", server));
thControlServer.start();
I have some questions:
Is it correct to reuse service handler instances or I shouldn't be doing this?
Why does it fail when I use TFramedTransport or TCompactProtocol? How to fix this?
Any help on this issue is welcome. Thanks in advance!
I was having the same problem and finally found the answer. It is possible to set the transport type on the server, though this is not clear from most tutorials and examples I've found on the web. Have a look at all of the methods of the TServer.Args class (or the args classes for other servers, which extend TServer.Args). There are methods inputTransportFactory and outputTransportFactory. You can use new TFramedTransport.Factory() as inputs to each of these methods to declare which transport the server should use. In scala:
val handler = new ServiceStatusHandler
val processor = new ServiceStatus.Processor(handler)
val serverTransport = new TServerSocket(9090)
val args = new TServer.Args(serverTransport)
.processor(processor)
.inputTransportFactory(new TFramedTransport.Factory)
.outputTransportFactory(new TFramedTransport.Factory)
val server = new TSimpleServer(args)
println("Starting the simple server...")
server.serve()
Note that if you are using a TAsyncClient, you have no choice about the transport that you use. You must use TNonblockingTransport, which has only one standard implementation, TNonblockingSocket, which internally wraps whatever protocol you are using in a framed transport. It doesn't actually wrap your chosen protocol in a TFramedTransport, but it does prepend the length of the frame to the content that it writes, and expects the server to prepend the length of the response as well. This wasn't documented anywhere I found, but if you look at the source code and experiment with different combinations, you will find that with TSimpleServer you must use TFramedTransport to get it to work with an async client.
By the way, it's also worth noting that the docs say that a TNonblockingServer must use TFramedTransport in the outermost later of the transport. However, the examples don't show this being set in TNonblockingServer.Args, yet you still find that you must use TFramedTransport on the client side to successfully execute an rpc on the server. This is because TNonblockingServer.Args has its input and output protocols set to TFramedTransport by default (you can see this using reflection to inspect the fields of the superclass hierarchy or in the source code for the constructor of AbstractNonblockingServerArgs -- you can override the input and output transports, but the server will likely fail for the reasons discussed in the documentation).
When the issue happens with framed, but it works without framed, then you have an incompatible protocol stack on both ends. Choose one of the following:
either modify the server code to use framed as well
or do not use framed on the client
A good rule of thumb is, to always use the exact same protocol/transport stack on both ends. In the particular case it blows up, because framed adds a four-byte header holding the size of the message that follows. If the server does not use framed, these additional four bytes sent by the client will be interpreted (wrongly) as part of the message.
Altough the sample code in that answer
TNonblockingServer in thrift crashes when TFramedTransport opens is for C++, adding framed on the server should be very similar with Java.
PS: Yes, it is perfectly ok to re-use your handler. A typical handler is a stateless thing.

Categories

Resources