Skip to content

Commit

Permalink
Merge branch 'master' into feature/buffer-max-size
Browse files Browse the repository at this point in the history
  • Loading branch information
rbygrave authored Dec 1, 2024
2 parents 3115f31 + d25a6ce commit 14384a1
Show file tree
Hide file tree
Showing 10 changed files with 273 additions and 62 deletions.
3 changes: 3 additions & 0 deletions avaje-jex/src/main/java/io/avaje/jex/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,9 @@ default Context headers(Map<String, String> headers) {
*/
BasicAuthCredentials basicAuthCredentials();

/** Return true if the response has been sent. */
boolean responseSent();

/**
* This interface represents a cookie used in HTTP communication. Cookies are small pieces of data
* sent from a server to a web browser and stored on the user's computer. They can be used to
Expand Down
5 changes: 5 additions & 0 deletions avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,11 @@ public Context write(InputStream is) {
return this;
}

@Override
public boolean responseSent() {
return exchange.getResponseCode() != -1;
}

int statusCode() {
return statusCode == 0 ? 200 : statusCode;
}
Expand Down
25 changes: 25 additions & 0 deletions avaje-jex/src/main/java/io/avaje/jex/routes/MultiHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.avaje.jex.routes;

import io.avaje.jex.Context;
import io.avaje.jex.ExchangeHandler;

import java.io.IOException;

final class MultiHandler implements ExchangeHandler {

private final ExchangeHandler[] handlers;

MultiHandler(ExchangeHandler[] handlers) {
this.handlers = handlers;
}

@Override
public void handle(Context ctx) throws IOException {
for (ExchangeHandler handler : handlers) {
handler.handle(ctx);
if (ctx.responseSent()) {
break;
}
}
}
}
6 changes: 6 additions & 0 deletions avaje-jex/src/main/java/io/avaje/jex/routes/RouteEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ final class RouteEntry implements SpiRoutes.Entry {
this.roles = roles;
}

@Override
public SpiRoutes.Entry multiHandler(ExchangeHandler[] handlers) {
final var handler = new MultiHandler(handlers);
return new RouteEntry(path, handler, roles);
}

@Override
public void inc() {
active.incrementAndGet();
Expand Down
46 changes: 19 additions & 27 deletions avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndex.java
Original file line number Diff line number Diff line change
@@ -1,36 +1,33 @@
package io.avaje.jex.routes;

import java.util.ArrayList;
import java.util.List;

final class RouteIndex {

/**
* Partition entries by the number of path segments.
*/
private final RouteIndex.Entry[] entries = new RouteIndex.Entry[6];
private final IndexEntry[] entries;

/**
* Wildcard/splat based route entries.
*/
private final List<SpiRoutes.Entry> wildcardEntries = new ArrayList<>();
private final SpiRoutes.Entry[] wildcardEntries;

RouteIndex() {
for (int i = 0; i < entries.length; i++) {
entries[i] = new RouteIndex.Entry();
}
RouteIndex(List<SpiRoutes.Entry> wildcards, List<List<SpiRoutes.Entry>> pathEntries) {
this.wildcardEntries = wildcards.toArray(new SpiRoutes.Entry[0]);
this.entries = pathEntries.stream()
.map(RouteIndex::toEntry)
.toList()
.toArray(new IndexEntry[0]);
}

private int index(int segmentCount) {
return Math.min(segmentCount, 5);
private static IndexEntry toEntry(List<SpiRoutes.Entry> routeEntries) {
return new IndexEntry(routeEntries.toArray(new SpiRoutes.Entry[0]));
}

void add(SpiRoutes.Entry entry) {
if (entry.multiSlash()) {
wildcardEntries.add(entry);
} else {
entries[index(entry.segmentCount())].add(entry);
}
private int index(int segmentCount) {
return Math.min(segmentCount, 5);
}

SpiRoutes.Entry match(String pathInfo) {
Expand Down Expand Up @@ -63,7 +60,7 @@ private int segmentCount(String pathInfo) {

long activeRequests() {
long total = 0;
for (RouteIndex.Entry entry : entries) {
for (IndexEntry entry : entries) {
total += entry.activeRequests();
}
for (SpiRoutes.Entry entry : wildcardEntries) {
Expand All @@ -72,21 +69,16 @@ long activeRequests() {
return total;
}

private static class Entry {
private static final class IndexEntry {

private final List<SpiRoutes.Entry> list = new ArrayList<>();
private final SpiRoutes.Entry[] pathEntries;

void add(SpiRoutes.Entry entry) {
if (entry.literal()) {
// add literal paths to the beginning
list.add(0, entry);
} else {
list.add(entry);
}
IndexEntry(SpiRoutes.Entry[] pathEntries) {
this.pathEntries = pathEntries;
}

SpiRoutes.Entry match(String pathInfo) {
for (SpiRoutes.Entry entry : list) {
for (SpiRoutes.Entry entry : pathEntries) {
if (entry.matches(pathInfo)) {
return entry;
}
Expand All @@ -96,7 +88,7 @@ SpiRoutes.Entry match(String pathInfo) {

long activeRequests() {
long total = 0;
for (SpiRoutes.Entry entry : list) {
for (SpiRoutes.Entry entry : pathEntries) {
total += entry.activeRequests();
}
return total;
Expand Down
85 changes: 85 additions & 0 deletions avaje-jex/src/main/java/io/avaje/jex/routes/RouteIndexBuild.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package io.avaje.jex.routes;

import io.avaje.jex.ExchangeHandler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Build the RouteIndex.
*/
final class RouteIndexBuild {

/**
* Partition entries by the number of path segments.
*/
private final RouteIndexBuild.Entry[] entries = new RouteIndexBuild.Entry[6];

/**
* Wildcard/splat based route entries.
*/
private final List<SpiRoutes.Entry> wildcardEntries = new ArrayList<>();

RouteIndexBuild() {
for (int i = 0; i < entries.length; i++) {
entries[i] = new RouteIndexBuild.Entry();
}
}

private int index(int segmentCount) {
return Math.min(segmentCount, 5);
}

void add(SpiRoutes.Entry entry) {
if (entry.multiSlash()) {
wildcardEntries.add(entry);
} else {
entries[index(entry.segmentCount())].add(entry);
}
}

/**
* Build and return the RouteIndex.
*/
RouteIndex build() {
final List<List<SpiRoutes.Entry>> pathEntries = new ArrayList<>();
for (Entry entry : entries) {
pathEntries.add(entry.build());
}
return new RouteIndex(wildcardEntries, pathEntries);
}

private static class Entry {

private final List<SpiRoutes.Entry> list = new ArrayList<>();
private final Map<String,List<SpiRoutes.Entry>> pathMap = new HashMap<>();

void add(SpiRoutes.Entry entry) {
if (entry.literal()) {
// add literal paths to the beginning
list.addFirst(entry);
} else {
pathMap.computeIfAbsent(entry.matchPath(), k -> new ArrayList<>(2)).add(entry);
}
}

List<SpiRoutes.Entry> build() {
List<SpiRoutes.Entry> result = new ArrayList<>(list.size() + pathMap.size());
result.addAll(list);
pathMap.values().forEach(pathList -> {
if (pathList.size() == 1) {
result.add(pathList.getFirst());
} else {
ExchangeHandler[] handlers = pathList.stream()
.map(SpiRoutes.Entry::handler)
.toList()
.toArray(new ExchangeHandler[0]);
result.add(pathList.getFirst().multiHandler(handlers));
}
});
return result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ public final class RoutesBuilder {

public RoutesBuilder(Routing routing, boolean ignoreTrailingSlashes) {
this.ignoreTrailingSlashes = ignoreTrailingSlashes;
final var buildMap = new EnumMap<Routing.Type, RouteIndexBuild>(Routing.Type.class);
for (var handler : routing.handlers()) {
typeMap.computeIfAbsent(handler.getType(), h -> new RouteIndex()).add(convert(handler));
buildMap.computeIfAbsent(handler.getType(), h -> new RouteIndexBuild()).add(convert(handler));
}
buildMap.forEach((key, value) -> typeMap.put(key, value.build()));
filters = List.copyOf(routing.filters());
}

Expand Down
5 changes: 3 additions & 2 deletions avaje-jex/src/main/java/io/avaje/jex/routes/SpiRoutes.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package io.avaje.jex.routes;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;

import io.avaje.jex.Context;
import io.avaje.jex.ExchangeHandler;
import io.avaje.jex.HttpFilter;
import io.avaje.jex.Routing;
Expand Down Expand Up @@ -103,6 +101,9 @@ interface Entry {

/** Return the authentication roles for the route. */
Set<Role> roles();

/** Create and return a new Entry with multiple handlers. */
Entry multiHandler(ExchangeHandler[] handlers);
}

}
78 changes: 78 additions & 0 deletions avaje-jex/src/test/java/io/avaje/jex/jdk/MultiHandlerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package io.avaje.jex.jdk;

import io.avaje.jex.Jex;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;

import java.net.http.HttpResponse;

import static org.assertj.core.api.Assertions.assertThat;

class MultiHandlerTest {

static TestPair pair = init();

static TestPair init() {
Jex app = Jex.create()
.routing(routing -> routing
.get("/hi", ctx4 -> {
if (ctx4.header("Hx-Request") != null) {
ctx4.text("HxResponse");
}
})
.get("/hi", ctx -> ctx.text("NormalResponse"))
.get("/hi/{id}", ctx3 -> {
if (ctx3.header("Hx-Request") != null) {
ctx3.text("HxResponse|" + ctx3.pathParam("id"));
}
})
.get("/hi/{id}", ctx2 -> {
if (ctx2.header("H2-Request") != null) {
ctx2.text("H2Response|" + ctx2.pathParam("id"));
}
})
.get("/hi/{id}", ctx1 -> ctx1.text("NormalResponse|" + ctx1.pathParam("id")))
);
return TestPair.create(app);
}

@AfterAll
static void end() {
pair.shutdown();
}

@Test
void test() {
HttpResponse<String> hres = pair.request().path("hi").GET().asString();
assertThat(hres.statusCode()).isEqualTo(200);
assertThat(hres.body()).isEqualTo("NormalResponse");

HttpResponse<String> hxRes = pair.request()
.header("Hx-Request", "true")
.path("hi")
.GET().asString();
assertThat(hxRes.statusCode()).isEqualTo(200);
assertThat(hxRes.body()).isEqualTo("HxResponse");
}

@Test
void testWithPathParam() {
HttpResponse<String> hres = pair.request().path("hi/42").GET().asString();
assertThat(hres.statusCode()).isEqualTo(200);
assertThat(hres.body()).isEqualTo("NormalResponse|42");

HttpResponse<String> hxRes = pair.request()
.header("Hx-Request", "true")
.path("hi/42")
.GET().asString();
assertThat(hxRes.statusCode()).isEqualTo(200);
assertThat(hxRes.body()).isEqualTo("HxResponse|42");

HttpResponse<String> h2Res = pair.request()
.header("H2-Request", "true")
.path("hi/42")
.GET().asString();
assertThat(h2Res.statusCode()).isEqualTo(200);
assertThat(h2Res.body()).isEqualTo("H2Response|42");
}
}
Loading

0 comments on commit 14384a1

Please sign in to comment.