/*
* Copyright 2015-present Facebook, Inc.
*
* 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.facebook.buck.httpserver;
import com.facebook.buck.artifact_cache.ArtifactCache;
import com.facebook.buck.artifact_cache.ArtifactInfo;
import com.facebook.buck.artifact_cache.CacheResult;
import com.facebook.buck.artifact_cache.HttpArtifactCacheBinaryProtocol;
import com.facebook.buck.artifact_cache.StoreResponseReadResult;
import com.facebook.buck.io.BorrowablePath;
import com.facebook.buck.io.LazyPath;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.log.Logger;
import com.facebook.buck.rules.RuleKey;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.ByteSource;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.Optional;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
/** Implements a really simple cache server on top of the local dircache. */
public class ArtifactCacheHandler extends AbstractHandler {
private static final Logger LOG = Logger.get(ArtifactCacheHandler.class);
private final ProjectFilesystem projectFilesystem;
private Optional<ArtifactCache> artifactCache;
public ArtifactCacheHandler(ProjectFilesystem projectFilesystem) {
this.artifactCache = Optional.empty();
this.projectFilesystem = projectFilesystem;
}
public void setArtifactCache(Optional<ArtifactCache> artifactCache) {
this.artifactCache = artifactCache;
}
@Override
public void handle(
String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
try {
int status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
String method = baseRequest.getMethod();
if (method.equals("GET")) {
status = handleGet(baseRequest, response);
} else if (method.equals("PUT")) {
status = handlePut(baseRequest, response);
}
response.setStatus(status);
} catch (Exception e) {
LOG.error(e, "Exception when handling request %s", target);
e.printStackTrace(response.getWriter());
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} finally {
response.flushBuffer();
baseRequest.setHandled(true);
}
}
private int handleGet(Request baseRequest, HttpServletResponse response) throws IOException {
if (!artifactCache.isPresent()) {
response.getWriter().write("Serving local cache is disabled for this instance.");
return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
}
String path = baseRequest.getUri().getPath();
String[] pathElements = path.split("/");
if (pathElements.length != 4 || !pathElements[2].equals("key")) {
response.getWriter().write("Incorrect url format.");
return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
}
RuleKey ruleKey = new RuleKey(pathElements[3]);
Path temp = null;
try {
projectFilesystem.mkdirs(projectFilesystem.getBuckPaths().getScratchDir());
temp =
projectFilesystem.createTempFile(
projectFilesystem.getBuckPaths().getScratchDir(), "outgoing_rulekey", ".tmp");
CacheResult fetchResult = artifactCache.get().fetch(ruleKey, LazyPath.ofInstance(temp));
if (!fetchResult.getType().isSuccess()) {
return HttpServletResponse.SC_NOT_FOUND;
}
final Path tempFinal = temp;
HttpArtifactCacheBinaryProtocol.FetchResponse fetchResponse =
new HttpArtifactCacheBinaryProtocol.FetchResponse(
ImmutableSet.of(ruleKey),
fetchResult.getMetadata(),
new ByteSource() {
@Override
public InputStream openStream() throws IOException {
return projectFilesystem.newFileInputStream(tempFinal);
}
});
fetchResponse.write(response.getOutputStream());
response.setContentLengthLong(fetchResponse.getContentLength());
return HttpServletResponse.SC_OK;
} finally {
if (temp != null) {
projectFilesystem.deleteFileAtPathIfExists(temp);
}
}
}
private int handlePut(Request baseRequest, HttpServletResponse response) throws IOException {
if (!artifactCache.isPresent()) {
response.getWriter().write("Serving local cache is disabled for this instance.");
return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
}
Path temp = null;
try {
projectFilesystem.mkdirs(projectFilesystem.getBuckPaths().getScratchDir());
temp =
projectFilesystem.createTempFile(
projectFilesystem.getBuckPaths().getScratchDir(), "incoming_upload", ".tmp");
StoreResponseReadResult storeRequest;
try (DataInputStream requestInputData = new DataInputStream(baseRequest.getInputStream());
OutputStream tempFileOutputStream = projectFilesystem.newFileOutputStream(temp)) {
storeRequest =
HttpArtifactCacheBinaryProtocol.readStoreRequest(
requestInputData, tempFileOutputStream);
}
if (!storeRequest.getActualHashCode().equals(storeRequest.getExpectedHashCode())) {
response.getWriter().write("Checksum mismatch.");
return HttpServletResponse.SC_NOT_ACCEPTABLE;
}
artifactCache
.get()
.store(
ArtifactInfo.builder()
.setRuleKeys(storeRequest.getRuleKeys())
.setMetadata(storeRequest.getMetadata())
.build(),
BorrowablePath.borrowablePath(temp));
return HttpServletResponse.SC_ACCEPTED;
} finally {
if (temp != null) {
projectFilesystem.deleteFileAtPathIfExists(temp);
}
}
}
}