/* dCache - http://www.dcache.org/
*
* Copyright (C) 2013-2015 Deutsches Elektronen-Synchrotron
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.dcache.http;
import com.google.common.collect.ImmutableMap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.UUID;
import diskCacheV111.util.CacheException;
import diskCacheV111.vehicles.HttpDoorUrlInfoMessage;
import diskCacheV111.vehicles.HttpProtocolInfo;
import dmg.cells.nucleus.CellAddressCore;
import dmg.cells.nucleus.CellPath;
import org.dcache.pool.movers.NettyMover;
import org.dcache.pool.movers.NettyTransferService;
import org.dcache.util.NetworkUtils;
/**
* Netty-based HTTP transfer service.
*
* The service generates a UUID that identifies the transfer and sends it back
* as a part of the address information to the door.
*
* This UUID has to be included in client requests to the netty server, so the
* netty server can extract the right mover.
*
* The netty server are started on demand and shared by all http transfers of
* a pool. All transfers are handled on the same port.
*/
public class HttpTransferService extends NettyTransferService<HttpProtocolInfo>
{
private static final Logger LOGGER = LoggerFactory.getLogger(HttpTransferService.class);
public static final String UUID_QUERY_PARAM = "dcache-http-uuid";
private static final String QUERY_PARAM_ASSIGN = "=";
private static final String PROTOCOL_HTTP = "http";
private int chunkSize;
private ImmutableMap<String,String> customHeaders;
public HttpTransferService()
{
super("http");
}
public int getChunkSize()
{
return chunkSize;
}
@Required
public void setChunkSize(int chunkSize)
{
this.chunkSize = chunkSize;
}
@Required
public void setCustomHeaders(ImmutableMap<String,String> headers)
{
customHeaders = headers;
}
@Override
protected UUID createUuid(HttpProtocolInfo protocolInfo)
{
return UUID.randomUUID();
}
/**
* Send the network address of this mover to the door, along with the UUID
* identifying it.
*/
@Override
protected void sendAddressToDoor(NettyMover<HttpProtocolInfo> mover, int port)
throws SocketException, CacheException
{
HttpProtocolInfo protocolInfo = mover.getProtocolInfo();
String uri;
try {
uri = getUri(protocolInfo, port, mover.getUuid()).toASCIIString();
} catch (URISyntaxException e) {
throw new RuntimeException("Failed to create URI for HTTP mover. Please report to support@dcache.org", e);
}
CellAddressCore httpDoor = new CellAddressCore(
protocolInfo.getHttpDoorCellName(), protocolInfo.getHttpDoorDomainName());
LOGGER.debug("Sending redirect URI {} to {}", uri, httpDoor);
HttpDoorUrlInfoMessage httpDoorMessage =
new HttpDoorUrlInfoMessage(mover.getFileAttributes().getPnfsId().getId(), uri);
httpDoorMessage.setId(protocolInfo.getSessionId());
doorStub.notify(new CellPath(httpDoor), httpDoorMessage);
}
private URI getUri(HttpProtocolInfo protocolInfo, int port, UUID uuid)
throws SocketException, CacheException, URISyntaxException
{
String path = protocolInfo.getPath();
if (!path.startsWith("/")) {
path = "/" + path;
}
InetAddress localIP =
NetworkUtils.getLocalAddress(protocolInfo.getSocketAddress().getAddress());
return new URI(PROTOCOL_HTTP,
null,
localIP.getHostAddress(),
port,
path,
UUID_QUERY_PARAM + QUERY_PARAM_ASSIGN + uuid.toString(),
null);
}
@Override
protected void initChannel(Channel ch) throws Exception
{
super.initChannel(ch);
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("encoder", new HttpResponseEncoder());
if (LOGGER.isDebugEnabled()) {
pipeline.addLast("logger", new LoggingHandler());
}
pipeline.addLast("idle-state-handler",
new IdleStateHandler(0,
0,
clientIdleTimeout,
clientIdleTimeoutUnit));
pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
pipeline.addLast("keepalive", new KeepAliveHandler());
if (!customHeaders.isEmpty()) {
pipeline.addLast("custom-headers", new CustomResponseHeadersHandler(customHeaders));
}
pipeline.addLast("transfer", new HttpPoolRequestHandler(this, chunkSize));
}
}