Skip to content

Commit

Permalink
Merge pull request #613 from swisspost/Issue311_Improve-hook-searching
Browse files Browse the repository at this point in the history
Issue311 improve hook searching
  • Loading branch information
mcweba authored Nov 18, 2024
2 parents bf6015b + b88a5f6 commit 5fb534c
Show file tree
Hide file tree
Showing 6 changed files with 890 additions and 74 deletions.
48 changes: 48 additions & 0 deletions gateleen-hook/README_hook.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,10 +240,58 @@ hookHandler.enableResourceLogging(true);
```


## Query-Based Listener and Route Search

Gateleen allows searching for listeners and routes using the query parameter `q`. This simplifies filtering the registered hooks based on query parameters.

The search will be based on the value registered of the destination property

### Listener Search with `q`
Search for listeners based on a query parameter like this:

```
GET http://myserver:7012/playground/server/hooks/v1/registrations/listeners?q=mylistener
```

The response will contain the matching listeners. If no match is found, an empty list is returned:

**Example response with matches:**
```json
{
"listeners": [
"first+playground+server+test+nemo+origin+mylistener"
]
}
```

**Example response with no matches:**
```json
{
"listeners": []
}
```

### Route Search with `q`
Similarly, you can search for routes using a query parameter:

```
GET http://myserver:7012/playground/server/hooks/v1/registrations/routes/?q=myroute
```

The response contains the matching routes, or an empty list if no match is found.

**Example response with matches:**
```json
{
"routes": [
"first+playground+server+test+nemo+origin+myroute"
]
}

```
**Example response with no matches:**
```json
{
"routes": []
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,7 @@
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.eventbus.Message;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpClientResponse;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.http.*;
import io.vertx.core.http.impl.headers.HeadersMultiMap;
import io.vertx.core.json.DecodeException;
import io.vertx.core.json.JsonArray;
Expand All @@ -31,12 +26,7 @@
import org.swisspush.gateleen.core.logging.LoggableResource;
import org.swisspush.gateleen.core.logging.RequestLogger;
import org.swisspush.gateleen.core.storage.ResourceStorage;
import org.swisspush.gateleen.core.util.CollectionContentComparator;
import org.swisspush.gateleen.core.util.HttpHeaderUtil;
import org.swisspush.gateleen.core.util.HttpRequestHeader;
import org.swisspush.gateleen.core.util.HttpServerRequestUtil;
import org.swisspush.gateleen.core.util.ResourcesUtils;
import org.swisspush.gateleen.core.util.StatusCode;
import org.swisspush.gateleen.core.util.*;
import org.swisspush.gateleen.hook.queueingstrategy.DefaultQueueingStrategy;
import org.swisspush.gateleen.hook.queueingstrategy.DiscardPayloadQueueingStrategy;
import org.swisspush.gateleen.hook.queueingstrategy.QueueingStrategy;
Expand Down Expand Up @@ -70,11 +60,11 @@
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static io.vertx.core.http.HttpMethod.DELETE;
import static io.vertx.core.http.HttpMethod.PUT;
import static io.vertx.core.http.HttpMethod.*;
import static org.swisspush.gateleen.core.util.HttpRequestHeader.CONTENT_LENGTH;

/**
Expand All @@ -89,7 +79,6 @@ public class HookHandler implements LoggableResource {
public static final String HOOKS_LISTENERS_URI_PART = "/_hooks/listeners/";
public static final String LISTENER_QUEUE_PREFIX = "listener-hook";
private static final String X_QUEUE = "x-queue";
private static final String X_EXPIRE_AFTER = "X-Expire-After";
private static final String LISTENER_HOOK_TARGET_PATH = "listeners/";

public static final String HOOKS_ROUTE_URI_PART = "/_hooks/route";
Expand Down Expand Up @@ -123,6 +112,10 @@ public class HookHandler implements LoggableResource {
public static final String LISTABLE = "listable";
public static final String COLLECTION = "collection";

private static final String CONTENT_TYPE_JSON = "application/json";
private static final String LISTENERS_KEY = "listeners";
private static final String ROUTES_KEY = "routes";

private final Comparator<String> collectionContentComparator;
private static final Logger log = LoggerFactory.getLogger(HookHandler.class);

Expand Down Expand Up @@ -152,7 +145,10 @@ public class HookHandler implements LoggableResource {
private int routeMultiplier;

private final QueueSplitter queueSplitter;

private final String routeBase;
private final String listenerBase;
private final String normalizedRouteBase;
private final String normalizedListenerBase;

/**
* Creates a new HookHandler.
Expand Down Expand Up @@ -285,6 +281,11 @@ public HookHandler(Vertx vertx, HttpClient selfClient, final ResourceStorage use
this.queueSplitter = queueSplitter;
String hookSchema = ResourcesUtils.loadResource("gateleen_hooking_schema_hook", true);
jsonSchemaHook = JsonSchemaFactory.getInstance().getSchema(hookSchema);
this.listenerBase = hookRootUri + HOOK_LISTENER_STORAGE_PATH;
this.routeBase = hookRootUri + HOOK_ROUTE_STORAGE_PATH;
this.normalizedListenerBase = this.listenerBase.replaceAll("/+$", "");
this.normalizedRouteBase = this.routeBase.replaceAll("/+$", "");

}

public void init() {
Expand Down Expand Up @@ -379,9 +380,6 @@ private void registerCleanupHandler(Handler<Void> readyHandler) {
private void loadStoredRoutes(Handler<Void> readyHandler) {
log.debug("loadStoredRoutes");

// load the names of the routes from the hookStorage
final String routeBase = hookRootUri + HOOK_ROUTE_STORAGE_PATH;

hookStorage.get(routeBase, buffer -> {
if (buffer != null) {
JsonObject listOfRoutes = new JsonObject(buffer.toString());
Expand Down Expand Up @@ -426,8 +424,6 @@ private void loadStoredRoutes(Handler<Void> readyHandler) {
private void loadStoredListeners(final Handler<Void> readyHandler) {
log.debug("loadStoredListeners");

// load the names of the listener from the hookStorage
final String listenerBase = hookRootUri + HOOK_LISTENER_STORAGE_PATH;
hookStorage.get(listenerBase, buffer -> {
if (buffer != null) {
JsonObject listOfListeners = new JsonObject(buffer.toString());
Expand Down Expand Up @@ -541,13 +537,12 @@ public void registerListenerRegistrationHandler(Handler<Void> readyHandler) {
public boolean handle(final RoutingContext ctx) {
HttpServerRequest request = ctx.request();
boolean consumed = false;

var requestUri = request.uri();
/*
* 1) Un- / Register Listener / Routes
*/
var requestMethod = request.method();
if (requestMethod == PUT) {
var requestUri = request.uri();
if (requestUri.contains(HOOKS_LISTENERS_URI_PART)) {
handleListenerRegistration(request);
return true;
Expand All @@ -558,7 +553,6 @@ public boolean handle(final RoutingContext ctx) {
}
}
if (requestMethod == DELETE) {
var requestUri = request.uri();
if (requestUri.contains(HOOKS_LISTENERS_URI_PART)) {
handleListenerUnregistration(request);
return true;
Expand All @@ -569,6 +563,16 @@ public boolean handle(final RoutingContext ctx) {
}
}

if (requestMethod == GET && !request.params().isEmpty()) {
if (requestUri.contains(normalizedListenerBase) ) {
handleListenerSearch(request);
return true;
} else if (requestUri.contains(normalizedRouteBase) ) {
handleRouteSearch(request);
return true;
}
}

/*
* 2) Check if we have to queue a request for listeners
*/
Expand All @@ -592,6 +596,60 @@ public boolean handle(final RoutingContext ctx) {
}
}

private void handleListenerSearch(HttpServerRequest request) {
handleSearch(
listenerRepository.getListeners().stream().collect(Collectors.toMap(Listener::getListenerId, listener -> listener)),
listener -> listener.getHook().getDestination(),
LISTENERS_KEY,
request
);
}

private void handleRouteSearch(HttpServerRequest request) {
handleSearch(
routeRepository.getRoutes().entrySet().stream().collect(Collectors.toMap(entry -> entry.getValue().getHookDisplayText(), Map.Entry::getValue)),
route -> route.getHook().getDestination(),
ROUTES_KEY,
request
);
}

/**
* Search the repository for items matching the query parameter.
* Output a JSON response with the matched results.
* If parameter queryParam is empty or null a 400 Bad Request is returned.
* All params cannot be null
* @param repository The items to search .
* @param getDestination Function to extract destinations.
* @param resultKey The key for the result in the response.
* @param request The HTTP request to make a specific validations and return the results.
*/
private <T> void handleSearch(Map<String, T> repository, Function<T, String> getDestination, String resultKey, HttpServerRequest request) {
String queryParam = request.getParam("q");
if (request.params().size() > 1 || StringUtils.isEmpty(queryParam)) {
request.response().setStatusCode(StatusCode.BAD_REQUEST.getStatusCode());
request.response().setStatusMessage(StatusCode.BAD_REQUEST.getStatusMessage());
request.response().end("Only the 'q' parameter is allowed and can't be empty or null");
return ;
}

JsonArray matchingResults = new JsonArray();
repository.forEach((key, value) -> {
String destination = getDestination.apply(value);
if (destination != null && destination.contains(queryParam)) {
matchingResults.add(convertToStoragePattern(key));
}
});

JsonObject result = new JsonObject();
result.put(resultKey, matchingResults);

String encodedResult = result.encode();

request.response().putHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE_JSON);
request.response().end(encodedResult);
}

/**
* Create a listing of routes in the given parent. This happens
* only if we have a GET request, the routes are listable and
Expand Down Expand Up @@ -1420,7 +1478,7 @@ private void registerListener(Buffer buffer) {
target = hook.getDestination();
} else {
String urlPattern = hookRootUri + LISTENER_HOOK_TARGET_PATH + target;
routeRepository.addRoute(urlPattern, createRoute(urlPattern, hook));
routeRepository.addRoute(urlPattern, createRoute(urlPattern, hook, requestUrl));

if (log.isTraceEnabled()) {
log.trace("external target, add route for urlPattern: {}", urlPattern);
Expand Down Expand Up @@ -1602,12 +1660,13 @@ private void registerRoute(Buffer buffer) {
}

boolean mustCreateNewRoute = true;

Route existingRoute = routeRepository.getRoutes().get(routedUrl);
if (existingRoute != null) {
mustCreateNewRoute = mustCreateNewRouteForHook(existingRoute, hook);
}
if (mustCreateNewRoute) {
routeRepository.addRoute(routedUrl, createRoute(routedUrl, hook));
routeRepository.addRoute(routedUrl, createRoute(routedUrl, hook, requestUrl));
} else {
// see comment in #mustCreateNewRouteForHook()
existingRoute.getRule().setHeaderFunction(hook.getHeaderFunction());
Expand Down Expand Up @@ -1658,11 +1717,12 @@ private boolean headersFilterPatternEquals(Pattern headersFilterPatternLeft, Pat
*
* @param urlPattern urlPattern
* @param hook hook
* @param hookDisplayText text used for display only like in API
* @return Route
*/
private Route createRoute(String urlPattern, HttpHook hook) {
private Route createRoute(String urlPattern, HttpHook hook, String hookDisplayText) {
return new Route(vertx, userProfileStorage, loggingResourceManager, logAppenderRepository, monitoringHandler,
userProfilePath, hook, urlPattern, selfClient);
userProfilePath, hook, urlPattern, selfClient, hookDisplayText);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public class Route {
private MonitoringHandler monitoringHandler;
private String userProfilePath;
private ResourceStorage storage;
private String hookDisplayText;

private String urlPattern;
private HttpHook httpHook;
Expand Down Expand Up @@ -78,8 +79,9 @@ public class Route {
* @param httpHook httpHook
* @param urlPattern - this can be a listener or a normal urlPattern (eg. for a route)
*/
public Route(Vertx vertx, ResourceStorage storage, LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository,
MonitoringHandler monitoringHandler, String userProfilePath, HttpHook httpHook, String urlPattern, HttpClient selfClient) {
public Route(Vertx vertx, ResourceStorage storage, LoggingResourceManager loggingResourceManager,
LogAppenderRepository logAppenderRepository, MonitoringHandler monitoringHandler, String userProfilePath,
HttpHook httpHook, String urlPattern, HttpClient selfClient, String hookDisplayText) {
this.vertx = vertx;
this.storage = storage;
this.loggingResourceManager = loggingResourceManager;
Expand All @@ -89,6 +91,7 @@ public Route(Vertx vertx, ResourceStorage storage, LoggingResourceManager loggin
this.httpHook = httpHook;
this.urlPattern = urlPattern;
this.selfClient = selfClient;
this.hookDisplayText = hookDisplayText;

createRule();

Expand Down Expand Up @@ -273,4 +276,8 @@ public void cleanup() {
public HttpHook getHook() {
return httpHook;
}

public String getHookDisplayText() {
return hookDisplayText;
}
}
Loading

0 comments on commit 5fb534c

Please sign in to comment.