Return content as stream

This commit is contained in:
René Pfeuffer
2018-08-13 15:52:50 +02:00
parent b197f92945
commit 815cee1569
5 changed files with 169 additions and 21 deletions

View File

@@ -2,7 +2,8 @@ package sonia.scm.api.v2.resources;
import com.github.sdorra.spotter.ContentType; import com.github.sdorra.spotter.ContentType;
import com.github.sdorra.spotter.ContentTypes; import com.github.sdorra.spotter.ContentTypes;
import com.github.sdorra.spotter.Language; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.PathNotFoundException; import sonia.scm.repository.PathNotFoundException;
import sonia.scm.repository.RepositoryException; import sonia.scm.repository.RepositoryException;
@@ -16,12 +17,14 @@ import javax.ws.rs.HEAD;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Optional;
public class ContentResource { public class ContentResource {
private static final Logger LOG = LoggerFactory.getLogger(ContentResource.class);
private final RepositoryServiceFactory servicefactory; private final RepositoryServiceFactory servicefactory;
@Inject @Inject
@@ -34,17 +37,27 @@ public class ContentResource {
public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) { public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) {
try (RepositoryService repositoryService = servicefactory.create(new NamespaceAndName(namespace, name))) { try (RepositoryService repositoryService = servicefactory.create(new NamespaceAndName(namespace, name))) {
try { try {
byte[] content = getContent(revision, path, repositoryService);
Response.ResponseBuilder responseBuilder = Response.ok(content); StreamingOutput stream = os -> {
appendContentType(path, content, responseBuilder); try {
repositoryService.getCatCommand().setRevision(revision).retriveContent(os, path);
} catch (RepositoryException e) {
e.printStackTrace();
}
os.close();
};
Response.ResponseBuilder responseBuilder = Response.ok(stream);
appendContentType(path, getHead(revision, path, repositoryService), responseBuilder);
return responseBuilder.build(); return responseBuilder.build();
} catch (PathNotFoundException e) { } catch (PathNotFoundException e) {
return Response.status(404).build(); return Response.status(404).build();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); LOG.error("error reading repository resource {} from {}/{}", path, namespace, name, e);
return Response.status(500).entity(e.getMessage()).build(); return Response.status(500).entity(e.getMessage()).build();
} catch (RepositoryException e) { } catch (RepositoryException e) {
e.printStackTrace(); LOG.error("error reading repository resource {} from {}/{}", path, namespace, name, e);
return Response.status(500).entity(e.getMessage()).build(); return Response.status(500).entity(e.getMessage()).build();
} }
} catch (RepositoryNotFoundException e) { } catch (RepositoryNotFoundException e) {
@@ -57,19 +70,18 @@ public class ContentResource {
public Response metadata(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) { public Response metadata(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) {
try (RepositoryService repositoryService = servicefactory.create(new NamespaceAndName(namespace, name))) { try (RepositoryService repositoryService = servicefactory.create(new NamespaceAndName(namespace, name))) {
try { try {
byte[] content = getContent(revision, path, repositoryService);
Response.ResponseBuilder responseBuilder = Response.ok(); Response.ResponseBuilder responseBuilder = Response.ok();
appendContentType(path, content, responseBuilder); appendContentType(path, getHead(revision, path, repositoryService), responseBuilder);
return responseBuilder.build(); return responseBuilder.build();
} catch (PathNotFoundException e) { } catch (PathNotFoundException e) {
return Response.status(404).build(); return Response.status(404).build();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); LOG.error("error reading repository resource {} from {}/{}", path, namespace, name, e);
return Response.status(500).entity(e.getMessage()).build(); return Response.status(500).entity(e.getMessage()).build();
} catch (RepositoryException e) { } catch (RepositoryException e) {
e.printStackTrace(); LOG.error("error reading repository resource {} from {}/{}", path, namespace, name, e);
return Response.status(500).entity(e.getMessage()).build(); return Response.status(500).entity(e.getMessage()).build();
} }
} catch (RepositoryNotFoundException e) { } catch (RepositoryNotFoundException e) {
@@ -77,18 +89,14 @@ public class ContentResource {
} }
} }
private void appendContentType(String path, byte[] content, Response.ResponseBuilder responseBuilder) { private void appendContentType(String path, byte[] head, Response.ResponseBuilder responseBuilder) {
ContentType contentType = ContentTypes.detect(path, content); ContentType contentType = ContentTypes.detect(path, head);
System.out.println("Content-Type: " + contentType); responseBuilder.header("Content-Type", contentType.getRaw());
contentType.getLanguage().ifPresent(language -> responseBuilder.header("Language", language));
Optional<Language> language = contentType.getLanguage(); responseBuilder.header("Content-Length", head.length);
if (language.isPresent()) {
responseBuilder.header("Content-Type", contentType);
}
responseBuilder.header("Content-Length", content.length);
} }
private byte[] getContent(String revision, String path, RepositoryService repositoryService) throws IOException, RepositoryException { private byte[] getHead(String revision, String path, RepositoryService repositoryService) throws IOException, RepositoryException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
repositoryService.getCatCommand().setRevision(revision).retriveContent(outputStream, path); repositoryService.getCatCommand().setRevision(revision).retriveContent(outputStream, path);
return outputStream.toByteArray(); return outputStream.toByteArray();

View File

@@ -0,0 +1,131 @@
package sonia.scm.api.v2.resources;
import com.google.common.io.Resources;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.PathNotFoundException;
import sonia.scm.repository.RepositoryNotFoundException;
import sonia.scm.repository.api.CatCommandBuilder;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.net.URL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.AdditionalMatchers.not;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class ContentResourceTest {
private static final String NAMESPACE = "space";
private static final String REPO_NAME = "name";
private static final String REV = "rev";
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private RepositoryServiceFactory repositoryServiceFactory;
@InjectMocks
private ContentResource contentResource;
private CatCommandBuilder catCommand;
@Before
public void initService() throws Exception {
NamespaceAndName existingNamespaceAndName = new NamespaceAndName(NAMESPACE, REPO_NAME);
RepositoryService repositoryService = repositoryServiceFactory.create(existingNamespaceAndName);
catCommand = repositoryService.getCatCommand();
when(catCommand.setRevision(REV)).thenReturn(catCommand);
// defaults for unknown things
doThrow(new RepositoryNotFoundException("x")).when(repositoryServiceFactory).create(not(eq(existingNamespaceAndName)));
doThrow(new PathNotFoundException("x")).when(catCommand).retriveContent(any(), any());
}
@Test
public void shouldReadSimpleFile() throws Exception {
mockContent("file", "Hello".getBytes());
Response response = contentResource.get(NAMESPACE, REPO_NAME, REV, "file");
assertEquals(200, response.getStatus());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
((StreamingOutput) response.getEntity()).write(baos);
assertEquals("Hello", baos.toString());
}
@Test
public void shouldHandleMissingFile() {
Response response = contentResource.get(NAMESPACE, REPO_NAME, REV, "doesNotExist");
assertEquals(404, response.getStatus());
}
@Test
public void shouldHandleMissingRepository() {
Response response = contentResource.get("no", "repo", REV, "anything");
assertEquals(404, response.getStatus());
}
@Test
public void shouldRecognizeTikaSourceCode() throws Exception {
mockContentFromResource("SomeGoCode.go");
Response response = contentResource.get(NAMESPACE, REPO_NAME, REV, "SomeGoCode.go");
assertEquals(200, response.getStatus());
assertEquals("GO", response.getHeaderString("Language"));
assertEquals("text/x-go", response.getHeaderString("Content-Type"));
}
@Test
public void shouldRecognizeSpecialSourceCode() throws Exception {
mockContentFromResource("Dockerfile");
Response response = contentResource.get(NAMESPACE, REPO_NAME, REV, "Dockerfile");
assertEquals(200, response.getStatus());
assertEquals("DOCKERFILE", response.getHeaderString("Language"));
assertEquals("text/plain", response.getHeaderString("Content-Type"));
}
@Test
public void shouldRandomByteFile() throws Exception {
mockContentFromResource("JustBytes");
Response response = contentResource.get(NAMESPACE, REPO_NAME, REV, "JustBytes");
assertEquals(200, response.getStatus());
assertFalse(response.getHeaders().containsKey("Language"));
assertEquals("application/octet-stream", response.getHeaderString("Content-Type"));
}
private void mockContentFromResource(String fileName) throws Exception {
URL url = Resources.getResource(fileName);
mockContent(fileName, Resources.toByteArray(url));
}
private void mockContent(String path, byte[] content) throws Exception {
doAnswer(invocation -> {
OutputStream outputStream = (OutputStream) invocation.getArguments()[0];
outputStream.write(content);
outputStream.close();
return null;
}).when(catCommand).retriveContent(any(), eq(path));
}
}

View File

@@ -0,0 +1,8 @@
FROM ubuntu
COPY nothing /nowhere
RUN apt-get update
EXEC shutdhown -h now

Binary file not shown.

View File

@@ -0,0 +1 @@
package resources