Skip to content

Commit

Permalink
Fix for #535. Static files in jar
Browse files Browse the repository at this point in the history
  • Loading branch information
perwendel committed May 2, 2016
1 parent e0ccffd commit 04d0563
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 6 deletions.
8 changes: 4 additions & 4 deletions src/main/java/spark/http/matching/MatcherFilter.java
Expand Up @@ -47,7 +47,7 @@ public class MatcherFilter implements Filter {
private static final String ACCEPT_TYPE_REQUEST_MIME_HEADER = "Accept";
private static final String HTTP_METHOD_OVERRIDE_HEADER = "X-HTTP-Method-Override";

private final StaticFilesConfiguration staticFilesConfiguration;
private final StaticFilesConfiguration staticFiles;

private spark.route.Routes routeMatcher;
private SerializerChain serializerChain;
Expand All @@ -64,12 +64,12 @@ public class MatcherFilter implements Filter {
* @param hasOtherHandlers If true, do nothing if request is not consumed by Spark in order to let others handlers process the request.
*/
public MatcherFilter(spark.route.Routes routeMatcher,
StaticFilesConfiguration staticFilesConfiguration,
StaticFilesConfiguration staticFiles,
boolean externalContainer,
boolean hasOtherHandlers) {

this.routeMatcher = routeMatcher;
this.staticFilesConfiguration = staticFilesConfiguration;
this.staticFiles = staticFiles;
this.externalContainer = externalContainer;
this.hasOtherHandlers = hasOtherHandlers;
this.serializerChain = new SerializerChain();
Expand All @@ -88,7 +88,7 @@ public void doFilter(ServletRequest servletRequest,
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;

// handle static resources
boolean consumedByStaticFile = staticFilesConfiguration.consume(httpRequest, httpResponse);
boolean consumedByStaticFile = staticFiles.consume(httpRequest, httpResponse);

if (consumedByStaticFile) {
return;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/spark/resource/AbstractResourceHandler.java
Expand Up @@ -77,7 +77,7 @@ public AbstractFileResolvingResource getResource(HttpServletRequest request) thr
* @param segment2 URI path segment (should be encoded)
* @return Legally combined path segments.
*/
protected static String addPaths(String segment1, String segment2) {
public static String addPaths(String segment1, String segment2) {
if (segment1 == null || segment1.length() == 0) {
if (segment1 != null && segment2 == null) {
return segment1;
Expand Down
120 changes: 120 additions & 0 deletions src/main/java/spark/resource/JarResourceHandler.java
@@ -0,0 +1,120 @@
/*
* Copyright 2016 - Per Wendel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package spark.resource;

import java.io.InputStream;
import java.net.MalformedURLException;

import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import spark.utils.Assert;

import static spark.resource.AbstractResourceHandler.addPaths;

/**
* Locates resources in jar file
*/
public class JarResourceHandler {

private static final Logger LOG = LoggerFactory.getLogger(JarResourceHandler.class);

private final String baseResource;
private String welcomeFile;

/**
* Constructor
*
* @param baseResource the base resource path
* @param welcomeFile the welcomeFile
*/
public JarResourceHandler(String baseResource, String welcomeFile) {
Assert.notNull(baseResource);

this.baseResource = baseResource;
this.welcomeFile = welcomeFile;
}

public InputStream getResource(HttpServletRequest request) throws MalformedURLException {

String servletPath;
String pathInfo;

boolean included = request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null;

if (included) {
servletPath = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
pathInfo = (String) request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);

if (servletPath == null && pathInfo == null) {
servletPath = request.getServletPath();
pathInfo = request.getPathInfo();
}
} else {
servletPath = request.getServletPath();
pathInfo = request.getPathInfo();
}

String pathInContext = addPaths(servletPath, pathInfo);
return getResourceStream(pathInContext);
}

private InputStream getResourceStream(String path) throws MalformedURLException {
if (path == null || !path.startsWith("/")) {
throw new MalformedURLException(path);
}

InputStream resourceStream = null;

try {
path = UriPath.canonical(path);
path = addPaths(baseResource, path);

if (isDirectory(path)) {
if (welcomeFileConfigured()) {
path = addPaths(path, welcomeFile);
resourceStream = loadStream(path);
}
} else {
resourceStream = loadStream(path);
}

} catch (Exception e) {
if (LOG.isDebugEnabled()) {
LOG.debug(e.getClass().getSimpleName() + " when trying to get resource. " + e.getMessage());
}
}

return resourceStream;
}

private InputStream loadStream(String path) {
return JarResourceHandler.class.getResourceAsStream(path);
}

private boolean welcomeFileConfigured() {
return welcomeFile != null;
}

private boolean isDirectory(String path) {
return path.endsWith("/");
}

}
80 changes: 79 additions & 1 deletion src/main/java/spark/staticfiles/StaticFilesConfiguration.java
@@ -1,5 +1,5 @@
/*
* Copyright 2015 - Per Wendel
* Copyright 2016 - Per Wendel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,7 @@
package spark.staticfiles;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
Expand All @@ -36,6 +37,7 @@
import spark.resource.ClassPathResourceHandler;
import spark.resource.ExternalResource;
import spark.resource.ExternalResourceHandler;
import spark.resource.JarResourceHandler;
import spark.utils.Assert;
import spark.utils.GzipUtils;
import spark.utils.IOUtils;
Expand All @@ -48,6 +50,7 @@ public class StaticFilesConfiguration {
private final Logger LOG = LoggerFactory.getLogger(StaticFilesConfiguration.class);

private List<AbstractResourceHandler> staticResourceHandlers = null;
private List<JarResourceHandler> jarResourceHandlers = null;

private boolean staticResourcesSet = false;
private boolean externalStaticResourcesSet = false;
Expand All @@ -62,9 +65,26 @@ public class StaticFilesConfiguration {
public boolean consume(HttpServletRequest httpRequest,
HttpServletResponse httpResponse) throws IOException {

if (consumeWithFileResourceHandlers(httpRequest, httpResponse)) {
return true;
}

if (consumeWithJarResourceHandler(httpRequest, httpResponse)) {
return true;
}

return false;
}


private boolean consumeWithFileResourceHandlers(HttpServletRequest httpRequest,
HttpServletResponse httpResponse) throws IOException {
if (staticResourceHandlers != null) {

for (AbstractResourceHandler staticResourceHandler : staticResourceHandlers) {

AbstractFileResolvingResource resource = staticResourceHandler.getResource(httpRequest);

if (resource != null && resource.isReadable()) {
OutputStream wrappedOutputStream = GzipUtils.checkAndWrap(httpRequest, httpResponse, false);
customHeaders.forEach(httpResponse::setHeader); //add all user-defined headers to response
Expand All @@ -74,19 +94,49 @@ public boolean consume(HttpServletRequest httpRequest,
return true;
}
}

}
return false;
}

private boolean consumeWithJarResourceHandler(HttpServletRequest httpRequest,
HttpServletResponse httpResponse) throws IOException {
if (jarResourceHandlers != null) {

for (JarResourceHandler jarResourceHandler : jarResourceHandlers) {
InputStream stream = jarResourceHandler.getResource(httpRequest);

if (stream != null) {
OutputStream wrappedOutputStream = GzipUtils.checkAndWrap(httpRequest, httpResponse, false);
customHeaders.forEach(httpResponse::setHeader); //add all user-defined headers to response

IOUtils.copy(stream, wrappedOutputStream);

wrappedOutputStream.flush();
wrappedOutputStream.close();

return true;
}
}
}
return false;
}

/**
* Clears all static file configuration
*/
public void clear() {

if (staticResourceHandlers != null) {
staticResourceHandlers.clear();
staticResourceHandlers = null;
}

if (jarResourceHandlers != null) {
jarResourceHandlers.clear();
jarResourceHandlers = null;
}

staticResourcesSet = false;
externalStaticResourcesSet = false;
}
Expand All @@ -102,6 +152,11 @@ public synchronized void configure(String folder) {
if (!staticResourcesSet) {
try {
ClassPathResource resource = new ClassPathResource(folder);

if (configureJarCase(folder, resource)) {
return;
}

if (!resource.getFile().isDirectory()) {
LOG.error("Static resource location must be a folder");
return;
Expand All @@ -110,6 +165,7 @@ public synchronized void configure(String folder) {
if (staticResourceHandlers == null) {
staticResourceHandlers = new ArrayList<>();
}

staticResourceHandlers.add(new ClassPathResourceHandler(folder, "index.html"));
LOG.info("StaticResourceHandler configured with folder = " + folder);
} catch (IOException e) {
Expand All @@ -120,6 +176,28 @@ public synchronized void configure(String folder) {

}

private boolean configureJarCase(String folder, ClassPathResource resource) throws IOException {
if (resource.getURL().getProtocol().equals("jar")) {

InputStream stream = StaticFilesConfiguration.class.getResourceAsStream(folder);

if (stream != null) {
if (jarResourceHandlers == null) {
jarResourceHandlers = new ArrayList<>();
}

// Add jar file resource handler
jarResourceHandlers.add(new JarResourceHandler(folder, "index.html"));
staticResourcesSet = true;
return true;
} else {
LOG.error("Static file configuration failed.");
}

}
return false;
}

/**
* Configures location for static resources
*
Expand Down

0 comments on commit 04d0563

Please sign in to comment.