Browse Source

Allow application filters on smart HTTP operations

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
Shawn O. Pearce 13 years ago
parent
commit
af3562f7f7

+ 44
- 9
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java View File

@@ -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());

+ 61
- 20
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java View File

@@ -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);

+ 3
- 0
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java View File

@@ -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.
*

+ 58
- 13
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java View File

@@ -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);
}
}
}

+ 62
- 22
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java View File

@@ -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();

Loading…
Cancel
Save