blob: c9cdd0c853120b67fe8d98766346fe4832e3300f [file] [log] [blame]
// Copyright 2018 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.remote.blobstore.http;
import com.google.auth.Credentials;
import com.google.common.base.Charsets;
import com.google.common.io.BaseEncoding;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandler;
import io.netty.channel.ChannelPromise;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import java.io.IOException;
import java.net.SocketAddress;
import java.net.URI;
import java.nio.channels.ClosedChannelException;
import java.util.List;
import java.util.Map;
/** Common functionality shared by concrete classes. */
abstract class AbstractHttpHandler<T extends HttpObject> extends SimpleChannelInboundHandler<T>
implements ChannelOutboundHandler {
private final Credentials credentials;
public AbstractHttpHandler(Credentials credentials) {
this.credentials = credentials;
}
protected ChannelPromise userPromise;
@SuppressWarnings("FutureReturnValueIgnored")
protected void failAndResetUserPromise(Throwable t) {
if (userPromise != null && !userPromise.isDone()) {
userPromise.setFailure(t);
}
userPromise = null;
}
@SuppressWarnings("FutureReturnValueIgnored")
protected void succeedAndResetUserPromise() {
userPromise.setSuccess();
userPromise = null;
}
protected void addCredentialHeaders(HttpRequest request, URI uri) throws IOException {
String userInfo = uri.getUserInfo();
if (userInfo != null) {
String value = BaseEncoding.base64Url().encode(userInfo.getBytes(Charsets.UTF_8));
request.headers().set(HttpHeaderNames.AUTHORIZATION, "Basic " + value);
return;
}
if (credentials == null || !credentials.hasRequestMetadata()) {
return;
}
Map<String, List<String>> authHeaders = credentials.getRequestMetadata(uri);
if (authHeaders == null || authHeaders.isEmpty()) {
return;
}
for (Map.Entry<String, List<String>> entry : authHeaders.entrySet()) {
String name = entry.getKey();
for (String value : entry.getValue()) {
request.headers().add(name, value);
}
}
}
protected String constructPath(URI uri, String hash, boolean isCas) {
StringBuilder builder = new StringBuilder();
builder.append(uri.getPath());
if (!uri.getPath().endsWith("/")) {
builder.append("/");
}
builder.append(isCas ? "cas/" : "ac/");
builder.append(hash);
return builder.toString();
}
protected String constructHost(URI uri) {
return uri.getHost() + ":" + uri.getPort();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable t) {
failAndResetUserPromise(t);
ctx.fireExceptionCaught(t);
}
@SuppressWarnings("FutureReturnValueIgnored")
@Override
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
ctx.bind(localAddress, promise);
}
@SuppressWarnings("FutureReturnValueIgnored")
@Override
public void connect(
ChannelHandlerContext ctx,
SocketAddress remoteAddress,
SocketAddress localAddress,
ChannelPromise promise) {
ctx.connect(remoteAddress, localAddress, promise);
}
@SuppressWarnings("FutureReturnValueIgnored")
@Override
public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) {
failAndResetUserPromise(new ClosedChannelException());
ctx.disconnect(promise);
}
@SuppressWarnings("FutureReturnValueIgnored")
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
failAndResetUserPromise(new ClosedChannelException());
ctx.close(promise);
}
@SuppressWarnings("FutureReturnValueIgnored")
@Override
public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) {
failAndResetUserPromise(new ClosedChannelException());
ctx.deregister(promise);
}
@SuppressWarnings("FutureReturnValueIgnored")
@Override
public void read(ChannelHandlerContext ctx) {
ctx.read();
}
@SuppressWarnings("FutureReturnValueIgnored")
@Override
public void flush(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
failAndResetUserPromise(new ClosedChannelException());
ctx.fireChannelInactive();
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) {
failAndResetUserPromise(new IOException("handler removed"));
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) {
failAndResetUserPromise(new ClosedChannelException());
ctx.fireChannelUnregistered();
}
}