Java example for sending non-blocking http request from AKKA - java

It is in AKKA documentation written that
... Actors should not block (i.e. passively wait while occupying a Thread) on some external entity, which might be a lock, a network socket, etc. The blocking operations should be done in some special-cased thread which sends messages to the actors which shall act on them.
source http://doc.akka.io/docs/akka/2.0/general/actor-systems.html#Actor_Best_Practices
I have found the following information at the moment :
I read Sending outbound HTTP request from Akka / Scala and checked the example at https://github.com/dsciamma/fbgl1
I found following article http://nurkiewicz.blogspot.de/2012/11/non-blocking-io-discovering-akka.html explaining how to use https://github.com/AsyncHttpClient/async-http-client non blocking http client with akka. But is written in Scala.
How can i write an actor that make non-blocking http requests?
It must downlad a remote url page as file and than send the generated file object to the master actor. master actor then sends this request to parser actor to parse the file...

In the last response, Koray is using a wrong reference for the sender, the correct way to do it is:
public class ReduceActor extends UntypedActor {
#Override
public void onReceive(Object message) throws Exception {
if (message instanceof URI) {
URI url = (URI) message;
AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
final ActorRef sender = getSender();
asyncHttpClient.prepareGet(url.toURL().toString()).execute(new AsyncCompletionHandler<Response>() {
#Override
public Response onCompleted(Response response) throws Exception {
File f = new File("e:/tmp/crawler/" + UUID.randomUUID().toString() + ".html");
// Do something with the Response
// ...
// System.out.println(response1.getStatusLine());
FileOutputStream fao = new FileOutputStream(f);
IOUtils.copy(response.getResponseBodyAsStream(), fao);
System.out.println("File downloaded " + f);
sender.tell(new WordCount(f));
return response;
}
#Override
public void onThrowable(Throwable t) {
// Something wrong happened.
}
});
} else
unhandled(message);
}
Checkout this other thread of akka: https://stackoverflow.com/a/11899690/575746

I have implemented this in this way.
public class ReduceActor extends UntypedActor {
#Override
public void onReceive(Object message) throws Exception {
if (message instanceof URI) {
URI url = (URI) message;
AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
asyncHttpClient.prepareGet(url.toURL().toString()).execute(new AsyncCompletionHandler<Response>() {
#Override
public Response onCompleted(Response response) throws Exception {
File f = new File("e:/tmp/crawler/" + UUID.randomUUID().toString() + ".html");
// Do something with the Response
// ...
// System.out.println(response1.getStatusLine());
FileOutputStream fao = new FileOutputStream(f);
IOUtils.copy(response.getResponseBodyAsStream(), fao);
System.out.println("File downloaded " + f);
getSender().tell(new WordCount(f));
return response;
}
#Override
public void onThrowable(Throwable t) {
// Something wrong happened.
}
});
} else
unhandled(message);
}

Related

Async response streaming with Apache Async Http client

I am using apache async http client to stream objects from azure storage.
I only need to return the HttpResponse object which has the stream associated. My clients will actually have to read from that stream to store the file locally.
So Apache Async clients use a BasicAsyncResponseConsumer which actually buffers the entire file in local memory before calling the completed callback.
I am trying to create my own implementation of AbstractAsyncResponseConsumer so that I can stream the response body instead of actually storing it first but have been unsuccessful to do so till now.
Here is the bare bones cosumer class for reference ->
public class MyConsumer extends` AbstractAsyncResponseConsumer<HttpResponse> {
#Override
protected void onResponseReceived(HttpResponse response) throws HttpException, IOException {
}
#Override
protected void onContentReceived(ContentDecoder decoder, IOControl ioctrl) throws IOException {
}
#Override
protected void onEntityEnclosed(HttpEntity entity, ContentType contentType) throws IOException {
}
#Override
protected HttpResponse buildResult(HttpContext context) throws Exception {
return null;
}
#Override
protected void releaseResources() {
}
}
And here is the code to send the request and return the response ->
public void getFile(HttpRequestBase request) {
MyConsumer myConsumer = new MyConsumer();
HttpAsyncRequestProducer producer =
HttpAsyncMethods.create(request);
CompletableFuture<HttpResponse> future = new CompletableFuture<>();
return Future<HttpResponse> responseFuture =
httpclient.execute(producer,myConsumer,
new FutureCallback<HttpResponse>() {
#Override
public void completed(HttpResponse result) {
//This is called only when all the response body has been read
//future.complete(Result)
}
#Override
public void failed(Exception ex) {
}
#Override
public void cancelled() {
}
});
return future;
}
I will be returning a CompletableFuture of the HttpResponse object to my clients.
They shouldnt be waiting for my http client to read all the response body first in local buffer.
They ideally should start copying directly from the stream provided in the response object.
What should I add inmy implementation of the consumer to get the desired result ?
I don't know if you still have this problem, but if what you want is an InputStream that actually streams data, then you'll want to use the blocking version of Apache HttpClient.
Java's built-in InputStream and OutputStream are inherently blocking, so returning a CompletableFuture of InputStream essentially defeats the purpose. BasicAsyncResponseConsumer buffering the entire response in memory is actually the right thing to do, because that's the only way of making it truly non-blocking.
Another option you can take a look at is HttpAsyncMethods.createZeroCopyConsumer. What it does is that it stores the content to a file in a completely non-blocking way.
Here's an example:
try (CloseableHttpAsyncClient client = HttpAsyncClients.createDefault()) {
client.start();
final CompletableFuture<HttpResponse> cf = new CompletableFuture<>();
client.execute(
HttpAsyncMethods.createGet("https://example.com"),
HttpAsyncMethods.createZeroCopyConsumer(new File("foo.html")),
new FutureCallback<HttpResponse>() {
#Override
public void completed(HttpResponse result) {
cf.complete(result);
}
#Override
public void failed(Exception ex) {
cf.completeExceptionally(ex);
}
#Override
public void cancelled() {
cf.cancel(true);
}
});
// When cf completes, the file will be ready.
// The InputStream inside the HttpResponse will be the FileInputStream of the created file.
}

Netty and MongoDB async callback not working together

I have a simple Netty test server that I would like to query a mongo database and return the result. I've setup the simple hello world tutorial from the Netty repository here: https://github.com/netty/netty/tree/4.0/example/src/main/java/io/netty/example/http/helloworld
I've modified the simple tutorial to add an asynchronous MongoDB call, which returns the same "hello world" string as the example, but after my modification the HTTP call never completes.
Original Method:
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof HttpRequest) {
HttpRequest req = (HttpRequest) msg;
boolean keepAlive = HttpHeaders.isKeepAlive(req);
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(CONTENT));
response.headers().set(CONTENT_TYPE, "text/plain");
response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
if (!keepAlive) {
ctx.write(response).addListener(ChannelFutureListener.CLOSE);
} else {
response.headers().set(CONNECTION, Values.KEEP_ALIVE);
ctx.write(response);
}
}
}
After My Change:
private final MongoCollection<Document> collection = ...
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
if (msg instanceof HttpRequest) {
final HttpRequest req = (HttpRequest) msg;
collection.find(Filters.eq("_id", new ObjectId("..."))).first(new SingleResultCallback<Document>() {
public void onResult(Document document, Throwable throwable) {
boolean keepAlive = HttpUtil.isKeepAlive(req);
FullHttpResponse response = ...
(SAME CODE AS ABOVE)
});
}
}
I can see it's hitting my code, but the response never gets sent to the client. How do I make an async call in the ServerHandler method?
You will need to also call flush() or change write(...) to writeAndFlush(...) to ensure the content is really flushed to the socket.

Java netty get POST Request Content

i am using netty 4.1 embeded in Java and trying to retrive Data from a clients POST request in the pipeline. I tried several options i found online but nothing works...
Maybe someone has a useful thought on this.
Regards and thanks for everyone who helps.
Pipeline:
p.addLast ("codec", new HttpServerCodec ());
p.addLast("decoder", new HttpRequestDecoder());
p.addLast("encoder", new HttpRequestEncoder());
p.addLast("handler",new InboundHandlerA());
Handler:
private static class InboundHandlerA extends ChannelInboundHandlerAdapter{
#Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("Connected!");
ctx.fireChannelActive();
}
public void channelRead (ChannelHandlerContext channelHandlerCtxt, Object msg) throws Exception {
System.out.println(msg);
}
}
Recieving HTTP requests using netty is simple, you can do this with the following pipeline:
// Provides support for http objects:
p.addLast("codec", new HttpServerCodec());
// Deals with fragmentation in http traffic:
p.addLast("aggregator", new HttpObjectAggregator(Short.MAX_VALUE));
// Deals with optional compression of responses:
// p.addLast("aggregator", new HttpContentCompressor());
p.addLast("handler",new InboundHandlerA());
This can be used with a custom SimpleChannelInboundHandler<FullHttpRequest>:
public class InboundHandlerA extends SimpleChannelInboundHandler<FullHttpRequest> {
#Override
public void channelActive(ChannelHandlerContext ctx) {
super.channelActive(ctx);
System.out.println("Connected!");
}
// Please keep in mind that this method
will be renamed to messageReceived(ChannelHandlerContext, I) in 5.0.
#Override
public void channelRead0 (ChannelHandlerContext ctx,
FullHttpRequest msg) throws Exception {
// Check for invalid http data:
if(msg.getDecoderResult() != DecoderResult.SUCCESS ) {
ctx.close();
return;
}
System.out.println("Recieved request!");
System.out.println("HTTP Method: " + msg.getMethod());
System.out.println("HTTP Version: " + msg.getProtocolVersion());
System.out.println("URI: " + msg.getUri());
System.out.println("Headers: " + msg.headers());
System.out.println("Trailing headers: " + msg.trailingHeaders());
ByteBuf data = msg.content();
System.out.println("POST/PUT length: " + data.readableBytes());
System.out.println("POST/PUT as string: ");
System.out.println("-- DATA --");
System.out.println(data.toString(StandardCharsets.UTF_8));
System.out.println("-- DATA END --");
// Send response back so the browser won't timeout
ByteBuf responseBytes = ctx.alloc().buffer();
responseBytes.writeBytes("Hello World".getBytes());
FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK, responseBytes);
response.headers().set(HttpHeaders.Names.CONTENT_TYPE,
"text/plain");
response.headers().set(HttpHeaders.Names.CONTENT_LENGTH,
response.content().readableBytes());
response.headers().set(HttpHeaders.Names.CONNECTION,
HttpHeaders.Values.KEEP_ALIVE);
ctx.write(response);
}
}
The code above is printing out all the details on a incoming message, including the post data. If you require only the post data, you can add a simple if-statement to filter on a POST response type

HTTP Request using Netty

I have just started netty and I am really disappointed with the documentation present on
their website.
I am trying to connect to an URL using Netty.. I took the time client example from their website and changed it as per my requirement..
Code :
public class NettyClient {
public static void main(String[] args) throws Exception {
String host = "myUrl.com/v1/parma?param1=value";
int port = 443;
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.handler(new ChannelInitializer<SocketChannel>() {
#Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ClientHandler());
ch.pipeline().addLast("encoder", new HttpRequestEncoder());
}
});
// Start the client.
ChannelFuture f = b.connect(host, port).sync();
// Wait until the connection is closed.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
}
}
}
But the problem is that that it expects only the url without the query parameters.. How can I pass query parameters with the URL?
and please provide me some link of a good documentation for Netty 4..
EDIT
Client code after referring the example mentioned in the answer :
URI uri = new URI("myUrl.com/v1/parma?param1=value");
String scheme = uri.getScheme() == null? "http" : uri.getScheme();
String host = "myUrl.com";
int port = 443;
boolean ssl = "https".equalsIgnoreCase(scheme);
// Configure the client.
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new NettyClientInitializer(ssl));
// Make the connection attempt.
Channel ch = b.connect(host, port).sync().channel();
// Prepare the HTTP request.
HttpRequest request = new DefaultHttpRequest(
HttpVersion.HTTP_1_1, HttpMethod.GET, uri.getRawPath());
request.headers().set(HttpHeaders.Names.HOST, host);
request.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE);
//request.headers().set(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP);
/*// Set some example cookies.
request.headers().set(
HttpHeaders.Names.COOKIE,
ClientCookieEncoder.encode(
new DefaultCookie("my-cookie", "foo"),
new DefaultCookie("another-cookie", "bar")));
*/
// Send the HTTP request.
ch.writeAndFlush(request);
// Wait for the server to close the connection.
ch.closeFuture().sync();
} finally {
// Shut down executor threads to exit.
group.shutdownGracefully();
}
handler code :
public class ClientHandler extends SimpleChannelInboundHandler<HttpObject> {
#Override
public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
if (msg instanceof HttpResponse) {
HttpResponse response = (HttpResponse) msg;
System.out.println("STATUS: " + response.getStatus());
System.out.println("VERSION: " + response.getProtocolVersion());
System.out.println();
if (!response.headers().isEmpty()) {
for (String name: response.headers().names()) {
for (String value: response.headers().getAll(name)) {
System.out.println("HEADER: " + name + " = " + value);
}
}
System.out.println();
}
if (HttpHeaders.isTransferEncodingChunked(response)) {
System.out.println("CHUNKED CONTENT {");
} else {
System.out.println("CONTENT {");
}
}
if (msg instanceof HttpContent) {
HttpContent content = (HttpContent) msg;
System.out.print(content.content().toString(CharsetUtil.UTF_8));
System.out.flush();
if (content instanceof LastHttpContent) {
System.out.println("} END OF CONTENT");
}
}
}
#Override
public void exceptionCaught(
ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
initializer code :
public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {
private final boolean ssl;
public NettyClientInitializer(boolean ssl) {
this.ssl = ssl;
}
#Override
public void initChannel(SocketChannel ch) throws Exception {
// Create a default pipeline implementation.
ChannelPipeline p = ch.pipeline();
p.addLast("log", new LoggingHandler(LogLevel.INFO));
// Enable HTTPS if necessary.
/*
if (ssl) {
SSLEngine engine =
SecureChatSslContextFactory.getClientContext().createSSLEngine();
engine.setUseClientMode(true);
p.addLast("ssl", new SslHandler(engine));
}
*/
p.addLast("codec", new HttpClientCodec());
// Remove the following line if you don't want automatic content decompression.
// p.addLast("inflater", new HttpContentDecompressor());
// Uncomment the following line if you don't want to handle HttpChunks.
p.addLast("aggregator", new HttpObjectAggregator(1048576));
p.addLast("handler", new ClientHandler());
}
}
Your code only handles the low-level connection at the moment. Indeed at this level only the hostname and port can be used.
For the HTTP request You have to construct an HttpRequest object and send it over the channel. In this request object You define the query parameters and all such things.
There is a bunch of example code about HTTP client functionality on Netty website - have a a look!
In this example the problem lies with the constructor for the DefaultHttpRequest parameter of uri.getRawPath(). The invocation of this method does NOT return the query parameters. It works in this case as there were no query parameters in the Snoop example. By substituting uri.toASCIIString() returns the encoded uri complete with the query parameters. To prove this to yourself, rather than having a method invocation within a method invocation (a bad idea for just this reason, add the statement
String url = uri.getRawPath();
and look at the string url.
I had the exact same problem. I've done this natively in servlets for years but now was trying to do it in a Netty app.
Consequently the new code would be:
String path = uri.toASCIIString();
// Prepare the HTTP request.
HttpRequest request = new DefaultFullHttpRequest(
HttpVersion.HTTP_1_1, HttpMethod.GET, path);
When you build the request, you need to add the query to the path. Instead of
uri.getRawPath()
use
uri.getRawPath() + "?" + uri.getRawQuery()

audio stream server with netty

I'm trying create a simple audio stream server like a concept proof, but I'm having some dificulties.
I'm streaming a single file to start, I searched but didn't found enought information to create a audio stream server, so I just created a simple server based on my little knowledge about servers. I've created it with netty passing the stream to ChunkedStream object and wrote it on channel:
public class CastServerHandler extends SimpleChannelHandler {
#Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
throws Exception {
HttpRequest request = (HttpRequest) e.getMessage();
if (request.getMethod() != GET) {
sendError(ctx, METHOD_NOT_ALLOWED);
return;
}
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
System.out.println(response.toString());
Channel channel = e.getChannel();
channel.write(response);
ChannelFuture writeFuture;
StreamSource source = StreamSource.getInstance();
ChunkedStream stream = new ChunkedStream(source.getLiveStream());
writeFuture = channel.write(stream);
writeFuture.addListener(new ChannelFutureProgressListener() {
public void operationComplete(ChannelFuture future) {
System.out.println("terminou");
future.getChannel().close();
}
public void operationProgressed(ChannelFuture future, long amount,
long current, long total) {
System.out.println("Transferido: " + current + " de " + total);
}
});
if (!isKeepAlive(request)) {
writeFuture.addListener(ChannelFutureListener.CLOSE);
}
}
private void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, status);
response.setHeader(CONTENT_TYPE, "text/plain; charset=UTF-8");
response.setContent(ChannelBuffers.copiedBuffer(
"Failure: " + status.toString() + "\r\n", CharsetUtil.UTF_8));
// Close the connection as soon as the error message is sent.
ctx.getChannel().write(response)
.addListener(ChannelFutureListener.CLOSE);
}
private void writeLiveStream(Channel channel) {
StreamSource source = StreamSource.getInstance();
ChunkedStream stream = new ChunkedStream(source.getLiveStream());
channel.write(stream);
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
throws Exception {
e.getCause().printStackTrace();
e.getChannel().close();
}
}
Ufortunately, I didn't successfully streamed the audio directly to web browser, so I tryied to figure out what icecast returns as response to web browser, and it return these properties in header:
Cache-Control:no-cache
Content-Type:application/ogg
Server:Icecast 2.3.2
ice-audio-info:samplerate=44100;channels=2;quality=3%2e00
icy-description:Stream de teste
icy-genre:Rock
icy-name:Radio teste Brevleq
icy-pub:0
Is there a simple way netty use to put these content in HttpResponse header (specially Content-type:applicatio/ogg)?? I hope this is the problem...
See the API of HttpResponse.
It has setHeader method.
I'd consider going with a straight binary protocol, and creating an HTTP interface only for a proxy. There's no reason to deal with a text based protocol for something like this.

Categories

Resources