/*
* Copyright (c) 2011-2013 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.vertx.core.http.impl;
import io.netty.buffer.ByteBuf;
import io.netty.channel.AbstractChannel;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelMetadata;
import io.netty.channel.ChannelOutboundBuffer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.channel.DefaultChannelConfig;
import io.netty.channel.EventLoop;
import io.netty.channel.nio.NioEventLoop;
import io.netty.handler.stream.ChunkedFile;
import io.netty.handler.stream.ChunkedStream;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import java.io.RandomAccessFile;
import java.net.SocketAddress;
/**
* A channel used for writing a file in an HTTP2 stream.
*
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
*/
class FileStreamChannel extends AbstractChannel {
private static final SocketAddress LOCAL_ADDRESS = new StreamSocketAddress();
private static final SocketAddress REMOTE_ADDRESS = new StreamSocketAddress();
private static final ChannelMetadata METADATA = new ChannelMetadata(true);
private final ChannelConfig config = new DefaultChannelConfig(this);
private boolean active;
private boolean closed;
private long bytesWritten;
private final VertxHttp2Stream stream;
FileStreamChannel(
Handler<AsyncResult<Long>> resultHandler,
VertxHttp2Stream stream,
long offset,
long length) {
super(null, Id.INSTANCE);
pipeline().addLast(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ChunkedWriteHandler());
pipeline.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof RandomAccessFile) {
ChannelFuture fut = ctx.writeAndFlush(new ChunkedFile((RandomAccessFile) evt, offset, length, 8192 /* default chunk size */ ));
fut.addListener(f -> {
if (resultHandler != null) {
if (f.isSuccess()) {
resultHandler.handle(Future.succeededFuture(bytesWritten));
} else {
resultHandler.handle(Future.failedFuture(f.cause()));
}
}
fut.addListener(ChannelFutureListener.CLOSE);
});
}
}
});
}
});
this.stream = stream;
}
final Handler<Void> drainHandler = v -> {
flush();
};
@Override
protected void doRegister() throws Exception {
active = true;
}
@Override
protected AbstractUnsafe newUnsafe() {
return new DefaultUnsafe();
}
@Override
protected boolean isCompatible(EventLoop loop) {
return loop instanceof NioEventLoop;
}
@Override
protected SocketAddress localAddress0() {
return LOCAL_ADDRESS;
}
@Override
protected SocketAddress remoteAddress0() {
return REMOTE_ADDRESS;
}
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
}
@Override
protected void doDisconnect() throws Exception {
doClose();
}
@Override
protected void doClose() throws Exception {
active = false;
closed = true;
}
@Override
protected void doBeginRead() throws Exception {
}
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
ByteBuf chunk;
while (!stream.isNotWritable() && (chunk = (ByteBuf) in.current()) != null) {
bytesWritten += chunk.readableBytes();
stream.writeData(chunk.retain(), false);
stream.handlerContext.flush();
in.remove();
}
}
@Override
public ChannelConfig config() {
return config;
}
@Override
public boolean isOpen() {
return !closed;
}
@Override
public boolean isActive() {
return active;
}
@Override
public ChannelMetadata metadata() {
return METADATA;
}
private static class StreamSocketAddress extends SocketAddress {
@Override
public String toString() {
return "stream";
}
}
private class DefaultUnsafe extends AbstractUnsafe {
@Override
public void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
safeSetSuccess(promise);
}
}
static class Id implements ChannelId {
static final ChannelId INSTANCE = new Id();
private Id() { }
@Override
public String asShortText() {
return toString();
}
@Override
public String asLongText() {
return toString();
}
@Override
public int compareTo(ChannelId o) {
if (o instanceof Id) {
return 0;
}
return asLongText().compareTo(o.asLongText());
}
@Override
public int hashCode() {
return 0;
}
@Override
public boolean equals(Object obj) {
return obj instanceof Id;
}
@Override
public String toString() {
return "stream";
}
}
}