Skip to content

Commit 9864949

Browse files
committed
Use a zero-copy string for routing
1 parent c03126d commit 9864949

File tree

9 files changed

+458
-165
lines changed

9 files changed

+458
-165
lines changed

jooby/src/main/java/io/jooby/internal/$Chi.java

Lines changed: 288 additions & 124 deletions
Large diffs are not rendered by default.

jooby/src/main/java/io/jooby/internal/RouterMatch.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ public void key(List<String> keys) {
3333
}
3434
}
3535

36-
public void value(String value) {
36+
public void value($Chi.ZeroCopyString value) {
3737
if (vars == Collections.EMPTY_MAP) {
3838
vars = new LinkedHashMap();
3939
}
40-
vars.put(vars.size(), value);
40+
vars.put(vars.size(), value.toString());
4141
}
4242

4343
public void pop() {

jooby/src/test/java/io/jooby/internal/ChiBenchmark.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,34 +27,43 @@ public class ChiBenchmark {
2727

2828
private $Chi router;
2929

30-
Context foo;
30+
Context plaintext;
3131

32-
Context fooBar;
32+
Context article;
33+
34+
Context articleEdit;
3335

3436
@Setup
3537
public void setup() {
3638
router = new $Chi();
3739

38-
router.insert(route("GET", "/foo"));
39-
router.insert(route("GET", "/foo/{bar}"));
40+
router.insert(route("GET", "/plaintext"));
41+
router.insert(route("GET", "/json"));
42+
43+
plaintext = context("GET", "/plaintext");
4044

41-
foo = context("GET", "/foo");
45+
article = context("GET", "/articles/{id}");
4246

43-
fooBar = context("GET", "/foo/xuqy");
47+
articleEdit = context("GET", "/articles/{id}/edit");
4448
}
4549

4650
private Route route(String method, String pattern) {
4751
return new Route(method, pattern, ctx -> "").setReturnType(String.class);
4852
}
4953

5054
@Benchmark
51-
public void staticMatch() {
52-
router.find(foo, null, null);
55+
public void _plaintext() {
56+
router.find(plaintext, null, null);
57+
}
58+
59+
@Benchmark
60+
public void article() {
61+
router.find(article, null, null);
5362
}
5463

5564
@Benchmark
56-
public void paramMatch() {
57-
router.find(fooBar, null, null);
65+
public void articleEdit() {
66+
router.find(articleEdit, null, null);
5867
}
5968

6069
private static Context context(String method, String path) {

jooby/src/test/java/io/jooby/internal/ChiTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,33 @@ public void wildOnRoot() throws Exception {
9696
});
9797
}
9898

99+
@Test
100+
public void searchString() throws Exception {
101+
$Chi router = new $Chi();
102+
103+
// app.get("/regex/{zid:[0-9]+}/edit", ctx -> ctx.getRoute().getPathKeys());
104+
105+
router.insert(route("GET", "/regex/{nid:[0-9]+}", stringHandler("nid")));
106+
router.insert(route("GET", "/regex/{zid:[0-9]+}/edit", stringHandler("zid")));
107+
router.insert(route("GET", "/articles/{id}", stringHandler("id")));
108+
router.insert(route("GET", "/articles/*", stringHandler("*")));
109+
110+
find(router, "/regex/678/edit", (ctx, result) -> {
111+
assertTrue(result.matches);
112+
assertEquals("zid", result.route().getPipeline().apply(ctx));
113+
});
114+
115+
find(router, "/articles/tail/match", (ctx, result) -> {
116+
assertTrue(result.matches);
117+
assertEquals("*", result.route().getPipeline().apply(ctx));
118+
});
119+
120+
find(router, "/articles/123", (ctx, result) -> {
121+
assertTrue(result.matches);
122+
assertEquals("id", result.route().getPipeline().apply(ctx));
123+
});
124+
}
125+
99126
private void find($Chi router, String pattern,
100127
SneakyThrows.Consumer2<Context, RouterMatch> consumer) {
101128
Context rootctx = ctx(pattern);
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package io.jooby.internal;
2+
3+
import io.jooby.internal.$Chi.ZeroCopyString;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
import static org.junit.jupiter.api.Assertions.assertEquals;
8+
import static org.junit.jupiter.api.Assertions.assertTrue;
9+
10+
public class ZeroCopyStringTest {
11+
@Test
12+
public void shouldCreateFromString() {
13+
String string = "/path";
14+
ZeroCopyString search = new ZeroCopyString(string);
15+
assertEquals(string, search.toString());
16+
assertEquals(string.length(), search.length());
17+
}
18+
19+
@Test
20+
public void shouldStartWith() {
21+
ZeroCopyString search = new ZeroCopyString("/api/profile");
22+
ZeroCopyString s1 = new ZeroCopyString("/api");
23+
assertTrue(search.startsWith(s1));
24+
}
25+
26+
@Test
27+
public void shouldSubstringFromBeginning() {
28+
ZeroCopyString search = new ZeroCopyString("/api/profile");
29+
ZeroCopyString profile = search.substring("/api".length());
30+
assertEquals("/profile", profile.toString());
31+
assertEquals("/profile".length(), profile.length());
32+
assertTrue(profile.startsWith(new ZeroCopyString("/profile")), profile.toString());
33+
ZeroCopyString empty = profile.substring("/profile".length());
34+
assertEquals("", empty.toString());
35+
assertEquals(0, empty.length());
36+
}
37+
38+
@Test
39+
public void shouldSubstring() {
40+
ZeroCopyString sfoos = new ZeroCopyString("/foo/");
41+
ZeroCopyString s = new ZeroCopyString("/");
42+
ZeroCopyString foos = sfoos.substring(s.length());
43+
assertEquals("foo/", foos.toString());
44+
ZeroCopyString f = foos.substring("foo".length());
45+
assertEquals("/", f.toString());
46+
}
47+
48+
@Test
49+
public void shouldSubstringRange() {
50+
ZeroCopyString string = new ZeroCopyString("/articles/{id}");
51+
assertEquals("id", string.substring("/articles/{".length(), string.length() - 1).toString());
52+
assertEquals("id", string.substring("/articles/".length()).substring(1, 3).toString());
53+
}
54+
55+
@Test
56+
public void shouldSubstringRangeBug() {
57+
ZeroCopyString string = new ZeroCopyString("/regex/678/edit".toCharArray(), 7, 8);
58+
assertEquals("678/edit", string.toString());
59+
assertEquals("678", string.substring(0, 3).toString());
60+
}
61+
62+
@Test
63+
public void shouldFindCharacter() {
64+
ZeroCopyString string = new ZeroCopyString("/articles/tail/match");
65+
ZeroCopyString tailmatch = string.substring("/articles/".length());
66+
assertEquals("tail/match", tailmatch.toString());
67+
assertEquals("tail/match".indexOf('/'), tailmatch.indexOf('/'));
68+
}
69+
}

tests/src/test/java/io/jooby/junit/ServerExtensionImpl.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ public class ServerExtensionImpl implements TestTemplateInvocationContextProvide
2727

2828
private static class ServerInfo implements Comparable<ServerInfo> {
2929
private final int index;
30-
private Supplier<Server> server;
30+
private ServerProvider server;
3131

3232
private ExecutionMode mode;
3333

3434
private String description;
3535

36-
public ServerInfo(Supplier<Server> server, ExecutionMode mode, int index, String description) {
36+
public ServerInfo(ServerProvider server, ExecutionMode mode, int index, String description) {
3737
this.server = server;
3838
this.mode = mode;
3939
this.description = description;
@@ -75,7 +75,7 @@ public ServerInfo(Supplier<Server> server, ExecutionMode mode, int index, String
7575
if (executionModes.isEmpty()) {
7676
serverInfos.add(
7777
new ServerInfo(
78-
newServer(it),
78+
new ServerProvider(it),
7979
null,
8080
i,
8181
displayName(it, null, i, repetitions)
@@ -84,7 +84,7 @@ public ServerInfo(Supplier<Server> server, ExecutionMode mode, int index, String
8484
executionModes.stream()
8585
.map(mode ->
8686
new ServerInfo(
87-
newServer(it),
87+
new ServerProvider(it),
8888
mode,
8989
i,
9090
displayName(it, mode, i, repetitions)
@@ -99,13 +99,6 @@ public ServerInfo(Supplier<Server> server, ExecutionMode mode, int index, String
9999
.map(info -> invocationContext(info));
100100
}
101101

102-
private Supplier<Server> newServer(Class serverClass) {
103-
return () -> stream(ServiceLoader.load(Server.class).spliterator(), false)
104-
.filter(s -> serverClass.isInstance(s))
105-
.findFirst()
106-
.orElseThrow(() -> new IllegalArgumentException("Server not found: " + serverClass));
107-
}
108-
109102
private TestTemplateInvocationContext invocationContext(ServerInfo serverInfo) {
110103
return new TestTemplateInvocationContext() {
111104
@Override

tests/src/test/java/io/jooby/junit/ServerParameterResolver.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
11
package io.jooby.junit;
22

33
import io.jooby.ExecutionMode;
4-
import io.jooby.Server;
54
import org.junit.jupiter.api.extension.ExtensionContext;
65
import org.junit.jupiter.api.extension.ParameterContext;
76
import org.junit.jupiter.api.extension.ParameterResolutionException;
87
import org.junit.jupiter.api.extension.ParameterResolver;
98

109
import java.lang.reflect.Method;
11-
import java.util.function.Supplier;
1210

1311
public class ServerParameterResolver implements ParameterResolver {
1412

15-
private final Supplier<Server> server;
13+
private final ServerProvider server;
1614

1715
private final ExecutionMode executionMode;
1816

19-
20-
public ServerParameterResolver(Supplier<Server> server, ExecutionMode executionMode) {
17+
public ServerParameterResolver(ServerProvider server, ExecutionMode executionMode) {
2118
this.server = server;
2219
this.executionMode = executionMode;
2320
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package io.jooby.junit;
2+
3+
import io.jooby.Server;
4+
import io.jooby.jetty.Jetty;
5+
import io.jooby.netty.Netty;
6+
import io.jooby.utow.Utow;
7+
8+
import java.util.ServiceLoader;
9+
import java.util.function.Supplier;
10+
11+
import static java.util.stream.StreamSupport.stream;
12+
13+
public class ServerProvider implements Supplier<Server> {
14+
private Class serverClass;
15+
16+
public ServerProvider(Class serverClass) {
17+
this.serverClass = serverClass;
18+
}
19+
20+
public boolean matchesEventLoopThread(String threadName) {
21+
if (serverClass == Jetty.class) {
22+
return threadName.startsWith("worker-");
23+
}
24+
if (serverClass == Netty.class) {
25+
return threadName.startsWith("eventloop");
26+
}
27+
if (serverClass == Utow.class) {
28+
return threadName.startsWith("worker I/O");
29+
}
30+
return false;
31+
}
32+
33+
public String getName() {
34+
return serverClass.getSimpleName();
35+
}
36+
37+
@Override public Server get() {
38+
return stream(ServiceLoader.load(Server.class).spliterator(), false)
39+
.filter(s -> serverClass.isInstance(s))
40+
.findFirst()
41+
.orElseThrow(() -> new IllegalArgumentException("Server not found: " + serverClass));
42+
}
43+
}

tests/src/test/java/io/jooby/junit/ServerTestRunner.java

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ public class ServerTestRunner {
1919

2020
private final String testName;
2121

22-
private final Supplier<Server> server;
22+
private final ServerProvider server;
2323

2424
private final ExecutionMode executionMode;
2525

2626
private Supplier<Jooby> provider;
2727

2828
private boolean followRedirects = true;
2929

30-
public ServerTestRunner(String testName, Supplier<Server> server, ExecutionMode executionMode) {
30+
public ServerTestRunner(String testName, ServerProvider server, ExecutionMode executionMode) {
3131
this.testName = testName;
3232
this.server = server;
3333
this.executionMode = executionMode;
@@ -87,7 +87,7 @@ public void ready(SneakyThrows.Consumer2<WebClient, WebClient> onReady) {
8787
}
8888

8989
public String getServer() {
90-
return server.getClass().getSimpleName();
90+
return server.getName();
9191
}
9292

9393
public ServerTestRunner dontFollowRedirects() {
@@ -96,16 +96,7 @@ public ServerTestRunner dontFollowRedirects() {
9696
}
9797

9898
public boolean matchesEventLoopThread(String threadName) {
99-
if (server instanceof Jetty) {
100-
return threadName.startsWith("worker-");
101-
}
102-
if (server instanceof Netty) {
103-
return threadName.startsWith("eventloop");
104-
}
105-
if (server instanceof Utow) {
106-
return threadName.startsWith("worker I/O");
107-
}
108-
return false;
99+
return server.matchesEventLoopThread(threadName);
109100
}
110101

111102
@Override public String toString() {

0 commit comments

Comments
 (0)