Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue311 improve hook searching #613

Merged
merged 39 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ae01c87
create a method to search routes and listeners by parameter "q"
steniobhz Sep 30, 2024
9e39c83
Fix null pointer in HookHandler
steniobhz Oct 3, 2024
d9f1c76
Fix tests and implement more validations.
steniobhz Oct 9, 2024
4a0b791
Add integration tests for handleListenerSearch
steniobhz Oct 10, 2024
922708c
Add an integration test at ListenerTest
steniobhz Oct 11, 2024
2f712c0
Fix return method
steniobhz Oct 11, 2024
a0161a2
Added test for hookHandleSearch to verify behavior when no matching l…
steniobhz Oct 11, 2024
322508f
Add tests for hookHandleSearch with no matching listeners and no list…
steniobhz Oct 11, 2024
55ab41b
Implemented a search mechanism that iterates over the provided reposi…
steniobhz Oct 11, 2024
8feb386
Fix error in mock tests
steniobhz Oct 16, 2024
4004646
- add tests for Route
steniobhz Oct 16, 2024
64e6905
Update README_hook.md
steniobhz Oct 17, 2024
6aeec5c
add validation to return 400 instead 404 search parameter != "q" or null
steniobhz Oct 17, 2024
4f7f4ff
add testes to check if parameter is valid
steniobhz Oct 17, 2024
777b906
Fix RouteListingTest and added more validations.
steniobhz Oct 21, 2024
c392df5
Fix README_hook.md
steniobhz Oct 21, 2024
19bc63d
Recreate and fix tests
steniobhz Oct 22, 2024
13b99c2
Fix Url for listeners search
steniobhz Oct 22, 2024
ad49bda
Add test with more listeners to check if search works as expected
steniobhz Oct 22, 2024
8a2a7cf
Improve tests
steniobhz Oct 22, 2024
ac9c720
Improve unit tests
steniobhz Oct 22, 2024
3609204
improve test url as suggested
steniobhz Oct 22, 2024
76adc34
move integration testes to the right project
steniobhz Oct 24, 2024
8096031
improve integration tests
steniobhz Oct 28, 2024
775704d
Improve and optimize the code
steniobhz Oct 28, 2024
97cbb6e
Create new unit tests
steniobhz Oct 28, 2024
db98244
improve and implement new unit testes to cover new hookHandlerSearch
steniobhz Oct 29, 2024
f921c3b
improve the implementation and remove unused methods
steniobhz Oct 30, 2024
0f1c5e7
improve code
steniobhz Oct 31, 2024
8e3d799
Add integration tests to compare results between current routes searc…
steniobhz Nov 4, 2024
4a4fbff
Added validations for route and listener search using parameters
steniobhz Nov 13, 2024
27f561f
Add validation to check if is valid parameters for search request
steniobhz Nov 14, 2024
044ca92
Move specific validations
steniobhz Nov 14, 2024
9956eba
improve code
steniobhz Nov 14, 2024
4fb6453
Fix comments
steniobhz Nov 14, 2024
12f4d78
optimize testes creating reusable functions
steniobhz Nov 14, 2024
d7272ec
Fix testHandleGETRequestWithTrailingSlash
steniobhz Nov 14, 2024
8c47a2a
#311 some cleanup
mcweba Nov 18, 2024
b88a5f6
#311 some cleanup
mcweba Nov 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
steniobhz marked this conversation as resolved.
Show resolved Hide resolved

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
Loading