-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cache request body as soon as possible in case somebody reads it befo…
…re vinz
- Loading branch information
Showing
6 changed files
with
292 additions
and
103 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# config can be in parent dir (I think this is already default) | ||
config.stopBubbling=false | ||
|
||
# setters return this | ||
lombok.accessors.chain=true | ||
|
||
lombok.copyableAnnotations += javax.inject.Named | ||
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Value | ||
lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier |
188 changes: 188 additions & 0 deletions
188
src/main/java/cogni/zone/vinzclortho/CacheBodyFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
package cogni.zone.vinzclortho; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.apache.commons.io.FileUtils; | ||
import org.apache.commons.io.IOUtils; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import javax.servlet.Filter; | ||
import javax.servlet.FilterChain; | ||
import javax.servlet.ReadListener; | ||
import javax.servlet.ServletException; | ||
import javax.servlet.ServletInputStream; | ||
import javax.servlet.ServletRequest; | ||
import javax.servlet.ServletResponse; | ||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletRequestWrapper; | ||
import java.io.ByteArrayInputStream; | ||
import java.io.File; | ||
import java.io.FileInputStream; | ||
import java.io.FileNotFoundException; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.function.Supplier; | ||
|
||
@RequiredArgsConstructor | ||
public class CacheBodyFilter implements Filter { | ||
private static final Logger log = LoggerFactory.getLogger(CacheBodyFilter.class); | ||
|
||
public static final String bodyContentProviderAttributeKey = "cogni.zone.vinzclortho.CacheBodyFilter.bodyContentProvider"; | ||
public static final String bodySizeProviderAttributeKey = "cogni.zone.vinzclortho.CacheBodyFilter.bodySizeProvider"; | ||
|
||
private static final byte[] emptyByteArray = new byte[0]; | ||
private static final int maxBytesInMemory = 1024 << 10; //1MB (I just alt-entered...) | ||
|
||
private final RouteConfigurationService routeConfigurationService; | ||
|
||
@Override | ||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { | ||
HttpServletRequest httpRequest = (HttpServletRequest) request; | ||
if (routeConfigurationService.hasMatchingRoute(httpRequest)) { | ||
//We patch the request because vinz is nice and lets everybody pass before him | ||
// But not everybody is as nice as vinz, and it might be that the body of the request is already consumed when we get to vinz (foei!) | ||
log.debug("Vinz matches, patching request"); | ||
doPatchFilter(httpRequest, response, chain); | ||
} | ||
else { | ||
log.debug("No vinz"); | ||
chain.doFilter(request, response); | ||
} | ||
} | ||
private void doPatchFilter(HttpServletRequest httpRequest, ServletResponse response, FilterChain chain) throws ServletException, IOException { | ||
Runnable cleanupCall = null; | ||
InputStream wrappedBodyInputStream; | ||
Supplier<InputStream> cachedBodyInputStreamProvider; | ||
int contentLength = httpRequest.getContentLength(); | ||
long realContentLength; | ||
if (-1 == contentLength || contentLength > maxBytesInMemory) { | ||
List<InputStream> toClose = new ArrayList<>(); | ||
File tempFile = File.createTempFile("vinzClortho", "bodyCache.dat"); | ||
FileUtils.copyToFile(httpRequest.getInputStream(), tempFile); | ||
cleanupCall = () -> cleanup(toClose, tempFile); | ||
cachedBodyInputStreamProvider = () -> getInputStreamForFile(toClose, tempFile); | ||
wrappedBodyInputStream = getInputStreamForFile(toClose, tempFile); | ||
realContentLength = tempFile.length(); | ||
} | ||
else { | ||
byte[] bytes = 0 == contentLength ? emptyByteArray : IOUtils.toByteArray(httpRequest.getInputStream()); | ||
cachedBodyInputStreamProvider = () -> new ByteArrayInputStream(bytes); | ||
wrappedBodyInputStream = new ByteArrayInputStream(bytes); | ||
realContentLength = bytes.length; | ||
} | ||
|
||
httpRequest.setAttribute(bodyContentProviderAttributeKey, cachedBodyInputStreamProvider); | ||
httpRequest.setAttribute(bodySizeProviderAttributeKey, realContentLength); | ||
HttpServletRequest wrapper = new VinzServletRequestWrapper(httpRequest, new BodyInputStream(wrappedBodyInputStream)); | ||
|
||
try { | ||
chain.doFilter(wrapper, response); | ||
} | ||
finally { | ||
if (null != cleanupCall) cleanupCall.run(); | ||
} | ||
} | ||
|
||
private InputStream getInputStreamForFile(List<InputStream> toClose, File tempFile) { | ||
try { | ||
InputStream inputStream = new FileInputStream(tempFile); | ||
toClose.add(inputStream); | ||
return inputStream; | ||
} | ||
catch (FileNotFoundException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private void cleanup(List<InputStream> inputStreams, File file) { | ||
inputStreams.forEach(IOUtils::closeQuietly); | ||
if (!file.delete()) file.deleteOnExit(); | ||
} | ||
|
||
private static class VinzServletRequestWrapper extends HttpServletRequestWrapper { | ||
private final BodyInputStream bodyInputStream; | ||
|
||
private VinzServletRequestWrapper(HttpServletRequest request, BodyInputStream bodyInputStream) { | ||
super(request); | ||
this.bodyInputStream = bodyInputStream; | ||
} | ||
|
||
@Override | ||
public ServletInputStream getInputStream() throws IOException { | ||
return bodyInputStream; | ||
} | ||
} | ||
|
||
private static class BodyInputStream extends ServletInputStream { | ||
|
||
private final InputStream inputStream; | ||
|
||
private BodyInputStream(InputStream inputStream) { | ||
this.inputStream = inputStream; | ||
} | ||
|
||
@Override | ||
public boolean isFinished() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public boolean isReady() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public void setReadListener(ReadListener listener) { | ||
throw new UnsupportedOperationException("Nop"); | ||
} | ||
|
||
@Override | ||
public int read() throws IOException { | ||
return inputStream.read(); | ||
} | ||
|
||
@Override | ||
public int read(byte[] bytes, int off, int len) throws IOException { | ||
return inputStream.read(bytes, off, len); | ||
} | ||
|
||
@Override | ||
public int read(byte[] bytes) throws IOException { | ||
return inputStream.read(bytes); | ||
} | ||
|
||
@Override | ||
public long skip(long byteCount) throws IOException { | ||
return inputStream.skip(byteCount); | ||
} | ||
|
||
@Override | ||
public int available() throws IOException { | ||
return inputStream.available(); | ||
} | ||
|
||
@Override | ||
public void close() throws IOException { | ||
inputStream.close(); | ||
} | ||
|
||
@Override | ||
public synchronized void mark(int readlimit) { | ||
inputStream.mark(readlimit); | ||
} | ||
|
||
@Override | ||
public synchronized void reset() throws IOException { | ||
inputStream.reset(); | ||
} | ||
|
||
@Override | ||
public boolean markSupported() { | ||
return inputStream.markSupported(); | ||
} | ||
} | ||
|
||
|
||
} |
45 changes: 45 additions & 0 deletions
45
src/main/java/cogni/zone/vinzclortho/RouteConfigurationService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package cogni.zone.vinzclortho; | ||
|
||
import lombok.Data; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.util.AntPathMatcher; | ||
|
||
import javax.servlet.http.HttpServletRequest; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
@RequiredArgsConstructor | ||
@Slf4j | ||
public class RouteConfigurationService { | ||
|
||
private final Configuration config; | ||
|
||
public boolean hasMatchingRoute(HttpServletRequest httpRequest) { | ||
return findRoute(httpRequest).isPresent(); | ||
} | ||
|
||
public Optional<Route> findRoute(HttpServletRequest httpRequest) { | ||
return config.getRoutes().stream() | ||
.filter(route -> matches(route, httpRequest)) | ||
.findFirst(); | ||
} | ||
|
||
private boolean matches(Route route, HttpServletRequest request) { | ||
return new AntPathMatcher().match(route.getPath(), request.getServletPath()); | ||
} | ||
|
||
@Data | ||
public static class Configuration { | ||
private List<Route> routes = Collections.synchronizedList(new ArrayList<>()); | ||
} | ||
|
||
@Data | ||
public static class Route { | ||
private String name; | ||
private String path; | ||
private String url; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.