According to the specification [1], the error response body must include the error message in json format. [1] https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md#response-errors Change-Id: I79e7a841d230fdedefa53b9c6d2d477e81e1f9e6 Signed-off-by: David Pursehouse <david.pursehouse@gmail.com>tags/v4.5.0.201609210915-r
public void testDownloadInvalidPathInfo() | public void testDownloadInvalidPathInfo() | ||||
throws ClientProtocolException, IOException { | throws ClientProtocolException, IOException { | ||||
String TEXT = "test"; | String TEXT = "test"; | ||||
AnyLongObjectId id = putContent(TEXT); | |||||
String id = putContent(TEXT).name().substring(0, 60); | |||||
Path f = Paths.get(getTempDirectory().toString(), "download"); | Path f = Paths.get(getTempDirectory().toString(), "download"); | ||||
try { | try { | ||||
getContent(id.name().substring(0, 60), f); | |||||
getContent(id, f); | |||||
fail("expected RuntimeException"); | fail("expected RuntimeException"); | ||||
} catch (RuntimeException e) { | } catch (RuntimeException e) { | ||||
assertEquals("Status: 422 Unprocessable Entity", | |||||
e.getMessage()); | |||||
String error = String.format( | |||||
"Invalid pathInfo '/%s' does not match '/{SHA-256}'", id); | |||||
assertEquals(formatErrorMessage(422, error), e.getMessage()); | |||||
} | } | ||||
} | } | ||||
public void testDownloadInvalidId() | public void testDownloadInvalidId() | ||||
throws ClientProtocolException, IOException { | throws ClientProtocolException, IOException { | ||||
String TEXT = "test"; | String TEXT = "test"; | ||||
AnyLongObjectId id = putContent(TEXT); | |||||
String id = putContent(TEXT).name().replace('f', 'z'); | |||||
Path f = Paths.get(getTempDirectory().toString(), "download"); | Path f = Paths.get(getTempDirectory().toString(), "download"); | ||||
try { | try { | ||||
getContent(id.name().replace('f', 'z'), f); | |||||
getContent(id, f); | |||||
fail("expected RuntimeException"); | fail("expected RuntimeException"); | ||||
} catch (RuntimeException e) { | } catch (RuntimeException e) { | ||||
assertEquals("Status: 422 Unprocessable Entity", | |||||
e.getMessage()); | |||||
String error = String.format("Invalid id: : %s", id); | |||||
assertEquals(formatErrorMessage(422, error), e.getMessage()); | |||||
} | } | ||||
} | } | ||||
getContent(id, f); | getContent(id, f); | ||||
fail("expected RuntimeException"); | fail("expected RuntimeException"); | ||||
} catch (RuntimeException e) { | } catch (RuntimeException e) { | ||||
assertEquals("Status: 404 Not Found", | |||||
e.getMessage()); | |||||
String error = String.format("Object '%s' not found", id.getName()); | |||||
assertEquals(formatErrorMessage(404, error), e.getMessage()); | |||||
} | } | ||||
} | } | ||||
FileUtils.delete(f.toFile(), FileUtils.RETRY); | FileUtils.delete(f.toFile(), FileUtils.RETRY); | ||||
} | } | ||||
@SuppressWarnings("boxing") | |||||
private String formatErrorMessage(int status, String message) { | |||||
return String.format("Status: %d {\n \"message\": \"%s\"\n}", status, | |||||
message); | |||||
} | |||||
} | } |
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
import java.io.BufferedInputStream; | import java.io.BufferedInputStream; | ||||
import java.io.ByteArrayOutputStream; | |||||
import java.io.FileNotFoundException; | import java.io.FileNotFoundException; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
StatusLine statusLine = response.getStatusLine(); | StatusLine statusLine = response.getStatusLine(); | ||||
int status = statusLine.getStatusCode(); | int status = statusLine.getStatusCode(); | ||||
if (statusLine.getStatusCode() >= 400) { | if (statusLine.getStatusCode() >= 400) { | ||||
throw new RuntimeException("Status: " + status + " " | |||||
+ statusLine.getReasonPhrase()); | |||||
String error; | |||||
try { | |||||
BufferedInputStream bis = new BufferedInputStream( | |||||
response.getEntity().getContent()); | |||||
ByteArrayOutputStream buf = new ByteArrayOutputStream(); | |||||
int result = bis.read(); | |||||
while (result != -1) { | |||||
buf.write((byte) result); | |||||
result = bis.read(); | |||||
} | |||||
error = buf.toString(); | |||||
} catch (IOException e) { | |||||
error = statusLine.getReasonPhrase(); | |||||
} | |||||
throw new RuntimeException("Status: " + status + " " + error); | |||||
} | } | ||||
assertEquals(200, status); | assertEquals(200, status); | ||||
} | } |
package org.eclipse.jgit.lfs.server.fs; | package org.eclipse.jgit.lfs.server.fs; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.PrintWriter; | |||||
import java.text.MessageFormat; | import java.text.MessageFormat; | ||||
import javax.servlet.AsyncContext; | import javax.servlet.AsyncContext; | ||||
import org.eclipse.jgit.lfs.lib.LongObjectId; | import org.eclipse.jgit.lfs.lib.LongObjectId; | ||||
import org.eclipse.jgit.lfs.server.internal.LfsServerText; | import org.eclipse.jgit.lfs.server.internal.LfsServerText; | ||||
import com.google.gson.FieldNamingPolicy; | |||||
import com.google.gson.Gson; | |||||
import com.google.gson.GsonBuilder; | |||||
/** | /** | ||||
* Servlet supporting upload and download of large objects as defined by the | * Servlet supporting upload and download of large objects as defined by the | ||||
* GitHub Large File Storage extension API extending git to allow separate | * GitHub Large File Storage extension API extending git to allow separate | ||||
private final long timeout; | private final long timeout; | ||||
private static Gson gson = createGson(); | |||||
/** | /** | ||||
* @param repository | * @param repository | ||||
* the repository storing the large objects | * the repository storing the large objects | ||||
if (obj != null) { | if (obj != null) { | ||||
if (repository.getSize(obj) == -1) { | if (repository.getSize(obj) == -1) { | ||||
sendError(rsp, HttpStatus.SC_NOT_FOUND, MessageFormat | sendError(rsp, HttpStatus.SC_NOT_FOUND, MessageFormat | ||||
.format(LfsServerText.get().objectNotFound, obj)); | |||||
.format(LfsServerText.get().objectNotFound, | |||||
obj.getName())); | |||||
return; | return; | ||||
} | } | ||||
AsyncContext context = req.startAsync(); | AsyncContext context = req.startAsync(); | ||||
} | } | ||||
} | } | ||||
static class Error { | |||||
String message; | |||||
Error(String m) { | |||||
this.message = m; | |||||
} | |||||
} | |||||
static void sendError(HttpServletResponse rsp, int status, String message) | static void sendError(HttpServletResponse rsp, int status, String message) | ||||
throws IOException { | throws IOException { | ||||
rsp.setStatus(status); | rsp.setStatus(status); | ||||
// TODO return message in response body in json format as specified in | |||||
// https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md | |||||
PrintWriter writer = rsp.getWriter(); | |||||
gson.toJson(new Error(message), writer); | |||||
writer.flush(); | |||||
writer.close(); | |||||
rsp.flushBuffer(); | rsp.flushBuffer(); | ||||
} | } | ||||
private static Gson createGson() { | |||||
GsonBuilder gb = new GsonBuilder() | |||||
.setFieldNamingPolicy( | |||||
FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) | |||||
.setPrettyPrinting().disableHtmlEscaping(); | |||||
return gb.create(); | |||||
} | |||||
} | } |