FileLfsServlet.java

  1. /*
  2.  * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com> and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */
  10. package org.eclipse.jgit.lfs.server.fs;

  11. import java.io.IOException;
  12. import java.io.PrintWriter;
  13. import java.text.MessageFormat;

  14. import javax.servlet.AsyncContext;
  15. import javax.servlet.ServletException;
  16. import javax.servlet.annotation.WebServlet;
  17. import javax.servlet.http.HttpServlet;
  18. import javax.servlet.http.HttpServletRequest;
  19. import javax.servlet.http.HttpServletResponse;

  20. import org.apache.http.HttpStatus;
  21. import org.eclipse.jgit.lfs.errors.InvalidLongObjectIdException;
  22. import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
  23. import org.eclipse.jgit.lfs.lib.Constants;
  24. import org.eclipse.jgit.lfs.lib.LongObjectId;
  25. import org.eclipse.jgit.lfs.server.internal.LfsGson;
  26. import org.eclipse.jgit.lfs.server.internal.LfsServerText;

  27. /**
  28.  * Servlet supporting upload and download of large objects as defined by the
  29.  * GitHub Large File Storage extension API extending git to allow separate
  30.  * storage of large files
  31.  * (https://github.com/github/git-lfs/tree/master/docs/api).
  32.  *
  33.  * @since 4.3
  34.  */
  35. @WebServlet(asyncSupported = true)
  36. public class FileLfsServlet extends HttpServlet {

  37.     private static final long serialVersionUID = 1L;

  38.     private final FileLfsRepository repository;

  39.     private final long timeout;

  40.     /**
  41.      * <p>Constructor for FileLfsServlet.</p>
  42.      *
  43.      * @param repository
  44.      *            the repository storing the large objects
  45.      * @param timeout
  46.      *            timeout for object upload / download in milliseconds
  47.      */
  48.     public FileLfsServlet(FileLfsRepository repository, long timeout) {
  49.         this.repository = repository;
  50.         this.timeout = timeout;
  51.     }

  52.     /**
  53.      * {@inheritDoc}
  54.      *
  55.      * Handle object downloads
  56.      */
  57.     @Override
  58.     protected void doGet(HttpServletRequest req,
  59.             HttpServletResponse rsp) throws ServletException, IOException {
  60.         AnyLongObjectId obj = getObjectToTransfer(req, rsp);
  61.         if (obj != null) {
  62.             if (repository.getSize(obj) == -1) {
  63.                 sendError(rsp, HttpStatus.SC_NOT_FOUND, MessageFormat
  64.                         .format(LfsServerText.get().objectNotFound,
  65.                                 obj.getName()));
  66.                 return;
  67.             }
  68.             AsyncContext context = req.startAsync();
  69.             context.setTimeout(timeout);
  70.             rsp.getOutputStream()
  71.                     .setWriteListener(new ObjectDownloadListener(repository,
  72.                             context, rsp, obj));
  73.         }
  74.     }

  75.     /**
  76.      * Retrieve object id from request
  77.      *
  78.      * @param req
  79.      *            servlet request
  80.      * @param rsp
  81.      *            servlet response
  82.      * @return object id, or <code>null</code> if the object id could not be
  83.      *         retrieved
  84.      * @throws java.io.IOException
  85.      *             if an I/O error occurs
  86.      * @since 4.6
  87.      */
  88.     protected AnyLongObjectId getObjectToTransfer(HttpServletRequest req,
  89.             HttpServletResponse rsp) throws IOException {
  90.         String info = req.getPathInfo();
  91.         int length = 1 + Constants.LONG_OBJECT_ID_STRING_LENGTH;
  92.         if (info.length() != length) {
  93.             sendError(rsp, HttpStatus.SC_UNPROCESSABLE_ENTITY, MessageFormat
  94.                     .format(LfsServerText.get().invalidPathInfo, info));
  95.             return null;
  96.         }
  97.         try {
  98.             return LongObjectId.fromString(info.substring(1, length));
  99.         } catch (InvalidLongObjectIdException e) {
  100.             sendError(rsp, HttpStatus.SC_UNPROCESSABLE_ENTITY, e.getMessage());
  101.             return null;
  102.         }
  103.     }

  104.     /**
  105.      * {@inheritDoc}
  106.      *
  107.      * Handle object uploads
  108.      */
  109.     @Override
  110.     protected void doPut(HttpServletRequest req,
  111.             HttpServletResponse rsp) throws ServletException, IOException {
  112.         AnyLongObjectId id = getObjectToTransfer(req, rsp);
  113.         if (id != null) {
  114.             AsyncContext context = req.startAsync();
  115.             context.setTimeout(timeout);
  116.             req.getInputStream().setReadListener(new ObjectUploadListener(
  117.                     repository, context, req, rsp, id));
  118.         }
  119.     }

  120.     /**
  121.      * Send an error response.
  122.      *
  123.      * @param rsp
  124.      *            the servlet response
  125.      * @param status
  126.      *            HTTP status code
  127.      * @param message
  128.      *            error message
  129.      * @throws java.io.IOException
  130.      *             on failure to send the response
  131.      * @since 4.6
  132.      */
  133.     protected static void sendError(HttpServletResponse rsp, int status, String message)
  134.             throws IOException {
  135.         if (rsp.isCommitted()) {
  136.             rsp.getOutputStream().close();
  137.             return;
  138.         }
  139.         rsp.reset();
  140.         rsp.setStatus(status);
  141.         try (PrintWriter writer = rsp.getWriter()) {
  142.             LfsGson.toJson(message, writer);
  143.             writer.flush();
  144.         }
  145.         rsp.flushBuffer();
  146.     }
  147. }