
io.higgs.http.client.HTTPStreamingRequest Maven / Gradle / Ivy
package io.higgs.http.client;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.higgs.core.func.Function1;
import io.higgs.http.client.readers.Reader;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.DefaultChannelPromise;
import io.netty.channel.EventLoopGroup;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import java.net.URI;
import java.util.List;
import static io.netty.handler.codec.http.HttpConstants.CR;
import static io.netty.handler.codec.http.HttpConstants.LF;
/**
* @author Courtney Robinson
*/
public class HTTPStreamingRequest extends Request {
private static final byte[] CRLF = {CR, LF};
public HTTPStreamingRequest(HttpRequestBuilder builder, EventLoopGroup group, URI uri, Reader f) {
super(builder, group, uri, HttpMethod.POST, HttpVersion.HTTP_1_1, f);
}
@Override
protected void newNettyRequest(URI uri, HttpMethod method, HttpVersion version) {
request = new DefaultHttpRequest(version, method, uri.getRawPath());
headers().set(HttpHeaders.Names.REFERER, originalUri == null ? uri.toString() : originalUri.toString());
}
public FutureResponse execute(Function1 conf) {
if (!request.headers().contains(HttpHeaders.Names.CONTENT_TYPE)) {
request.headers().set(HttpHeaders.Names.CONTENT_TYPE, "application/json");
}
if (!request.headers().contains(HttpHeaders.Names.CONTENT_LENGTH)) {
request.headers().remove(HttpHeaders.Names.CONTENT_LENGTH);
}
if (!request.headers().contains(HttpHeaders.Names.CONNECTION)) {
request.headers().remove(HttpHeaders.Names.CONNECTION);
}
request.headers().set(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
request.headers().set(HttpHeaders.Names.EXPECT, HttpHeaders.Values.CONTINUE);
FutureResponse res = super.execute(conf);
channel.config().setOption(ChannelOption.ALLOW_HALF_CLOSURE, false);
return res;
}
public void onReady(final Function1 listener) {
if (connectFuture == null) {
throw new IllegalStateException("Not connected");
}
connectFuture.addListener(new GenericFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
SslHandler sslHandler = channel.pipeline().get("ssl") instanceof SslHandler ?
(SslHandler) channel.pipeline().get("ssl") : null;
if (sslHandler == null && useSSL) {
throw new IllegalStateException("SSL request but 'ssl' handler in the " +
"pipeline is not an SslHandler instance");
}
if (sslHandler != null) {
sslHandler.handshakeFuture().addListener(new GenericFutureListener>() {
@Override
public void operationComplete(Future super Channel> future) throws Exception {
if (future.isSuccess()) {
connected();
} else {
response.markFailed(future.cause());
}
}
});
} else {
connected();
}
}
}
private void connected() {
StreamSender sender = new StreamSender(channel);
List names = channel.pipeline().names();
for (String name : names) {
ChannelHandler handler = channel.pipeline().get(name);
if (!(handler instanceof SslHandler) && handler != null) {
channel.pipeline().remove(name);
}
}
//add last, after SSL handler, if present
channel.pipeline().addLast("raw-content-encoder", new MessageToByteEncoder() {
@Override
protected void encode(ChannelHandlerContext ctx, Chunk msg, ByteBuf out) throws Exception {
//since we removed all other handlers, we have to write the HTTP chunk size ourselves
out.writeBytes(Integer.toHexString(msg.data.readableBytes()).getBytes());
out.writeBytes(CRLF);
out.writeBytes(msg.data);
out.writeBytes(CRLF);
msg.future.setSuccess();
}
});
listener.apply(sender);
}
});
}
public static class Chunk {
public final DefaultChannelPromise future;
public final ByteBuf data;
public Chunk(DefaultChannelPromise future, ByteBuf data) {
this.future = future;
this.data = data;
}
}
public static class StreamSender {
protected final ObjectMapper MAPPER = new ObjectMapper();
private final Channel channel;
public StreamSender(Channel channel) {
if (channel == null) {
throw new IllegalArgumentException("Channel cannot be null");
}
this.channel = channel;
}
public ChannelFuture send(Object content) throws JsonProcessingException {
return send(channel.alloc().ioBuffer().writeBytes(MAPPER.writeValueAsBytes(content)));
}
public ChannelFuture send(final String content) {
return send(Unpooled.wrappedBuffer(content.getBytes()));
}
public synchronized ChannelFuture send(final ByteBuf content) {
DefaultChannelPromise promise = new DefaultChannelPromise(channel);
try {
channel.writeAndFlush(new Chunk(promise, content));
} catch (Exception e) {
promise.setFailure(e);
}
return promise;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy