mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-12 00:15:44 +01:00
Return content as stream
This commit is contained in:
@@ -2,7 +2,8 @@ package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.github.sdorra.spotter.ContentType;
|
||||
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.PathNotFoundException;
|
||||
import sonia.scm.repository.RepositoryException;
|
||||
@@ -16,12 +17,14 @@ import javax.ws.rs.HEAD;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.StreamingOutput;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ContentResource {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ContentResource.class);
|
||||
|
||||
private final RepositoryServiceFactory servicefactory;
|
||||
|
||||
@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) {
|
||||
try (RepositoryService repositoryService = servicefactory.create(new NamespaceAndName(namespace, name))) {
|
||||
try {
|
||||
byte[] content = getContent(revision, path, repositoryService);
|
||||
Response.ResponseBuilder responseBuilder = Response.ok(content);
|
||||
appendContentType(path, content, responseBuilder);
|
||||
|
||||
StreamingOutput stream = os -> {
|
||||
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();
|
||||
} catch (PathNotFoundException e) {
|
||||
return Response.status(404).build();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOG.error("error reading repository resource {} from {}/{}", path, namespace, name, e);
|
||||
return Response.status(500).entity(e.getMessage()).build();
|
||||
} catch (RepositoryException e) {
|
||||
e.printStackTrace();
|
||||
LOG.error("error reading repository resource {} from {}/{}", path, namespace, name, e);
|
||||
return Response.status(500).entity(e.getMessage()).build();
|
||||
}
|
||||
} 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) {
|
||||
try (RepositoryService repositoryService = servicefactory.create(new NamespaceAndName(namespace, name))) {
|
||||
try {
|
||||
byte[] content = getContent(revision, path, repositoryService);
|
||||
|
||||
Response.ResponseBuilder responseBuilder = Response.ok();
|
||||
|
||||
appendContentType(path, content, responseBuilder);
|
||||
appendContentType(path, getHead(revision, path, repositoryService), responseBuilder);
|
||||
return responseBuilder.build();
|
||||
} catch (PathNotFoundException e) {
|
||||
return Response.status(404).build();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOG.error("error reading repository resource {} from {}/{}", path, namespace, name, e);
|
||||
return Response.status(500).entity(e.getMessage()).build();
|
||||
} catch (RepositoryException e) {
|
||||
e.printStackTrace();
|
||||
LOG.error("error reading repository resource {} from {}/{}", path, namespace, name, e);
|
||||
return Response.status(500).entity(e.getMessage()).build();
|
||||
}
|
||||
} catch (RepositoryNotFoundException e) {
|
||||
@@ -77,18 +89,14 @@ public class ContentResource {
|
||||
}
|
||||
}
|
||||
|
||||
private void appendContentType(String path, byte[] content, Response.ResponseBuilder responseBuilder) {
|
||||
ContentType contentType = ContentTypes.detect(path, content);
|
||||
System.out.println("Content-Type: " + contentType);
|
||||
|
||||
Optional<Language> language = contentType.getLanguage();
|
||||
if (language.isPresent()) {
|
||||
responseBuilder.header("Content-Type", contentType);
|
||||
}
|
||||
responseBuilder.header("Content-Length", content.length);
|
||||
private void appendContentType(String path, byte[] head, Response.ResponseBuilder responseBuilder) {
|
||||
ContentType contentType = ContentTypes.detect(path, head);
|
||||
responseBuilder.header("Content-Type", contentType.getRaw());
|
||||
contentType.getLanguage().ifPresent(language -> responseBuilder.header("Language", language));
|
||||
responseBuilder.header("Content-Length", head.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();
|
||||
repositoryService.getCatCommand().setRevision(revision).retriveContent(outputStream, path);
|
||||
return outputStream.toByteArray();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
8
scm-webapp/src/test/resources/Dockerfile
Normal file
8
scm-webapp/src/test/resources/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
||||
FROM ubuntu
|
||||
|
||||
COPY nothing /nowhere
|
||||
|
||||
RUN apt-get update
|
||||
|
||||
EXEC shutdhown -h now
|
||||
|
||||
BIN
scm-webapp/src/test/resources/JustBytes
Normal file
BIN
scm-webapp/src/test/resources/JustBytes
Normal file
Binary file not shown.
1
scm-webapp/src/test/resources/SomeGoCode.go
Normal file
1
scm-webapp/src/test/resources/SomeGoCode.go
Normal file
@@ -0,0 +1 @@
|
||||
package resources
|
||||
Reference in New Issue
Block a user