View Javadoc
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  
12  import java.io.IOException;
13  import java.io.PrintWriter;
14  import java.text.MessageFormat;
15  
16  import javax.servlet.AsyncContext;
17  import javax.servlet.ServletException;
18  import javax.servlet.annotation.WebServlet;
19  import javax.servlet.http.HttpServlet;
20  import javax.servlet.http.HttpServletRequest;
21  import javax.servlet.http.HttpServletResponse;
22  
23  import org.apache.http.HttpStatus;
24  import org.eclipse.jgit.lfs.errors.InvalidLongObjectIdException;
25  import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
26  import org.eclipse.jgit.lfs.lib.Constants;
27  import org.eclipse.jgit.lfs.lib.LongObjectId;
28  import org.eclipse.jgit.lfs.server.internal.LfsGson;
29  import org.eclipse.jgit.lfs.server.internal.LfsServerText;
30  
31  /**
32   * Servlet supporting upload and download of large objects as defined by the
33   * GitHub Large File Storage extension API extending git to allow separate
34   * storage of large files
35   * (https://github.com/github/git-lfs/tree/master/docs/api).
36   *
37   * @since 4.3
38   */
39  @WebServlet(asyncSupported = true)
40  public class FileLfsServlet extends HttpServlet {
41  
42  	private static final long serialVersionUID = 1L;
43  
44  	private final FileLfsRepository repository;
45  
46  	private final long timeout;
47  
48  	/**
49  	 * <p>Constructor for FileLfsServlet.</p>
50  	 *
51  	 * @param repository
52  	 *            the repository storing the large objects
53  	 * @param timeout
54  	 *            timeout for object upload / download in milliseconds
55  	 */
56  	public FileLfsServlet(FileLfsRepository repository, long timeout) {
57  		this.repository = repository;
58  		this.timeout = timeout;
59  	}
60  
61  	/**
62  	 * {@inheritDoc}
63  	 *
64  	 * Handle object downloads
65  	 */
66  	@Override
67  	protected void doGet(HttpServletRequest req,
68  			HttpServletResponse rsp) throws ServletException, IOException {
69  		AnyLongObjectId obj = getObjectToTransfer(req, rsp);
70  		if (obj != null) {
71  			if (repository.getSize(obj) == -1) {
72  				sendError(rsp, HttpStatus.SC_NOT_FOUND, MessageFormat
73  						.format(LfsServerText.get().objectNotFound,
74  								obj.getName()));
75  				return;
76  			}
77  			AsyncContext context = req.startAsync();
78  			context.setTimeout(timeout);
79  			rsp.getOutputStream()
80  					.setWriteListener(new ObjectDownloadListener(repository,
81  							context, rsp, obj));
82  		}
83  	}
84  
85  	/**
86  	 * Retrieve object id from request
87  	 *
88  	 * @param req
89  	 *            servlet request
90  	 * @param rsp
91  	 *            servlet response
92  	 * @return object id, or <code>null</code> if the object id could not be
93  	 *         retrieved
94  	 * @throws java.io.IOException
95  	 *             if an I/O error occurs
96  	 * @since 4.6
97  	 */
98  	protected AnyLongObjectId getObjectToTransfer(HttpServletRequest req,
99  			HttpServletResponse rsp) throws IOException {
100 		String info = req.getPathInfo();
101 		int length = 1 + Constants.LONG_OBJECT_ID_STRING_LENGTH;
102 		if (info.length() != length) {
103 			sendError(rsp, HttpStatus.SC_UNPROCESSABLE_ENTITY, MessageFormat
104 					.format(LfsServerText.get().invalidPathInfo, info));
105 			return null;
106 		}
107 		try {
108 			return LongObjectId.fromString(info.substring(1, length));
109 		} catch (InvalidLongObjectIdException e) {
110 			sendError(rsp, HttpStatus.SC_UNPROCESSABLE_ENTITY, e.getMessage());
111 			return null;
112 		}
113 	}
114 
115 	/**
116 	 * {@inheritDoc}
117 	 *
118 	 * Handle object uploads
119 	 */
120 	@Override
121 	protected void doPut(HttpServletRequest req,
122 			HttpServletResponse rsp) throws ServletException, IOException {
123 		AnyLongObjectId id = getObjectToTransfer(req, rsp);
124 		if (id != null) {
125 			AsyncContext context = req.startAsync();
126 			context.setTimeout(timeout);
127 			req.getInputStream().setReadListener(new ObjectUploadListener(
128 					repository, context, req, rsp, id));
129 		}
130 	}
131 
132 	/**
133 	 * Send an error response.
134 	 *
135 	 * @param rsp
136 	 *            the servlet response
137 	 * @param status
138 	 *            HTTP status code
139 	 * @param message
140 	 *            error message
141 	 * @throws java.io.IOException
142 	 *             on failure to send the response
143 	 * @since 4.6
144 	 */
145 	protected static void sendError(HttpServletResponse rsp, int status, String message)
146 			throws IOException {
147 		if (rsp.isCommitted()) {
148 			rsp.getOutputStream().close();
149 			return;
150 		}
151 		rsp.reset();
152 		rsp.setStatus(status);
153 		try (PrintWriter writer = rsp.getWriter()) {
154 			LfsGson.toJson(message, writer);
155 			writer.flush();
156 		}
157 		rsp.flushBuffer();
158 	}
159 }