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

Add JSONPath + example for ConditionalInterceptor #1268

Merged
merged 11 commits into from
Sep 27, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
*/
@MCElement(name = "if")
public class ConditionalInterceptor extends AbstractFlowInterceptor {
private static final Logger log = LoggerFactory.getLogger(InterceptorFlowController.class);
private static final Logger log = LoggerFactory.getLogger(ConditionalInterceptor.class);

// configuration
private String test;
Expand All @@ -63,7 +63,7 @@ public class ConditionalInterceptor extends AbstractFlowInterceptor {
/**
* Spring Expression Language
*/
private final SpelParserConfiguration spelConfig = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, this.getClass().getClassLoader());
private final SpelParserConfiguration spelConfig = new SpelParserConfiguration(SpelCompilerMode.MIXED, this.getClass().getClassLoader());
christiangoerdes marked this conversation as resolved.
Show resolved Hide resolved
private Expression spelExpr;

private final InterceptorFlowController interceptorFlowController = new InterceptorFlowController();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
limitations under the License. */
package com.predic8.membrane.core.lang.spel.functions;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.JsonPath;
import com.predic8.membrane.core.interceptor.AbstractInterceptorWithSession;
import com.predic8.membrane.core.lang.spel.ExchangeEvaluationContext;
import com.predic8.membrane.core.security.SecurityScheme;
Expand All @@ -21,6 +23,7 @@

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Predicate;
Expand All @@ -39,7 +42,17 @@
* The ExchangeEvaluationContext provides a specialized Membrane SpEL context, enabling access to the Exchange and other relevant data.
*/
public class BuiltInFunctions {
private static final Logger log = LoggerFactory.getLogger(ExchangeEvaluationContext.class.getName());
private static final Logger log = LoggerFactory.getLogger(BuiltInFunctions.class);

private static final ObjectMapper objectMapper = new ObjectMapper();

public static Object jsonPath(String jsonPath, ExchangeEvaluationContext ctx) {
try {
return JsonPath.read(objectMapper.readValue(ctx.getMessage().getBodyAsStringDecoded(), Map.class), jsonPath);
} catch (Exception ignored) {
return null;
}
}

public static boolean weight(double weightInPercent, ExchangeEvaluationContext ignored) {
return Math.max(0, Math.min(1, weightInPercent / 100.0)) > ThreadLocalRandom.current().nextDouble();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,16 @@ public class BuiltInFunctionsTest {
static void init() throws URISyntaxException {
var exc = Request.get("foo").buildExchange();
exc.setProperty(SECURITY_SCHEMES, List.of(new ApiKeySecurityScheme(HEADER, "X-Api-Key").scopes("demo", "test")));
exc.getRequest().setBodyContent("{\"name\":\"John\"}".getBytes());
christiangoerdes marked this conversation as resolved.
Show resolved Hide resolved
ctx = new ExchangeEvaluationContext(exc);
}

@Test
void testJsonPath() {
assertEquals("John", BuiltInFunctions.jsonPath("$.name", ctx));
assertNull(BuiltInFunctions.jsonPath("$.foo", ctx));
}

@Test
void testRate() throws Exception {
assertEquals(0.01, calculateRate(1), 0.05);
Expand Down
107 changes: 106 additions & 1 deletion distribution/examples/if/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,106 @@

# ConditionalInterceptor

Conditionally apply plugins using expressions.

## Running the Example

***Note:*** *You can test these requests using the provided HTTP request snippets.*

1. **Navigate** to the `examples/if` directory.
2. **Start** the Router by executing `router-service.sh` (Linux/Mac) or `router-service.bat` (Windows).
3. **Execute the following requests** (alternatively, use the `requests.http` file):

- **JSON Request**:
```bash
curl -X POST http://localhost:2000 -H "Content-Type: application/json" -d '{"foo": "bar"}' -v
```
- **JSON with Non-null 'name' Key**:
```bash
curl -X POST http://localhost:2000 -H "Content-Type: application/json" -d '{"name": "bar"}' -v
```
- **JSON with 'name' Key as 'foo'**:
```bash
curl -X POST http://localhost:2000 -H "Content-Type: application/json" -d '{"name": "foo"}' -v
```
- **Query Parameter Check**:
```bash
curl -X GET 'http://localhost:2000/?param1=value1' -v
```
```bash
curl -X GET 'http://localhost:2000/?param1=value2' -v
```
- **Header Check for 'bar'**:
```bash
curl -X GET http://localhost:2000 -H "X-Test-Header: foo" -v
```
```bash
curl -X GET http://localhost:2000 -H "X-Test-Header: foobar" -v
```
- **Large Body Detection**:
```bash
curl -X POST http://localhost:2000 -d "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." -v
```

4. **Review the Configuration**:
- Take a look at `proxies.xml` to understand the configuration details and the conditional logic applied.

## Configuration Overview

### Request Handling

The configuration applies various conditions to incoming requests:

```xml
<api port="2000">
<request>
<if test="headers['Content-Type'] == 'application/json'" language="SpEL">
<groovy>println("JSON Request!")</groovy>
</if>
<if test="jsonPath('$.name') != null" language="SpEL">
<groovy>println("The JSON request contains the key 'name', and it is not null.")</groovy>
</if>
<if test="jsonPath('$.name') == 'foo'" language="SpEL">
<groovy>println("The JSON request contains the key 'name' with the value 'foo'.")</groovy>
</if>
<if test="method == 'POST'" language="SpEL">
<groovy>println("Request method was POST.")</groovy>
</if>
<if test="params['param1'] == 'value2'" language="SpEL">
<groovy>println("Query Parameter Given!")</groovy>
</if>
<if test="headers['X-Test-Header'] matches '.*bar.*'" language="SpEL">
<groovy>println("X-Test-Header contains 'bar'")</groovy>
</if>
<if test="request.getBody.getLength gt 64" language="SpEL">
<groovy>println("Long body")</groovy>
</if>
</request>
```

### Response Manipulation

Responses are manipulated based on status codes and request conditions:

```xml
<response>
<if test="statusCode matches '[45]\d\d'" language="SpEL">
<template pretty="yes" contentType="application/json">
{
"type": "https://membrane-api.io/error/",
"title": "${exc.response.statusMessage}",
"status": ${exc.response.statusCode}
}
</template>
</if>
<if test="statusCode == 302" language="SpEL">
<groovy>println("Status code changed")
exc.getResponse().setStatusCode(404)</groovy>
</if>
</response>

<template>Success</template>
<return statusCode="302"/>
</api>
```

This configuration allows you to dynamically handle requests and adjust responses based on specified conditions.
6 changes: 6 additions & 0 deletions distribution/examples/if/proxies.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
<if test="headers['Content-Type'] == 'application/json'" language="SpEL">
<groovy>println("JSON Request!")</groovy>
</if>
<if test="jsonPath('$.name') != null" language="SpEL">
<groovy>println("The JSON request contains the key 'name', and it is not null.")</groovy>
</if>
<if test="jsonPath('$.name') == 'foo'" language="SpEL">
<groovy>println("The JSON request contains the key 'name' with the value 'foo'.")</groovy>
</if>
<if test="method == 'POST'" language="SpEL">
<groovy>println("Request method was POST.")</groovy>
</if>
Expand Down
14 changes: 14 additions & 0 deletions distribution/examples/if/requests.http
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ Content-Type: application/json

{"foo": "bar"}

### Will be detected as a JSON request where the 'name' key is not null.
POST / HTTP/2
Host: localhost:2000
Content-Type: application/json

{"name": "bar"}

### Will be detected as a JSON request where the 'name' key has the value 'foo'.
POST / HTTP/2
Host: localhost:2000
Content-Type: application/json

{"name": "foo"}

### Query parameter value will be checked.
GET /?param1=value1 HTTP/2
Host: localhost:2000
Expand Down
Loading