Netty convert HttpRequest to ByteBuf - java

In my application I need to receive a byte array on a socket, parse it as a HttpRequest to perform some check and, if the checks passes, get back to the byte array and do some more work.
The application is based on NETTY (this is a requirement).
My first idea was to create a pipeline like this:
HttpRequestDecoder (decode from ByteBuf to HttpRequest)
MyHttpRequestHandler (do my own checks on the HttpRequest)
HttpRequestEncoder (encode the HttpRequest to a ByteBuf)
MyButeBufHandler (do my works with the ByteBuf)
However the HttpRequestEncoder extends the ChannelOutboundHandlerAdapter so it doesn't get called for the inbound data.
How can I accomplish this task?
It would be nice to avoid decoding and re-encoding the request.
Regards,
Massimiliano

Use an EmbeddedChannel in MyHttpRequestHandler.
EmbeddedChannel ch = new EmbeddedChannel(new HttpRequestEncoder());
ch.writeOutbound(msg);
ByteBuf encoded = ch.readOutbound();
You'll have to keep the EmbeddedChannel as a member variable of MyHttpRequestEncoder because HttpRequestEncoder is stateful. Also, please close the EmbeddedChannel when you finished using it (probably in your channelInactive() method.)

I just had to encode and decode some HttpObjects and struggled a bit with it.
The hint that the decoder/encoder are stateful is very valuable.
That's why I thought I'll add my findings here. Maybe it's helpful to someone else.
I declared an RequestEncoder and a ResponseDecoder as a class member, but it still didn't work correctly. Until I remembered that the specific handler I was using the en/decoders within was shared...
That's how I got it to work in the end. My sequenceNr is to distinct between the different requests. I create one encoder and one decoder per request and save them in a HashMap. With my sequenceNr, I'm able to always get the same decoder/encoder for the same request. Don't forget to close and remove the de/encoder channels from the Map after processing the LastContent object.
#ChannelHandler.Sharable
public class HttpTunnelingServerHandler extends ChannelDuplexHandler {
private final Map<Integer, EmbeddedChannel> decoders = Collections.synchronizedMap(new HashMap<Integer, EmbeddedChannel>());
private final Map<Integer, EmbeddedChannel> encoders = Collections.synchronizedMap(new HashMap<Integer, EmbeddedChannel>());
.
.
//Encoding
if (!encoders.containsKey(currentResponse.getSequenceNr())) {
encoders.put(currentResponse.getSequenceNr(), new EmbeddedChannel(new HttpResponseEncoder()));
}
EmbeddedChannel encoderChannel = encoders.get(currentResponse.getSequenceNr());
encoderChannel.writeOutbound(recievedHttpObject);
ByteBuf encoded = (ByteBuf) encoderChannel.readOutbound();
.
.
//Decoding
if (!decoders.containsKey(sequenceNr)) {
decoders.put(sequenceNr, new EmbeddedChannel(new HttpRequestDecoder()));
}
EmbeddedChannel decoderChannel = decoders.get(sequenceNr);
decoderChannel.writeInbound(bb);
HttpObject httpObject = (HttpObject) decoderChannel.readInbound();
}

How about the put the EmbeddedChannel as the handler channel's attribute, instead of HashMap. Isn't it the same what you claim to solve the stateful encoder/decoder?
#Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.channel().attr(EMBEDED_CH).set( new EmbeddedChannel(new HttpRequestDecoder()));
super.channelActive(ctx);
}
#Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
EmbeddedChannel embedCh = ctx.channel().attr(EMBEDED_CH).get();
if (embedCh != null) {
embedCh.close();
}
super.channelInactive(ctx);
}

Related

Get Request Body from a Java 11 HttpRequest [duplicate]

In a test, I'd like to look inside the body of a HttpRequest. I'd like to get the body as a string. It seems that the only way to do that, is to subscribe to the BodyPublisher but how does that work?
This is an interesting question. Where do you get your HttpRequest from? The easiest way would be to obtain the body directly from the code that creates the HttpRequest. If that's not possible then the next thing would be to clone that request and wraps its body publisher in your own implementation of BodyPublisher before sending the request through the HttpClient. It should be relatively easy (if tedious) to write a subclass of HttpRequest that wraps an other instance of HttpRequest and delegates every calls to the wrapped instance, but overrides HttpRequest::bodyPublisher to do something like:
return request.bodyPublisher().map(this::wrapBodyPublisher);
Otherwise, you might also try to subscribe to the request body publisher and obtain the body bytes from it - but be aware that not all implementations of BodyPublisher may support multiple subscribers (whether concurrent or sequential).
To illustrate my suggestion above: something like below may work, depending on the concrete implementation of the body publisher, and provided that you can guard against concurrent subscriptions to the body publisher. That is - in a controlled test environment where you know all the parties, then it might be workable. Don't use anything this in production:
public class HttpRequestBody {
// adapt Flow.Subscriber<List<ByteBuffer>> to Flow.Subscriber<ByteBuffer>
static final class StringSubscriber implements Flow.Subscriber<ByteBuffer> {
final BodySubscriber<String> wrapped;
StringSubscriber(BodySubscriber<String> wrapped) {
this.wrapped = wrapped;
}
#Override
public void onSubscribe(Flow.Subscription subscription) {
wrapped.onSubscribe(subscription);
}
#Override
public void onNext(ByteBuffer item) { wrapped.onNext(List.of(item)); }
#Override
public void onError(Throwable throwable) { wrapped.onError(throwable); }
#Override
public void onComplete() { wrapped.onComplete(); }
}
public static void main(String[] args) throws Exception {
var request = HttpRequest.newBuilder(new URI("http://example.com/blah"))
.POST(BodyPublishers.ofString("Lorem ipsum dolor sit amet"))
.build();
// you must be very sure that nobody else is concurrently
// subscribed to the body publisher when executing this code,
// otherwise one of the subscribers is likely to fail.
String reqbody = request.bodyPublisher().map(p -> {
var bodySubscriber = BodySubscribers.ofString(StandardCharsets.UTF_8);
var flowSubscriber = new StringSubscriber(bodySubscriber);
p.subscribe(flowSubscriber);
return bodySubscriber.getBody().toCompletableFuture().join();
}).get();
System.out.println(reqbody);
}
}

Undertow handlers make all stack Non-Blocking

I'm studying undertow because I've seen is a good choice if you want to implement Non-Blocking IO and you want to have a reactive http listener.
Undertow uses handlers to handle http requests in a Non-Blocking way.
If I have some logic to be implemented between request and response, how to make this logic to be Non-Blocking too, inside of an undertow handler?
I mean, if it's inserted (or called) within the handleRequest() method is already dispatched to a working thread and then already Non-Blocking or do you need to use CompletableFuture, or Rx Observable or any other reactive library, in order to guarantee that all the stack is reactive?
This is my Handler class as a title of example, I simulate to read a Json which will be parsed into a Person.class object and the transformed (business logic) and then returned back as a Json response.
I've written the two alternatives, in order to understand better how to make the whole stack reactive and Non-Blocking.
Which one do I have to use?
public class DoBusinessLogicHandler implements HttpHandler {
JsonConverter json = JsonConverter.getInstance();
#Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
if (exchange.isInIoThread()) {
exchange.dispatch(this);
return;
}
Pooled<ByteBuffer> pooledByteBuffer = exchange.getConnection().getBufferPool().allocate();
ByteBuffer byteBuffer = pooledByteBuffer.getResource();
byteBuffer.clear();
exchange.getRequestChannel().read(byteBuffer);
int pos = byteBuffer.position();
byteBuffer.rewind();
byte[] bytes = new byte[pos];
byteBuffer.get(bytes);
byteBuffer.clear();
pooledByteBuffer.free();
String requestBody = new String(bytes, Charset.forName("UTF-8") );
/* FIRST ALTERNATIVE:
you can call the business logic directly because the whole body of handleRequest() is managed reactively
*/
Person person = (Person) json.getObjectFromJson(requestBody, Person.class);
Person p = transform(person);
sendResponse(exchange, json.getJsonOf(p));
/* SECOND ALTERNATIVE
you must wrap business logic within a reactive construction (RxJava, CompletableFuture, ecc.) in order to
have all the stack reactive
*/
CompletableFuture
.supplyAsync(()-> (Person) json.getObjectFromJson(requestBody, Person.class))
.thenApply(p -> transform(p))
.thenAccept(p -> sendResponse(exchange, json.getJsonOf(p)));
}
/* it could be also a database fetch or whatever */
private Person transform(Person p){
if(p!=null){
p.setTitle(p.getTitle().toUpperCase());
p.setName(p.getName().toUpperCase());
p.setSurname(p.getSurname().toUpperCase());
}
return p;
}
private void sendResponse(HttpServerExchange exchange, String response){
exchange.getResponseHeaders()
.put(Headers.CONTENT_TYPE, "application/json");
exchange.getResponseSender()
.send(response);
}
}

How to send parameters from CometD client to CometD server

I have a SessionListener on a CometD server. I want to pass data from a client to the server when the listener's sessionAdded() method is called.
The sessionAdded() method receives a ServerSession and ServerMessage object. ServerSession has an Attribute map that always seems to have nothing in it.
I would like to get some unique client data to the server. This data should be accessed by the server when the sessionAdded() method is invoked.
The documentation talks about basic use of a SessionListener, but says nothing about attributes. All the javadocs for client and server say about it is to describe how setAttribute() sets an attribute and how getAttribute() gets it.
Is there a way to do this? Can the ServerSession's attribute map be used to transfer attributes from the client to the server, and if so, how?
Someone please advise...
The ServerSession attributes map is a map that lives on the server.
It is an opaque (from the CometD point of view) map that applications can populate with whatever they need.
If you want to send data from a client to the server, you can just put this additional data into the handshake message, and then retrieve it from the message when BayeuxServer.SessionListener.sessionAdded() is called.
The client looks like this:
BayeuxClient client = ...;
Map<String, Object> extraFields = new HashMap<>();
Map<String, Object> ext = new HashMap<>();
extraFields.put(Message.EXT_FIELD, ext);
Map<String, Object> extraData = new HashMap<>();
ext.put("com.acme", extraData);
client.handshake(extraFields);
extraData.put("token", "foobar");
This creates an extra data structure that in JSON looks like this:
{
"ext": {
"com.acme": {
"token": "foobar"
}
}
}
It is always a very good practice to put your data under a namespace such as com.acme, so that you don't mess up with CometD fields, nor with other extensions that you may use.
Put your fields inside extraData, like for example field token in the example above.
Then, on the server:
public class MySessionListener implements BayeuxServer.SessionListener {
#Override
public void sessionAdded(ServerSession session, ServerMessage message) {
Map<String, Object> ext = message.getExt();
if (ext != null) {
Map<String, Object> extra = (Map<String, Object>)ext.get("com.acme");
if (extra != null) {
String token = (String)extra.get("token");
session.setAttribute("token", token);
}
}
}
#Override
public void sessionRemoved(ServerSession session, boolean timedout) {
}
}
This listener puts into the session attributes data that has been sent by the client, in the example above the token field.
Then, elsewhere in the application, you can access the session attributes and use that data.

Decoding GET and POST methods with Netty

I need to create a server application with Netty that will receive requests both like "GETs" or "POSTs". In case of GET requests, the parameters would come as query parameters.
I have been checking that HttpRequestDecoder would be suitable for the GET requests, and HttpPostRequestDecoder for the post. But how could I handle both at the same time?
Not very familiar with Netty, so I would appretiate a little bit of help :)
The netty provisions us to handle a request as a pipeline where you define the pipeline as a sequence of handlers.
One sequence could be like this:
p.addLast ("codec", new HttpServerCodec ());
p.addLast ("handler", new YourHandler());
where p is an instance of ChannelPipeline interface. You can define the YourHandler class as follows:
public class YourHandler extends ChannelInboundHandlerAdapter
{
#Override
public void channelRead (ChannelHandlerContext channelHandlerCtxt, Object msg)
throws Exception
{
// Handle requests as switch cases. GET, POST,...
// This post helps you to understanding switch case usage on strings:
// http://stackoverflow.com/questions/338206/switch-statement-with-strings-in-java
if (msg instanceof FullHttpRequest)
{
FullHttpRequest fullHttpRequest = (FullHttpRequest) msg;
switch (fullHttpRequest.getMethod ().toString ())
{
case "GET":
case "POST":
...
}
}
}
}
You want to first check the request type and switch on the value (GET/POST/PUT/DELETE etc...)
http://docs.jboss.org/netty/3.1/api/org/jboss/netty/handler/codec/http/HttpMethod.html

Netty UDP compatible decoders

Which decoders are safe to extend in use with a Non Blocking Datagram Channel?
Essentially, I need to go from *ByteBuff to String, which I then have code that will turn that string into an object. Also, this would need to be accomplished with a decoder. From object to string and finally back to a *ByteBuff.
I have tried extending ByteToMessageDecoder, but it seems that Netty never invokes the decode method. So I am not sure if this is mainly a problem with the Datagram Channel or a problem with my principle understanding of decoders...
Just in case here is some of my code
Initializer:
public class Initializer extends ChannelInitializer<NioDatagramChannel> {
private SimpleChannelInboundHandler<Packet> sipHandler;
public Initializer(SimpleChannelInboundHandler<Packet> handler) {
sipHandler = handler;
}
#Override
protected void initChannel(NioDatagramChannel chan) throws Exception {
ChannelPipeline pipe = chan.pipeline();
pipe.addLast("decoder", new SipDecoder());
pipe.addLast("handler", sipHandler);
pipe.addLast("encoder", new SipEncoder());
}
}
Beginning of my Decoder:
public class SipDecoder extends ByteToMessageDecoder {
private Packet sip;
#Override
protected void decode(ChannelHandlerContext context, ByteBuf byteBuf, List<Object> objects) throws Exception {
System.out.println("got hit...");
String data = new String(byteBuf.array());
sip = new Packet();
// [...]
}
}
To handle DatagramPacket's you need to use MessageToMessageDecoder as ByteToMessageDecoder only works for ByteBuf.

Categories