Permit applications embedding GitServlet to wrap the info/refs?service=$name and /$name operations with a servlet Filter. To help applications inspect state of the operation, expose the UploadPack or ReceivePack object into a request attribute. This can be useful for logging, or to implement throttling of requests like Gerrit Code Review uses to prevent server overload. Change-Id: Ib8773c14e2b7a650769bd578aad745e6651210cb Signed-off-by: Shawn O. Pearce <spearce@spearce.org>tags/v0.12.1
@@ -45,7 +45,10 @@ package org.eclipse.jgit.http.server; | |||
import java.io.File; | |||
import java.text.MessageFormat; | |||
import java.util.LinkedList; | |||
import java.util.List; | |||
import javax.servlet.Filter; | |||
import javax.servlet.ServletConfig; | |||
import javax.servlet.ServletException; | |||
import javax.servlet.http.HttpServletRequest; | |||
@@ -55,9 +58,9 @@ import org.eclipse.jgit.http.server.glue.ErrorServlet; | |||
import org.eclipse.jgit.http.server.glue.MetaServlet; | |||
import org.eclipse.jgit.http.server.glue.RegexGroupFilter; | |||
import org.eclipse.jgit.http.server.glue.ServletBinder; | |||
import org.eclipse.jgit.http.server.resolver.AsIsFileService; | |||
import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory; | |||
import org.eclipse.jgit.http.server.resolver.DefaultUploadPackFactory; | |||
import org.eclipse.jgit.http.server.resolver.AsIsFileService; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.transport.ReceivePack; | |||
import org.eclipse.jgit.transport.UploadPack; | |||
@@ -114,6 +117,10 @@ public class GitServlet extends MetaServlet { | |||
private ReceivePackFactory<HttpServletRequest> receivePackFactory = new DefaultReceivePackFactory(); | |||
private final List<Filter> uploadPackFilters = new LinkedList<Filter>(); | |||
private final List<Filter> receivePackFilters = new LinkedList<Filter>(); | |||
/** | |||
* New servlet that will load its base directory from {@code web.xml}. | |||
* <p> | |||
@@ -160,6 +167,17 @@ public class GitServlet extends MetaServlet { | |||
this.uploadPackFactory = f != null ? f : (UploadPackFactory<HttpServletRequest>)UploadPackFactory.DISABLED; | |||
} | |||
/** | |||
* @param filter | |||
* filter to apply before any of the UploadPack operations. The | |||
* UploadPack instance is available in the request attribute | |||
* {@link ServletUtils#ATTRIBUTE_HANDLER}. | |||
*/ | |||
public void addUploadPackFilter(Filter filter) { | |||
assertNotInitialized(); | |||
uploadPackFilters.add(filter); | |||
} | |||
/** | |||
* @param f | |||
* the factory to construct and configure a {@link ReceivePack} | |||
@@ -171,6 +189,17 @@ public class GitServlet extends MetaServlet { | |||
this.receivePackFactory = f != null ? f : (ReceivePackFactory<HttpServletRequest>)ReceivePackFactory.DISABLED; | |||
} | |||
/** | |||
* @param filter | |||
* filter to apply before any of the ReceivePack operations. The | |||
* ReceivePack instance is available in the request attribute | |||
* {@link ServletUtils#ATTRIBUTE_HANDLER}. | |||
*/ | |||
public void addReceivePackFilter(Filter filter) { | |||
assertNotInitialized(); | |||
receivePackFilters.add(filter); | |||
} | |||
private void assertNotInitialized() { | |||
if (initialized) | |||
throw new IllegalStateException(HttpServerText.get().alreadyInitializedByContainer); | |||
@@ -189,23 +218,29 @@ public class GitServlet extends MetaServlet { | |||
initialized = true; | |||
if (uploadPackFactory != UploadPackFactory.DISABLED) { | |||
serve("*/git-upload-pack")// | |||
.with(new UploadPackServlet(uploadPackFactory)); | |||
ServletBinder b = serve("*/git-upload-pack"); | |||
b = b.through(new UploadPackServlet.Factory(uploadPackFactory)); | |||
for (Filter f : uploadPackFilters) | |||
b = b.through(f); | |||
b.with(new UploadPackServlet()); | |||
} | |||
if (receivePackFactory != ReceivePackFactory.DISABLED) { | |||
serve("*/git-receive-pack")// | |||
.with(new ReceivePackServlet(receivePackFactory)); | |||
ServletBinder b = serve("*/git-receive-pack"); | |||
b = b.through(new ReceivePackServlet.Factory(receivePackFactory)); | |||
for (Filter f : receivePackFilters) | |||
b = b.through(f); | |||
b.with(new ReceivePackServlet()); | |||
} | |||
ServletBinder refs = serve("*/" + Constants.INFO_REFS); | |||
if (uploadPackFactory != UploadPackFactory.DISABLED) { | |||
refs = refs.through(// | |||
new UploadPackServlet.InfoRefs(uploadPackFactory)); | |||
refs = refs.through(new UploadPackServlet.InfoRefs( | |||
uploadPackFactory, uploadPackFilters)); | |||
} | |||
if (receivePackFactory != ReceivePackFactory.DISABLED) { | |||
refs = refs.through(// | |||
new ReceivePackServlet.InfoRefs(receivePackFactory)); | |||
refs = refs.through(new ReceivePackServlet.InfoRefs( | |||
receivePackFactory, receivePackFilters)); | |||
} | |||
if (asIs != AsIsFileService.DISABLED) { | |||
refs = refs.through(new IsLocalFilter()); |
@@ -47,11 +47,19 @@ import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; | |||
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; | |||
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; | |||
import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE; | |||
import static org.eclipse.jgit.http.server.ServletUtils.ATTRIBUTE_HANDLER; | |||
import static org.eclipse.jgit.http.server.ServletUtils.getInputStream; | |||
import static org.eclipse.jgit.http.server.ServletUtils.getRepository; | |||
import java.io.IOException; | |||
import java.util.List; | |||
import javax.servlet.Filter; | |||
import javax.servlet.FilterChain; | |||
import javax.servlet.FilterConfig; | |||
import javax.servlet.ServletException; | |||
import javax.servlet.ServletRequest; | |||
import javax.servlet.ServletResponse; | |||
import javax.servlet.http.HttpServlet; | |||
import javax.servlet.http.HttpServletRequest; | |||
import javax.servlet.http.HttpServletResponse; | |||
@@ -72,18 +80,27 @@ class ReceivePackServlet extends HttpServlet { | |||
private static final long serialVersionUID = 1L; | |||
static class InfoRefs extends SmartServiceInfoRefs { | |||
private final ReceivePackFactory receivePackFactory; | |||
private final ReceivePackFactory<HttpServletRequest> receivePackFactory; | |||
InfoRefs(final ReceivePackFactory receivePackFactory) { | |||
super("git-receive-pack"); | |||
InfoRefs(ReceivePackFactory<HttpServletRequest> receivePackFactory, | |||
List<Filter> filters) { | |||
super("git-receive-pack", filters); | |||
this.receivePackFactory = receivePackFactory; | |||
} | |||
@Override | |||
protected void advertise(HttpServletRequest req, Repository db, | |||
protected void begin(HttpServletRequest req, Repository db) | |||
throws IOException, ServiceNotEnabledException, | |||
ServiceNotAuthorizedException { | |||
ReceivePack rp = receivePackFactory.create(req, db); | |||
req.setAttribute(ATTRIBUTE_HANDLER, rp); | |||
} | |||
@Override | |||
protected void advertise(HttpServletRequest req, | |||
PacketLineOutRefAdvertiser pck) throws IOException, | |||
ServiceNotEnabledException, ServiceNotAuthorizedException { | |||
ReceivePack rp = receivePackFactory.create(req, db); | |||
ReceivePack rp = (ReceivePack) req.getAttribute(ATTRIBUTE_HANDLER); | |||
try { | |||
rp.sendAdvertisedRefs(pck); | |||
} finally { | |||
@@ -92,10 +109,44 @@ class ReceivePackServlet extends HttpServlet { | |||
} | |||
} | |||
private final ReceivePackFactory receivePackFactory; | |||
static class Factory implements Filter { | |||
private final ReceivePackFactory<HttpServletRequest> receivePackFactory; | |||
ReceivePackServlet(final ReceivePackFactory receivePackFactory) { | |||
this.receivePackFactory = receivePackFactory; | |||
Factory(ReceivePackFactory<HttpServletRequest> receivePackFactory) { | |||
this.receivePackFactory = receivePackFactory; | |||
} | |||
public void doFilter(ServletRequest request, ServletResponse response, | |||
FilterChain chain) throws IOException, ServletException { | |||
HttpServletRequest req = (HttpServletRequest) request; | |||
HttpServletResponse rsp = (HttpServletResponse) response; | |||
ReceivePack rp; | |||
try { | |||
rp = receivePackFactory.create(req, getRepository(req)); | |||
} catch (ServiceNotAuthorizedException e) { | |||
rsp.sendError(SC_UNAUTHORIZED); | |||
return; | |||
} catch (ServiceNotEnabledException e) { | |||
rsp.sendError(SC_FORBIDDEN); | |||
return; | |||
} | |||
try { | |||
req.setAttribute(ATTRIBUTE_HANDLER, rp); | |||
chain.doFilter(req, rsp); | |||
} finally { | |||
req.removeAttribute(ATTRIBUTE_HANDLER); | |||
} | |||
} | |||
public void init(FilterConfig filterConfig) throws ServletException { | |||
// Nothing. | |||
} | |||
public void destroy() { | |||
// Nothing. | |||
} | |||
} | |||
@Override | |||
@@ -106,9 +157,8 @@ class ReceivePackServlet extends HttpServlet { | |||
return; | |||
} | |||
final Repository db = getRepository(req); | |||
ReceivePack rp = (ReceivePack) req.getAttribute(ATTRIBUTE_HANDLER); | |||
try { | |||
final ReceivePack rp = receivePackFactory.create(req, db); | |||
rp.setBiDirectionalPipe(false); | |||
rsp.setContentType(RSP_TYPE); | |||
@@ -120,15 +170,6 @@ class ReceivePackServlet extends HttpServlet { | |||
}; | |||
rp.receive(getInputStream(req), out, null); | |||
out.close(); | |||
} catch (ServiceNotAuthorizedException e) { | |||
rsp.sendError(SC_UNAUTHORIZED); | |||
return; | |||
} catch (ServiceNotEnabledException e) { | |||
rsp.sendError(SC_FORBIDDEN); | |||
return; | |||
} catch (IOException e) { | |||
getServletContext().log(HttpServerText.get().internalErrorDuringReceivePack, e); | |||
rsp.sendError(SC_INTERNAL_SERVER_ERROR); |
@@ -71,6 +71,9 @@ public final class ServletUtils { | |||
/** Request attribute which stores the {@link Repository} instance. */ | |||
public static final String ATTRIBUTE_REPOSITORY = "org.eclipse.jgit.Repository"; | |||
/** Request attribute storing either UploadPack or ReceivePack. */ | |||
public static final String ATTRIBUTE_HANDLER = "org.eclipse.jgit.transport.UploadPackOrReceivePack"; | |||
/** | |||
* Get the selected repository from the request. | |||
* |
@@ -45,9 +45,11 @@ package org.eclipse.jgit.http.server; | |||
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; | |||
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; | |||
import static org.eclipse.jgit.http.server.ServletUtils.ATTRIBUTE_HANDLER; | |||
import static org.eclipse.jgit.http.server.ServletUtils.getRepository; | |||
import java.io.IOException; | |||
import java.util.List; | |||
import javax.servlet.Filter; | |||
import javax.servlet.FilterChain; | |||
@@ -68,8 +70,11 @@ import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; | |||
abstract class SmartServiceInfoRefs implements Filter { | |||
private final String svc; | |||
SmartServiceInfoRefs(final String service) { | |||
private final Filter[] filters; | |||
SmartServiceInfoRefs(final String service, final List<Filter> filters) { | |||
this.svc = service; | |||
this.filters = filters.toArray(new Filter[filters.size()]); | |||
} | |||
public void init(FilterConfig config) throws ServletException { | |||
@@ -83,31 +88,71 @@ abstract class SmartServiceInfoRefs implements Filter { | |||
public void doFilter(ServletRequest request, ServletResponse response, | |||
FilterChain chain) throws IOException, ServletException { | |||
final HttpServletRequest req = (HttpServletRequest) request; | |||
final HttpServletResponse rsp = (HttpServletResponse) response; | |||
if (svc.equals(req.getParameter("service"))) { | |||
final HttpServletResponse rsp = (HttpServletResponse) response; | |||
final Repository db = getRepository(req); | |||
try { | |||
final Repository db = getRepository(req); | |||
rsp.setContentType("application/x-" + svc + "-advertisement"); | |||
final SmartOutputStream buf = new SmartOutputStream(req, rsp); | |||
final PacketLineOut out = new PacketLineOut(buf); | |||
out.writeString("# service=" + svc + "\n"); | |||
out.end(); | |||
advertise(req, db, new PacketLineOutRefAdvertiser(out)); | |||
buf.close(); | |||
begin(req, db); | |||
} catch (ServiceNotAuthorizedException e) { | |||
rsp.sendError(SC_UNAUTHORIZED); | |||
return; | |||
} catch (ServiceNotEnabledException e) { | |||
rsp.sendError(SC_FORBIDDEN); | |||
return; | |||
} | |||
try { | |||
if (filters.length == 0) | |||
service(req, response); | |||
else | |||
new Chain().doFilter(request, response); | |||
} finally { | |||
req.removeAttribute(ATTRIBUTE_HANDLER); | |||
} | |||
} else { | |||
chain.doFilter(request, response); | |||
} | |||
} | |||
protected abstract void advertise(HttpServletRequest req, Repository db, | |||
private void service(ServletRequest request, ServletResponse response) | |||
throws IOException { | |||
final HttpServletRequest req = (HttpServletRequest) request; | |||
final HttpServletResponse rsp = (HttpServletResponse) response; | |||
try { | |||
rsp.setContentType("application/x-" + svc + "-advertisement"); | |||
final SmartOutputStream buf = new SmartOutputStream(req, rsp); | |||
final PacketLineOut out = new PacketLineOut(buf); | |||
out.writeString("# service=" + svc + "\n"); | |||
out.end(); | |||
advertise(req, new PacketLineOutRefAdvertiser(out)); | |||
buf.close(); | |||
} catch (ServiceNotAuthorizedException e) { | |||
rsp.sendError(SC_UNAUTHORIZED); | |||
} catch (ServiceNotEnabledException e) { | |||
rsp.sendError(SC_FORBIDDEN); | |||
} | |||
} | |||
protected abstract void begin(HttpServletRequest req, Repository db) | |||
throws IOException, ServiceNotEnabledException, | |||
ServiceNotAuthorizedException; | |||
protected abstract void advertise(HttpServletRequest req, | |||
PacketLineOutRefAdvertiser pck) throws IOException, | |||
ServiceNotEnabledException, ServiceNotAuthorizedException; | |||
private class Chain implements FilterChain { | |||
private int filterIdx; | |||
public void doFilter(ServletRequest req, ServletResponse rsp) | |||
throws IOException, ServletException { | |||
if (filterIdx < filters.length) | |||
filters[filterIdx++].doFilter(req, rsp, this); | |||
else | |||
service(req, rsp); | |||
} | |||
} | |||
} |
@@ -47,18 +47,26 @@ import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; | |||
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; | |||
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; | |||
import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE; | |||
import static org.eclipse.jgit.http.server.ServletUtils.ATTRIBUTE_HANDLER; | |||
import static org.eclipse.jgit.http.server.ServletUtils.getInputStream; | |||
import static org.eclipse.jgit.http.server.ServletUtils.getRepository; | |||
import java.io.IOException; | |||
import java.util.List; | |||
import javax.servlet.Filter; | |||
import javax.servlet.FilterChain; | |||
import javax.servlet.FilterConfig; | |||
import javax.servlet.ServletException; | |||
import javax.servlet.ServletRequest; | |||
import javax.servlet.ServletResponse; | |||
import javax.servlet.http.HttpServlet; | |||
import javax.servlet.http.HttpServletRequest; | |||
import javax.servlet.http.HttpServletResponse; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.transport.UploadPack; | |||
import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; | |||
import org.eclipse.jgit.transport.UploadPack; | |||
import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; | |||
import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; | |||
import org.eclipse.jgit.transport.resolver.UploadPackFactory; | |||
@@ -72,18 +80,27 @@ class UploadPackServlet extends HttpServlet { | |||
private static final long serialVersionUID = 1L; | |||
static class InfoRefs extends SmartServiceInfoRefs { | |||
private final UploadPackFactory uploadPackFactory; | |||
private final UploadPackFactory<HttpServletRequest> uploadPackFactory; | |||
InfoRefs(final UploadPackFactory uploadPackFactory) { | |||
super("git-upload-pack"); | |||
InfoRefs(UploadPackFactory<HttpServletRequest> uploadPackFactory, | |||
List<Filter> filters) { | |||
super("git-upload-pack", filters); | |||
this.uploadPackFactory = uploadPackFactory; | |||
} | |||
@Override | |||
protected void advertise(HttpServletRequest req, Repository db, | |||
protected void begin(HttpServletRequest req, Repository db) | |||
throws IOException, ServiceNotEnabledException, | |||
ServiceNotAuthorizedException { | |||
UploadPack up = uploadPackFactory.create(req, db); | |||
req.setAttribute(ATTRIBUTE_HANDLER, up); | |||
} | |||
@Override | |||
protected void advertise(HttpServletRequest req, | |||
PacketLineOutRefAdvertiser pck) throws IOException, | |||
ServiceNotEnabledException, ServiceNotAuthorizedException { | |||
UploadPack up = uploadPackFactory.create(req, db); | |||
UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER); | |||
try { | |||
up.sendAdvertisedRefs(pck); | |||
} finally { | |||
@@ -92,10 +109,44 @@ class UploadPackServlet extends HttpServlet { | |||
} | |||
} | |||
private final UploadPackFactory uploadPackFactory; | |||
static class Factory implements Filter { | |||
private final UploadPackFactory<HttpServletRequest> uploadPackFactory; | |||
Factory(UploadPackFactory<HttpServletRequest> uploadPackFactory) { | |||
this.uploadPackFactory = uploadPackFactory; | |||
} | |||
public void doFilter(ServletRequest request, ServletResponse response, | |||
FilterChain chain) throws IOException, ServletException { | |||
HttpServletRequest req = (HttpServletRequest) request; | |||
HttpServletResponse rsp = (HttpServletResponse) response; | |||
UploadPack rp; | |||
try { | |||
rp = uploadPackFactory.create(req, getRepository(req)); | |||
} catch (ServiceNotAuthorizedException e) { | |||
rsp.sendError(SC_UNAUTHORIZED); | |||
return; | |||
} catch (ServiceNotEnabledException e) { | |||
rsp.sendError(SC_FORBIDDEN); | |||
return; | |||
} | |||
try { | |||
req.setAttribute(ATTRIBUTE_HANDLER, rp); | |||
chain.doFilter(req, rsp); | |||
} finally { | |||
req.removeAttribute(ATTRIBUTE_HANDLER); | |||
} | |||
} | |||
public void init(FilterConfig filterConfig) throws ServletException { | |||
// Nothing. | |||
} | |||
UploadPackServlet(final UploadPackFactory uploadPackFactory) { | |||
this.uploadPackFactory = uploadPackFactory; | |||
public void destroy() { | |||
// Nothing. | |||
} | |||
} | |||
@Override | |||
@@ -106,9 +157,8 @@ class UploadPackServlet extends HttpServlet { | |||
return; | |||
} | |||
final Repository db = getRepository(req); | |||
UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER); | |||
try { | |||
final UploadPack up = uploadPackFactory.create(req, db); | |||
up.setBiDirectionalPipe(false); | |||
rsp.setContentType(RSP_TYPE); | |||
@@ -121,16 +171,6 @@ class UploadPackServlet extends HttpServlet { | |||
up.upload(getInputStream(req), out, null); | |||
out.close(); | |||
} catch (ServiceNotAuthorizedException e) { | |||
rsp.reset(); | |||
rsp.sendError(SC_UNAUTHORIZED); | |||
return; | |||
} catch (ServiceNotEnabledException e) { | |||
rsp.reset(); | |||
rsp.sendError(SC_FORBIDDEN); | |||
return; | |||
} catch (IOException e) { | |||
getServletContext().log(HttpServerText.get().internalErrorDuringUploadPack, e); | |||
rsp.reset(); |