Skip to content

Commit 3c3215c

Browse files
committed
assets: custom response when asset isn't found fix #3501
1 parent ffc72e6 commit 3c3215c

File tree

3 files changed

+102
-7
lines changed

3 files changed

+102
-7
lines changed

docs/asciidoc/static-files.adoc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,3 +221,38 @@ specify a function via javadoc:AssetHandler[cacheControl, java.util.Function]:
221221
})
222222
}
223223
----
224+
225+
The asset handler generates a `404` response code when requested path is not found. You can change this by throwing
226+
an exception or generating any other content you want:
227+
228+
229+
.Custom not found:
230+
[source, java, role="primary"]
231+
----
232+
{
233+
AssetSource www = AssetSource.create(Paths.get("www"));
234+
assets("/static/*", new AssetHandler(www)
235+
.notFound(ctx -> {
236+
throw new MyAssetException();
237+
}));
238+
239+
error(MyAssetException.class, (ctx, cause, code) -> {
240+
// render MyAssetException as you want
241+
});
242+
}
243+
----
244+
245+
.Kotlin
246+
[source, kotlin, role="secondary"]
247+
----
248+
{
249+
val www = AssetSource.create(Paths.get("www"))
250+
assets("/static/*", AssetHandler(www)
251+
.notFound { _ ->
252+
throw MyAssetException()
253+
})
254+
error(MyAssetException::class) {
255+
// render MyAssetException as you want
256+
}
257+
}
258+
----

jooby/src/main/java/io/jooby/handler/AssetHandler.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,8 @@
1414
import java.util.function.Function;
1515

1616
import edu.umd.cs.findbugs.annotations.NonNull;
17-
import io.jooby.Context;
18-
import io.jooby.MediaType;
19-
import io.jooby.Route;
20-
import io.jooby.StatusCode;
17+
import edu.umd.cs.findbugs.annotations.Nullable;
18+
import io.jooby.*;
2119

2220
/**
2321
* Handler for static resources represented by the {@link Asset} contract.
@@ -28,6 +26,8 @@
2826
* @since 2.0.0
2927
*/
3028
public class AssetHandler implements Route.Handler {
29+
private static final SneakyThrows.Consumer<Context> NOT_FOUND =
30+
ctx -> ctx.send(StatusCode.NOT_FOUND);
3131
private static final int ONE_SEC = 1000;
3232

3333
private final AssetSource[] sources;
@@ -41,6 +41,7 @@ public class AssetHandler implements Route.Handler {
4141
private Function<String, CacheControl> cacheControl = path -> defaults;
4242

4343
private Function<Asset, MediaType> mediaTypeResolver = Asset::getContentType;
44+
private SneakyThrows.Consumer<Context> notFound = NOT_FOUND;
4445

4546
/**
4647
* Creates a new asset handler that fallback to the given fallback asset when the asset is not
@@ -82,7 +83,7 @@ public Object apply(@NonNull Context ctx) throws Exception {
8283
}
8384
// Still null?
8485
if (asset == null) {
85-
ctx.send(StatusCode.NOT_FOUND);
86+
notFound.accept(ctx);
8687
return ctx;
8788
} else {
8889
resolvedPath = fallback;
@@ -230,7 +231,19 @@ public AssetHandler cacheControl(@NonNull Function<String, CacheControl> cacheCo
230231
return this;
231232
}
232233

233-
private Asset resolve(String filepath) {
234+
/**
235+
* Sets a custom handler for <code>404</code> asset/resource. By default, generates a <code>404
236+
* </code> status code response.
237+
*
238+
* @param handler Handler.
239+
* @return This handler.
240+
*/
241+
public AssetHandler notFound(@NonNull SneakyThrows.Consumer<Context> handler) {
242+
this.notFound = handler;
243+
return this;
244+
}
245+
246+
private @Nullable Asset resolve(String filepath) {
234247
for (AssetSource source : sources) {
235248
Asset asset = source.resolve(filepath);
236249
if (asset != null) {
@@ -243,7 +256,7 @@ private Asset resolve(String filepath) {
243256
@Override
244257
public void setRoute(Route route) {
245258
List<String> keys = route.getPathKeys();
246-
this.filekey = keys.size() == 0 ? route.getPattern().substring(1) : keys.get(0);
259+
this.filekey = keys.isEmpty() ? route.getPattern().substring(1) : keys.get(0);
247260
// NOTE: It send an inputstream we don't need a renderer
248261
route.setReturnType(Context.class);
249262
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.i3501;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
10+
import io.jooby.handler.AssetHandler;
11+
import io.jooby.handler.AssetSource;
12+
import io.jooby.junit.ServerTest;
13+
import io.jooby.junit.ServerTestRunner;
14+
15+
public class Issue3501 {
16+
17+
@ServerTest
18+
public void assetHandlerShouldGenerateCustom404Response(ServerTestRunner runner) {
19+
runner
20+
.define(
21+
app -> {
22+
app.assets(
23+
"/issue3501/*",
24+
new AssetHandler(AssetSource.create(getClass().getClassLoader(), "/static"))
25+
.notFound(
26+
ctx -> {
27+
throw new UnsupportedOperationException();
28+
}));
29+
30+
app.error(
31+
UnsupportedOperationException.class,
32+
((ctx, cause, code) -> {
33+
ctx.send(cause.getClass().getName());
34+
}));
35+
})
36+
.ready(
37+
http -> {
38+
http.get(
39+
"/issue3501/index.js",
40+
rsp -> {
41+
assertEquals(
42+
UnsupportedOperationException.class.getName(), rsp.body().string());
43+
assertEquals(500, rsp.code());
44+
});
45+
});
46+
}
47+
}

0 commit comments

Comments
 (0)