Skip to content

Commit 1397663

Browse files
committed
assets: resolve missing asset as 404 fix #1105
1 parent d0fc71a commit 1397663

File tree

7 files changed

+209
-41
lines changed

7 files changed

+209
-41
lines changed

doc/routes.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,17 @@ assets.cdn = "http://d7471vfo50fqt.cloudfront.net"
241241
A ```GET``` to ```/assets/js/index.js``` will be redirected to: ```http://d7471vfo50fqt.cloudfront.net/assets/js/index.js```
242242
243243
244+
All these assets features are controlled globally from `.conf` file or individually:
245+
246+
```java
247+
{
248+
assets("/assets/**")
249+
.etag(true)
250+
.cdn("http://d7471vfo50fqt.cloudfront.net")
251+
.maxAge("365d");
252+
}
253+
```
254+
244255
### assets module
245256
246257
There is also a powerful and all-round awesome [assets](/doc/assets) module. This module can validate, concatenate, minify or compress JavaScript and CSS assets.

jooby/src/main/java/org/jooby/Jooby.java

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1922,22 +1922,19 @@ private Route.Filter filter(final Class<? extends Route.Filter> filter) {
19221922
}
19231923

19241924
@Override
1925-
public Definition assets(final String path, final Path basedir) {
1926-
AssetHandler handler = new AssetHandler(basedir);
1927-
configureAssetHandler(handler);
1928-
return assets(path, handler);
1925+
public Route.AssetDefinition assets(final String path, final Path basedir) {
1926+
return assets(path, new AssetHandler(basedir));
19291927
}
19301928

19311929
@Override
1932-
public Route.Definition assets(final String path, final String location) {
1933-
AssetHandler handler = new AssetHandler(location);
1934-
configureAssetHandler(handler);
1935-
return assets(path, handler);
1930+
public Route.AssetDefinition assets(final String path, final String location) {
1931+
return assets(path, new AssetHandler(location));
19361932
}
19371933

19381934
@Override
1939-
public Route.Definition assets(final String path, final AssetHandler handler) {
1940-
return appendDefinition(GET, path, handler);
1935+
public Route.AssetDefinition assets(final String path, final AssetHandler handler) {
1936+
Route.AssetDefinition route = appendDefinition(GET, path, handler, Route.AssetDefinition::new);
1937+
return configureAssetHandler(route);
19411938
}
19421939

19431940
@Override
@@ -1963,9 +1960,22 @@ public Route.Collection use(final String path, final Class<?> routeClass) {
19631960
* @return The same route definition.
19641961
*/
19651962
private Route.Definition appendDefinition(String method, String pattern, Route.Filter filter) {
1963+
return appendDefinition(method, pattern, filter, Route.Definition::new);
1964+
}
1965+
1966+
/**
1967+
* Keep track of routes in the order user define them.
1968+
*
1969+
* @param method Route method.
1970+
* @param pattern Route pattern.
1971+
* @param filter Route filter.
1972+
* @param creator Route creator.
1973+
* @return The same route definition.
1974+
*/
1975+
private <T extends Route.Definition> T appendDefinition(String method, String pattern,
1976+
Route.Filter filter, Throwing.Function4<String, String, Route.Filter, Boolean, T> creator) {
19661977
String pathPattern = prefixPath(pattern).orElse(pattern);
1967-
Route.Definition route = new Route.Definition(method, pathPattern, filter,
1968-
caseSensitiveRouting);
1978+
T route = creator.apply(method, pathPattern, filter, caseSensitiveRouting);
19691979
if (prefix != null) {
19701980
route.prefix = prefix;
19711981
// reset name will update the name if prefix != null
@@ -3493,7 +3503,7 @@ private static Logger logger(final Jooby app) {
34933503
return LoggerFactory.getLogger(app.getClass());
34943504
}
34953505

3496-
public void configureAssetHandler(final AssetHandler handler) {
3506+
private Route.AssetDefinition configureAssetHandler(final Route.AssetDefinition handler) {
34973507
onStart(r -> {
34983508
Config conf = r.require(Config.class);
34993509
handler
@@ -3502,6 +3512,7 @@ public void configureAssetHandler(final AssetHandler handler) {
35023512
.etag(conf.getBoolean("assets.etag"))
35033513
.maxAge(conf.getString("assets.cache.maxAge"));
35043514
});
3515+
return handler;
35053516
}
35063517

35073518
/**

jooby/src/main/java/org/jooby/Route.java

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,17 +213,19 @@
213213
import com.google.inject.Key;
214214
import com.google.inject.TypeLiteral;
215215
import static java.util.Objects.requireNonNull;
216+
import org.jooby.funzy.Throwing;
217+
import org.jooby.handlers.AssetHandler;
216218
import org.jooby.internal.RouteImpl;
217219
import org.jooby.internal.RouteMatcher;
218220
import org.jooby.internal.RoutePattern;
219221
import org.jooby.internal.RouteSourceImpl;
220222
import org.jooby.internal.SourceProvider;
221-
import org.jooby.funzy.Throwing;
222223

223224
import javax.annotation.Nonnull;
224225
import javax.annotation.Nullable;
225226
import java.lang.reflect.Array;
226227
import java.lang.reflect.Method;
228+
import java.time.Duration;
227229
import java.util.ArrayList;
228230
import java.util.Arrays;
229231
import java.util.Collections;
@@ -2050,7 +2052,136 @@ public interface Filter {
20502052
* @throws Throwable If something goes wrong.
20512053
*/
20522054
void handle(Request req, Response rsp, Route.Chain chain) throws Throwable;
2055+
}
2056+
2057+
/**
2058+
* Allow to customize an asset handler.
2059+
*
2060+
* @author edgar
2061+
*/
2062+
class AssetDefinition extends Definition {
2063+
2064+
private Boolean etag;
2065+
2066+
private String cdn;
2067+
2068+
private Object maxAge;
2069+
2070+
private Boolean lastModifiedSince;
2071+
2072+
private Integer statusCode;
2073+
2074+
/**
2075+
* Creates a new route definition.
2076+
*
2077+
* @param method A HTTP verb or <code>*</code>.
2078+
* @param pattern A path pattern.
2079+
* @param handler A callback to execute.
2080+
* @param caseSensitiveRouting Configure case for routing algorithm.
2081+
*/
2082+
public AssetDefinition(final String method, final String pattern,
2083+
final Route.Filter handler, boolean caseSensitiveRouting) {
2084+
super(method, pattern, handler, caseSensitiveRouting);
2085+
}
2086+
2087+
@Nonnull
2088+
@Override
2089+
public AssetHandler filter() {
2090+
return (AssetHandler) super.filter();
2091+
}
2092+
2093+
/**
2094+
* Indicates what to do when an asset is missing (not resolved). Default action is to resolve them
2095+
* as <code>404 (NOT FOUND)</code> request.
2096+
*
2097+
* If you specify a status code <= 0, missing assets are ignored and the next handler on pipeline
2098+
* will be executed.
2099+
*
2100+
* @param statusCode HTTP code or 0.
2101+
* @return This route definition.
2102+
*/
2103+
public AssetDefinition onMissing(final int statusCode) {
2104+
if (this.statusCode == null) {
2105+
filter().onMissing(statusCode);
2106+
this.statusCode = statusCode;
2107+
}
2108+
return this;
2109+
}
2110+
2111+
/**
2112+
* @param etag Turn on/off etag support.
2113+
* @return This route definition.
2114+
*/
2115+
public AssetDefinition etag(final boolean etag) {
2116+
if (this.etag == null) {
2117+
filter().etag(etag);
2118+
this.etag = etag;
2119+
}
2120+
return this;
2121+
}
2122+
2123+
/**
2124+
* @param enabled Turn on/off last modified support.
2125+
* @return This route definition.
2126+
*/
2127+
public AssetDefinition lastModified(final boolean enabled) {
2128+
if (this.lastModifiedSince == null) {
2129+
filter().lastModified(enabled);
2130+
this.lastModifiedSince = enabled;
2131+
}
2132+
return this;
2133+
}
20532134

2135+
/**
2136+
* @param cdn If set, every resolved asset will be serve from it.
2137+
* @return This route definition.
2138+
*/
2139+
public AssetDefinition cdn(final String cdn) {
2140+
if (this.cdn == null) {
2141+
filter().cdn(cdn);
2142+
this.cdn = cdn;
2143+
}
2144+
return this;
2145+
}
2146+
2147+
/**
2148+
* @param maxAge Set the cache header max-age value.
2149+
* @return This route definition.
2150+
*/
2151+
public AssetDefinition maxAge(final Duration maxAge) {
2152+
if (this.maxAge == null) {
2153+
filter().maxAge(maxAge);
2154+
this.maxAge = maxAge;
2155+
}
2156+
return this;
2157+
}
2158+
2159+
/**
2160+
* @param maxAge Set the cache header max-age value in seconds.
2161+
* @return This route definition.
2162+
*/
2163+
public AssetDefinition maxAge(final long maxAge) {
2164+
if (this.maxAge == null) {
2165+
filter().maxAge(maxAge);
2166+
this.maxAge = maxAge;
2167+
}
2168+
return this;
2169+
}
2170+
2171+
/**
2172+
* Parse value as {@link Duration}. If the value is already a number then it uses as seconds.
2173+
* Otherwise, it parse expressions like: 8m, 1h, 365d, etc...
2174+
*
2175+
* @param maxAge Set the cache header max-age value in seconds.
2176+
* @return This route definition.
2177+
*/
2178+
public AssetDefinition maxAge(final String maxAge) {
2179+
if (this.maxAge == null) {
2180+
filter().maxAge(maxAge);
2181+
this.maxAge = maxAge;
2182+
}
2183+
return this;
2184+
}
20542185
}
20552186

20562187
/**

jooby/src/main/java/org/jooby/Router.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -203,24 +203,20 @@
203203
*/
204204
package org.jooby;
205205

206+
import org.jooby.Route.Mapper;
207+
import org.jooby.funzy.Try;
208+
import org.jooby.handlers.AssetHandler;
209+
210+
import javax.annotation.Nonnull;
206211
import java.net.URLDecoder;
207212
import java.nio.file.Path;
208213
import java.util.ArrayList;
209214
import java.util.Arrays;
210215
import java.util.List;
211216
import java.util.Optional;
212217
import java.util.concurrent.Executor;
213-
import java.util.function.Consumer;
214218
import java.util.function.Predicate;
215219

216-
import com.google.common.escape.Escaper;
217-
import com.google.common.net.PercentEscaper;
218-
import org.jooby.Route.Mapper;
219-
import org.jooby.funzy.Try;
220-
import org.jooby.handlers.AssetHandler;
221-
222-
import javax.annotation.Nonnull;
223-
224220
/**
225221
* Route DSL. Constructs and creates several flavors of jooby routes.
226222
*
@@ -2009,7 +2005,7 @@ default Route.Definition delete(Route.ZeroArgHandler handler) {
20092005
* @return A new route definition.
20102006
*/
20112007
@Nonnull
2012-
default Route.Definition assets(final String path) {
2008+
default Route.AssetDefinition assets(final String path) {
20132009
return assets(path, "/");
20142010
}
20152011

@@ -2043,7 +2039,7 @@ default Route.Definition assets(final String path) {
20432039
* @return A new route definition.
20442040
*/
20452041
@Nonnull
2046-
Route.Definition assets(final String path, Path basedir);
2042+
Route.AssetDefinition assets(final String path, Path basedir);
20472043

20482044
/**
20492045
* Static files handler. Like {@link #assets(String)} but let you specify a different classpath
@@ -2089,7 +2085,7 @@ default Route.Definition assets(final String path) {
20892085
* @return A new route definition.
20902086
*/
20912087
@Nonnull
2092-
Route.Definition assets(String path, String location);
2088+
Route.AssetDefinition assets(String path, String location);
20932089

20942090
/**
20952091
* Send static files, like {@link #assets(String)} but let you specify a custom
@@ -2100,7 +2096,7 @@ default Route.Definition assets(final String path) {
21002096
* @return A new route definition.
21012097
*/
21022098
@Nonnull
2103-
Route.Definition assets(String path, AssetHandler handler);
2099+
Route.AssetDefinition assets(String path, AssetHandler handler);
21042100

21052101
/**
21062102
* <p>

jooby/src/main/java/org/jooby/handlers/AssetHandler.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@
208208
import com.typesafe.config.ConfigValueFactory;
209209
import static java.util.Objects.requireNonNull;
210210
import org.jooby.Asset;
211+
import org.jooby.Err;
211212
import org.jooby.Jooby;
212213
import org.jooby.MediaType;
213214
import org.jooby.Request;
@@ -284,6 +285,8 @@ private interface Loader {
284285

285286
private boolean lastModified = true;
286287

288+
private int statusCode = 404;
289+
287290
/**
288291
* <p>
289292
* Creates a new {@link AssetHandler}. The handler accepts a location pattern, that serve for
@@ -436,6 +439,21 @@ public AssetHandler maxAge(final String maxAge) {
436439
return this;
437440
}
438441

442+
/**
443+
* Indicates what to do when an asset is missing (not resolved). Default action is to resolve them
444+
* as <code>404 (NOT FOUND)</code> request.
445+
*
446+
* If you specify a status code <= 0, missing assets are ignored and the next handler on pipeline
447+
* will be executed.
448+
*
449+
* @param statusCode HTTP code or 0.
450+
* @return This handler.
451+
*/
452+
public AssetHandler onMissing(final int statusCode) {
453+
this.statusCode = statusCode;
454+
return this;
455+
}
456+
439457
@Override
440458
public void handle(final Request req, final Response rsp) throws Throwable {
441459
String path = req.path();
@@ -461,6 +479,8 @@ public void handle(final Request req, final Response rsp) throws Throwable {
461479
doHandle(req, rsp, asset);
462480
}
463481
}
482+
} else if (statusCode > 0) {
483+
throw new Err(statusCode);
464484
}
465485
}
466486

modules/coverage-report/src/test/java/org/jooby/AssetResolveNextFeature.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
public class AssetResolveNextFeature extends ServerFeature {
77

88
{
9-
assets("/assets/**", "/");
9+
assets("/assets/**", "/")
10+
.onMissing(0);
1011

1112
assets("/assets/js/*-*.js", "/META-INF/resources/webjars/{0}/{1}/{0}.js");
1213
}

0 commit comments

Comments
 (0)