rebuild http driver on new API

This commit is contained in:
Jonathan Shook 2022-06-24 01:00:27 -05:00
parent 586bb4c566
commit 8d87c6aee1
60 changed files with 650 additions and 928 deletions

View File

@ -24,7 +24,7 @@
<relativePath>../mvn-defaults</relativePath>
</parent>
<artifactId>driver-http</artifactId>
<artifactId>adapter-http</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>

View File

@ -0,0 +1,73 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.adapter.http;
import io.nosqlbench.adapter.http.core.HttpFormatParser;
import io.nosqlbench.adapter.http.core.HttpOp;
import io.nosqlbench.adapter.http.core.HttpOpMapper;
import io.nosqlbench.adapter.http.core.HttpSpace;
import io.nosqlbench.engine.api.activityimpl.OpMapper;
import io.nosqlbench.engine.api.activityimpl.uniform.BaseDriverAdapter;
import io.nosqlbench.engine.api.activityimpl.uniform.DriverAdapter;
import io.nosqlbench.engine.api.activityimpl.uniform.DriverSpaceCache;
import io.nosqlbench.nb.annotations.Service;
import io.nosqlbench.nb.api.config.standard.NBConfigModel;
import io.nosqlbench.nb.api.config.standard.NBConfiguration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
@Service(value = DriverAdapter.class, selector = "http")
public class HttpDriverAdapter extends BaseDriverAdapter<HttpOp, HttpSpace> {
@Override
public OpMapper<HttpOp> getOpMapper() {
DriverSpaceCache<? extends HttpSpace> spaceCache = getSpaceCache();
NBConfiguration config = getConfiguration();
return new HttpOpMapper(config, spaceCache);
}
@Override
public Function<String, ? extends HttpSpace> getSpaceInitializer(NBConfiguration cfg) {
return spaceName -> new HttpSpace(spaceName, cfg);
}
@Override
public List<Function<Map<String, Object>, Map<String, Object>>> getOpFieldRemappers() {
return super.getOpFieldRemappers();
}
@Override
public List<Function<String, Optional<Map<String, Object>>>> getOpStmtRemappers() {
return List.of(
s -> Optional.ofNullable(HttpFormatParser.parseUrl(s))
.map(LinkedHashMap::new),
s -> Optional.ofNullable(HttpFormatParser.parseInline(s))
.map(LinkedHashMap::new),
s -> Optional.ofNullable(HttpFormatParser.parseParams(s))
.map(LinkedHashMap::new)
);
}
@Override
public NBConfigModel getConfigModel() {
return super.getConfigModel().add(HttpSpace.getConfigModel());
}
}

View File

@ -14,9 +14,9 @@
* limitations under the License.
*/
package io.nosqlbench.activitytype.http;
package io.nosqlbench.adapter.http.core;
import io.nosqlbench.activitytype.http.statuscodes.HttpStatusCodes;
import io.nosqlbench.adapter.http.statuscodes.HttpStatusCodes;
import java.io.PrintStream;
import java.net.http.HttpClient;

View File

@ -14,8 +14,9 @@
* limitations under the License.
*/
package io.nosqlbench.activitytype.cmds;
package io.nosqlbench.adapter.http.core;
import io.nosqlbench.nb.api.config.params.ParamsParser;
import io.nosqlbench.nb.api.errors.BasicError;
import io.nosqlbench.virtdata.core.templates.BindPointParser;
@ -28,6 +29,13 @@ import java.util.regex.Pattern;
public class HttpFormatParser {
public static Map<String,String> parseParams(String oneliner) {
if (ParamsParser.hasValues(oneliner)) {
return ParamsParser.parse(oneliner,false);
}
return null;
}
public static Map<String, String> parseUrl(String uri) {
if (uri.matches("http.+")) {
return Map.of("uri", rewriteExplicitSections(uri));

View File

@ -14,20 +14,23 @@
* limitations under the License.
*/
package io.nosqlbench.activitytype.http;
package io.nosqlbench.adapter.http.core;
import io.nosqlbench.engine.api.activityapi.core.Action;
import io.nosqlbench.engine.api.activityapi.core.ActionDispenser;
import com.codahale.metrics.Histogram;
import io.nosqlbench.api.NBNamedElement;
import io.nosqlbench.engine.api.metrics.ActivityMetrics;
public class HttpActionDispenser implements ActionDispenser {
private final HttpActivity httpActivity;
public class HttpMetrics implements NBNamedElement {
private final HttpSpace space;
final Histogram statusCodeHistogram;
public HttpActionDispenser(HttpActivity httpActivity) {
this.httpActivity = httpActivity;
public HttpMetrics(HttpSpace space) {
this.space = space;
statusCodeHistogram = ActivityMetrics.histogram(this, "statuscode",space.getHdrDigits());
}
@Override
public Action getAction(int i) {
return new HttpAction(httpActivity.getActivityDef(), i, httpActivity);
public String getName() {
return "http"+(space.getName().equals("default")?"":"-"+space.getName());
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.adapter.http.core;
import io.nosqlbench.adapter.http.errors.InvalidResponseBodyException;
import io.nosqlbench.adapter.http.errors.InvalidStatusCodeException;
import io.nosqlbench.engine.api.activityimpl.uniform.flowtypes.RunnableOp;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
public class HttpOp implements RunnableOp {
public final Pattern ok_status;
public final Pattern ok_body;
public final HttpRequest request;
private final HttpClient client;
private final HttpSpace space;
private final long cycle;
public HttpOp(HttpClient client, HttpRequest request, Pattern ok_status, Pattern ok_body, HttpSpace space, long cycle) {
this.client = client;
this.request = request;
this.ok_status = ok_status;
this.ok_body = ok_body;
this.space = space;
this.cycle = cycle;
}
@Override
public void run() {
HttpResponse.BodyHandler<String> bodyreader = HttpResponse.BodyHandlers.ofString();
HttpResponse<String> response = null;
Exception error = null;
long startat = System.nanoTime();
try {
CompletableFuture<HttpResponse<String>> responseFuture = client.sendAsync(request, bodyreader);
response = responseFuture.get(space.getTimeoutMillis(), TimeUnit.MILLISECONDS);
space.getHttpMetrics().statusCodeHistogram.update(response.statusCode());
if (ok_status != null) {
if (!ok_status.matcher(String.valueOf(response.statusCode())).matches()) {
throw new InvalidStatusCodeException(ok_status, response.statusCode());
}
}
if (ok_body != null) {
if (!ok_body.matcher(response.body()).matches()) {
throw new InvalidResponseBodyException(ok_body, response.body());
}
}
} catch (Exception e) {
error = e;
} finally {
long nanos = System.nanoTime() - startat;
if (space.isDiagnosticMode()) {
space.getConsole().summarizeRequest("request", error, request, System.out, cycle, nanos);
if (response != null) {
space.getConsole().summarizeResponseChain(error, response, System.out, cycle, nanos);
} else {
System.out.println("---- RESPONSE was null");
}
System.out.println();
}
// propogate exception so main error handling logic can take over
if (error!=null) {
throw new RuntimeException(error);
}
}
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.adapter.http.core;
import io.nosqlbench.engine.api.activityimpl.BaseOpDispenser;
import io.nosqlbench.engine.api.templating.ParsedOp;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.function.LongFunction;
import java.util.regex.Pattern;
public class HttpOpDispenser extends BaseOpDispenser<HttpOp> {
private final LongFunction<HttpOp> opFunc;
public static final String DEFAULT_OK_BODY = ".+?";
public static final String DEFAULT_OK_STATUS = "2..";
public HttpOpDispenser(LongFunction<HttpSpace> ctxF, ParsedOp op) {
super(op);
opFunc = getOpFunc(ctxF, op);
}
private LongFunction<HttpOp> getOpFunc(LongFunction<HttpSpace> ctxF, ParsedOp op) {
LongFunction<HttpRequest.Builder> builderF = l -> HttpRequest.newBuilder();
LongFunction<String> bodyF = op.getAsFunctionOr("body", null);
LongFunction<HttpRequest.BodyPublisher> bodyPublisherF =
l -> Optional.ofNullable(bodyF.apply(l)).map(HttpRequest.BodyPublishers::ofString).orElse(
HttpRequest.BodyPublishers.noBody()
);
LongFunction<String> methodF = op.getAsFunctionOr("method", "GET");
LongFunction<HttpRequest.Builder> initBuilderF =
l -> builderF.apply(l).method(methodF.apply(l), bodyPublisherF.apply(l));
initBuilderF = op.enhanceFuncOptionally(
initBuilderF, "version", String.class,
(b, v) -> b.version(HttpClient.Version.valueOf(
v.replaceAll("/1.1", "_1_1")
.replaceAll("/2.0", "_2")
)
)
);
initBuilderF = op.enhanceFuncOptionally(initBuilderF, "uri", String.class, (b, v) -> b.uri(URI.create(v)));
op.getOptionalStaticValue("follow_redirects",boolean.class);
/**
* Add header adders for any key provided in the op template which is capitalized
*/
List<String> headerNames = op.getDefinedNames().stream()
.filter(n -> n.charAt(0) >= 'A')
.filter(n -> n.charAt(0) <= 'Z')
.toList();
if (headerNames.size()>0) {
for (String headerName : headerNames) {
initBuilderF = op.enhanceFunc(initBuilderF,headerName,String.class, (b,h) -> b.header(headerName,h));
}
}
initBuilderF = op.enhanceFuncOptionally(initBuilderF,"timeout",long.class,(b,v) -> b.timeout(Duration.ofMillis(v)));
LongFunction<HttpRequest.Builder> finalInitBuilderF = initBuilderF;
LongFunction<HttpRequest> reqF = l -> finalInitBuilderF.apply(l).build();
Pattern ok_status = op.getOptionalStaticValue("ok-status",String.class)
.map(Pattern::compile)
.orElse(Pattern.compile(DEFAULT_OK_STATUS));
Pattern ok_body = op.getOptionalStaticValue("ok-body", String.class)
.map(Pattern::compile)
.orElse(null);
LongFunction<HttpOp> opFunc = cycle -> new HttpOp(
ctxF.apply(cycle).getClient(),
reqF.apply(cycle),
ok_status,
ok_body,
ctxF.apply(cycle),cycle
);
return opFunc;
}
@Override
public HttpOp apply(long value) {
HttpOp op = this.opFunc.apply(value);
return op;
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.adapter.http.core;
import io.nosqlbench.engine.api.activityimpl.OpDispenser;
import io.nosqlbench.engine.api.activityimpl.OpMapper;
import io.nosqlbench.engine.api.activityimpl.uniform.DriverSpaceCache;
import io.nosqlbench.engine.api.templating.ParsedOp;
import io.nosqlbench.nb.api.config.standard.NBConfiguration;
import java.util.function.LongFunction;
public class HttpOpMapper implements OpMapper<HttpOp> {
private final NBConfiguration cfg;
private final DriverSpaceCache<? extends HttpSpace> spaceCache;
public HttpOpMapper(NBConfiguration cfg, DriverSpaceCache<? extends HttpSpace> spaceCache) {
this.cfg = cfg;
this.spaceCache = spaceCache;
}
@Override
public OpDispenser<? extends HttpOp> apply(ParsedOp cmd) {
LongFunction<String> spaceNameF = cmd.getAsFunctionOr("space", "default");
LongFunction<HttpSpace> spaceFunc = l -> spaceCache.get(spaceNameF.apply(l));
return new HttpOpDispenser(spaceFunc,cmd);
}
}

View File

@ -0,0 +1,133 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.adapter.http.core;
import io.nosqlbench.api.NBNamedElement;
import io.nosqlbench.nb.api.config.standard.ConfigModel;
import io.nosqlbench.nb.api.config.standard.NBConfigModel;
import io.nosqlbench.nb.api.config.standard.NBConfiguration;
import io.nosqlbench.nb.api.config.standard.Param;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.net.http.HttpClient;
import java.time.Duration;
import java.util.List;
import java.util.Locale;
/**
* ThreadLocal http clients have been removed from this version, as the built-in
* HTTP client implementation is meant to be immutable. If shared-state issues
* occur, thread-local support will be re-added.
*/
public class HttpSpace implements NBNamedElement {
private final static Logger logger = LogManager.getLogger(HttpSpace.class);
private final String name;
private final NBConfiguration cfg;
private HttpConsoleFormats console;
private HttpClient.Redirect followRedirects;
private Duration timeout;
private long timeoutMillis;
private final HttpClient httpclient;
private int hdrDigits;
private HttpMetrics httpMetrics;
private boolean diagnosticsEnabled;
public HttpSpace(String spaceName, NBConfiguration cfg) {
this.name = spaceName;
this.cfg = cfg;
applyConfig(cfg);
this.httpclient = newClient();
}
public HttpClient getClient() {
return this.httpclient;
}
private HttpClient newClient() {
HttpClient.Builder builder = HttpClient.newBuilder();
logger.debug("follow_redirects=>" + followRedirects);
builder = builder.followRedirects(this.followRedirects);
builder = builder.connectTimeout(this.timeout);
return builder.build();
}
public synchronized void applyConfig(NBConfiguration cfg) {
this.followRedirects =
HttpClient.Redirect.valueOf(
cfg.get("follow_redirects").toUpperCase(Locale.ROOT)
);
this.timeout = Duration.ofMillis(cfg.get("timeout", long.class));
this.timeoutMillis = cfg.get("timeout", long.class);
this.httpMetrics = new HttpMetrics(this);
this.console = cfg.getOptional("diag").map(s -> HttpConsoleFormats.apply(s, this.console))
.orElseGet(() -> HttpConsoleFormats.apply(null,null));
this.diagnosticsEnabled = console.isDiagnosticMode();
}
public long getTimeoutMillis() {
return timeoutMillis;
}
public int getHdrDigits() {
return hdrDigits;
}
@Override
public String getName() {
return name;
}
public HttpMetrics getHttpMetrics() {
return httpMetrics;
}
public boolean isDiagnosticMode() {
return diagnosticsEnabled;
}
public HttpConsoleFormats getConsole() {
return console;
}
public static NBConfigModel getConfigModel() {
return ConfigModel.of(HttpSpace.class)
.add(Param.defaultTo("follow_redirects", "normal")
.setRegex("normal|always|never")
.setDescription("Whether to follow redirects. Normal redirects are those which do not " +
"redirect from HTTPS to HTTP.")
)
.add(Param.optional(List.of("diag","diagnostics"), String.class)
.setDescription("Print extended diagnostics. This option has numerous" +
" possible values. See the markdown docs for details. (nb help http)")
)
.add(Param.defaultTo("timeout", Long.MAX_VALUE)
.setDescription("How long to wait for requests before timeout out. Default is forever."))
.add(Param.defaultTo("hdr_digits", 4)
.setDescription("number of digits of precision to keep in HDR histograms"))
.asReadOnly();
}
}

View File

@ -14,18 +14,21 @@
* limitations under the License.
*/
package io.nosqlbench.activitytype.http;
package io.nosqlbench.adapter.http.errors;
import java.util.regex.Pattern;
public class InvalidResponseBodyException extends RuntimeException {
private final long cycleValue;
private final Pattern ok_body;
private final String body;
public InvalidResponseBodyException(long cycleValue, Pattern ok_body, String body) {
this.cycleValue = cycleValue;
public InvalidResponseBodyException(Pattern ok_body, String body) {
this.ok_body = ok_body;
this.body = body;
}
@Override
public String getMessage() {
return "Server returned body which failed content check with pattern '" + ok_body.pattern() + "'";
}
}

View File

@ -14,12 +14,11 @@
* limitations under the License.
*/
package io.nosqlbench.activitytype.http;
package io.nosqlbench.adapter.http.errors;
import java.util.regex.Pattern;
public class InvalidStatusCodeException extends RuntimeException {
private final long cycleValue;
private final Pattern ok_status;
private final int statusCode;
@ -28,8 +27,7 @@ public class InvalidStatusCodeException extends RuntimeException {
return "Server returned status code '" + statusCode + "' which did not match ok_status '" + ok_status.toString() + "'";
}
public InvalidStatusCodeException(long cycleValue, Pattern ok_status, int statusCode) {
this.cycleValue = cycleValue;
public InvalidStatusCodeException(Pattern ok_status, int statusCode) {
this.ok_status = ok_status;
this.statusCode = statusCode;
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.nosqlbench.activitytype.http.statuscodes;
package io.nosqlbench.adapter.http.statuscodes;
import io.nosqlbench.nb.api.content.Content;
import io.nosqlbench.nb.api.content.NBIO;

View File

@ -14,9 +14,9 @@
* limitations under the License.
*/
package io.nosqlbench.activitytype.http.statuscodes;
package io.nosqlbench.adapter.http.statuscodes;
enum HttpStatusRanges {
public enum HttpStatusRanges {
Informational("INFORMATIONAL", 100, 199, "Request received, continuing process"),
Success("SUCCESS",200, 299, "Request successfully received, understood, and accepted"),
Redirection("REDIRECTION", 300, 399, "Further action must be taken in order to complete the request."),

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.nosqlbench.activitytype.http.statuscodes;
package io.nosqlbench.adapter.http.statuscodes;
public class IetfStatusCode {
private final String values;

View File

@ -15,14 +15,14 @@ parameters and executed.
The simplest possible statement form looks like this:
```yaml
statement: http://google.com/
op: http://google.com/
```
Or, you can have a list:
```yaml
# A list of statements
statements:
ops:
- http://google.com/
- http://amazon.com/
```
@ -31,7 +31,7 @@ Or you can template the values used in the URI, and even add ratios:
```yaml
# A list of named statements with variable fields and specific ratios:
statements:
ops:
- s1: http://google.com/search?query={query}
ratio: 3
- s2: https://www.amazon.com/s?k={query}
@ -47,7 +47,7 @@ verification conditions:
```yaml
# Require that the result be status code 200-299 match regex "OK, account id is .*" in the body
statements:
ops:
- get-from-google:
method: GET
uri: "https://google.com/"
@ -64,7 +64,7 @@ parameters in a familiar and condensed form. A custom config parser makes
this form available fo rhose who want to emulate a well-known pattern:
```yaml
statements:
ops:
- s1: |
GET https://google.com/ HTTP/1.1
Content-Type: application/json
@ -81,7 +81,7 @@ All request fields can be made dynamic with binding functions. To make a
request that has all dynamic fields, you can do something like this:
```yaml
statements:
ops:
- s1: |
{method} {scheme}://{host}:{port}/{path}?{query} {version}
Content-Type: {content_type}
@ -95,7 +95,7 @@ interpreted internally as if you had configured your op template like
this:
```yaml
statements:
ops:
- method: { method }
uri: { scheme }://{host}:{port}/{path}?{query}
version: { version }
@ -129,7 +129,6 @@ defaults:
- **uri** - This is the URI that you might put into the URL bar of your
browser. There is no default.
Example: `https://en.wikipedia.org/wiki/Leonhard_Euler`
If the uri contains a question mark '?' as a query delimiter, then all
embedded sections which are contained within `URLENCODE[[` ... `]]`
sections are preprocessed by the HTTP driver. This allows you to keep
@ -207,10 +206,6 @@ undefined results.
## HTTP Activity Parameters
- **client_scope** - default: activity - One of activity, or thread. This
controls how many clients instances you use with an HTTP activity. By
default, all threads will use the same client instance.
- **follow_redirects** - default: normal - One of never, always, or
normal. Normal redirects are those which do not redirect from HTTPS to
HTTP.

View File

@ -16,6 +16,7 @@
package io.nosqlbench.activitytype.cmds;
import io.nosqlbench.adapter.http.core.HttpFormatParser;
import org.junit.jupiter.api.Test;
import java.util.Map;

View File

@ -14,9 +14,8 @@
* limitations under the License.
*/
package io.nosqlbench.activitytype.http;
package io.nosqlbench.activitytype.cmds;
public class ReadyHttpOpTest {
public enum ClientScope {
thread,
activity
}

View File

@ -16,6 +16,9 @@
package io.nosqlbench.activitytype.http.statuscodes;
import io.nosqlbench.adapter.http.statuscodes.HttpStatusCodes;
import io.nosqlbench.adapter.http.statuscodes.HttpStatusRanges;
import io.nosqlbench.adapter.http.statuscodes.IetfStatusCode;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

View File

@ -0,0 +1,142 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.adapter.http;
import io.nosqlbench.adapter.http.core.HttpOpMapper;
import io.nosqlbench.adapter.http.core.HttpSpace;
import io.nosqlbench.engine.api.activityconfig.StatementsLoader;
import io.nosqlbench.engine.api.activityconfig.yaml.OpTemplate;
import io.nosqlbench.engine.api.activityconfig.yaml.StmtsDocList;
import io.nosqlbench.engine.api.activityimpl.uniform.DriverSpaceCache;
import io.nosqlbench.engine.api.templating.ParsedOp;
import io.nosqlbench.nb.api.config.standard.NBConfiguration;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
public class HttpOpMapperTest {
static NBConfiguration cfg;
static HttpDriverAdapter adapter;
static HttpOpMapper mapper;
@BeforeAll
public static void initializeTestMapper() {
cfg = HttpSpace.getConfigModel().apply(Map.of());
adapter = new HttpDriverAdapter();
adapter.applyConfig(cfg);
DriverSpaceCache<? extends HttpSpace> cache = adapter.getSpaceCache();
mapper = new HttpOpMapper(cfg, cache);
}
private static ParsedOp parsedOpFor(String yaml) {
StmtsDocList docs = StatementsLoader.loadString(yaml, Map.of());
OpTemplate stmtDef = docs.getStmts().get(0);
ParsedOp parsedOp = new ParsedOp(stmtDef, cfg, List.of(adapter.getPreprocessor()));
return parsedOp;
}
@Test
public void testOnelineSpec() {
ParsedOp pop = parsedOpFor("""
ops:
- s1: method=get uri=http://localhost/
""");
assertThat(pop.getDefinedNames()).containsAll(List.of("method", "uri"));
}
@Test
public void testRFCFormMinimal() {
ParsedOp pop = parsedOpFor("""
ops:
- s1: get http://localhost/
""");
assertThat(pop.getDefinedNames()).containsAll(List.of("method", "uri"));
}
@Test
public void testRFCFormVersioned() {
ParsedOp pop = parsedOpFor("""
ops:
- s1: get http://localhost/ HTTP/1.1
""");
assertThat(pop.getDefinedNames()).containsAll(List.of("method", "uri", "version"));
}
@Test
public void testRFCFormHeaders() {
ParsedOp pop = parsedOpFor("""
ops:
- s1: |
get http://localhost/
Content-Type: application/json
""");
assertThat(pop.getDefinedNames()).containsAll(List.of("method", "uri", "Content-Type"));
}
@Test
public void testRFCFormBody() {
ParsedOp pop = parsedOpFor("""
statements:
- s1: |
get http://localhost/
body1
""");
assertThat(pop.getDefinedNames()).containsAll(List.of("method", "uri", "body"));
}
@Test
public void testRFCAllValuesTemplated() {
// This can not be fully resolved in the unit testing context, but it could be
// in the integrated testing context. It is sufficient to verify parsing here.
ParsedOp pop = parsedOpFor("""
statements:
- s1: |
{method} {scheme}://{host}/{path}?{query} {version}
Header1: {header1val}
{body}
bindings:
method: StaticStringMapper('test')
scheme: StaticStringMapper('test')
host: StaticStringMapper('test')
path: StaticStringMapper('test')
query: StaticStringMapper('test')
version: StaticStringMapper('test')
header1val: StaticStringMapper('test')
body: StaticStringMapper('test')
""");
System.out.println(pop);
assertThat(pop.getDefinedNames()).containsAll(List.of(
"method","uri","version","Header1","body"
));
}
}

View File

@ -1,53 +0,0 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.activitytype.cmds;
import io.nosqlbench.activitytype.http.async.HttpAsyncAction;
import java.net.http.HttpClient;
import java.util.function.LongFunction;
public class HttpAsyncOp {
public final HttpAsyncAction action;
public final LongFunction<? extends HttpOp> op;
public final long cycle;
private final HttpOp httpOp;
private final HttpClient client;
public HttpAsyncOp(HttpAsyncAction action, LongFunction<? extends HttpOp> op, long cycle, HttpClient client) {
this.action = action;
this.op = op;
this.cycle = cycle;
this.client = client;
this.httpOp = op.apply(cycle);
}
public HttpOp getOp() {
return httpOp;
}
public HttpClient getClient() {
return client;
}
public HttpAsyncAction getAction() {
return action;
}
}

View File

@ -1,35 +0,0 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.activitytype.cmds;
import io.nosqlbench.engine.api.activityimpl.uniform.flowtypes.Op;
import java.net.http.HttpRequest;
import java.util.regex.Pattern;
public class HttpOp implements Op {
public final Pattern ok_status;
public final Pattern ok_body;
public final HttpRequest request;
public HttpOp(HttpRequest request, Pattern ok_status, Pattern ok_body) {
this.request = request;
this.ok_status = ok_status;
this.ok_body = ok_body;
}
}

View File

@ -1,151 +0,0 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.activitytype.cmds;
import io.nosqlbench.engine.api.activityconfig.yaml.OpTemplate;
import io.nosqlbench.engine.api.activityimpl.BaseOpDispenser;
import io.nosqlbench.engine.api.templating.CommandTemplate;
import io.nosqlbench.nb.api.errors.BasicError;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
public class ReadyHttpOp extends BaseOpDispenser<HttpOp> {
private final CommandTemplate propertyTemplate;
public static final String DEFAULT_OK_BODY = ".+?";
public static final String DEFAULT_OK_STATUS = "2..";
// only populated if there is no value which is an actual bindings template
private final HttpOp cachedOp;
public ReadyHttpOp(OpTemplate stmtDef) {
super(stmtDef);
propertyTemplate = new CommandTemplate(stmtDef,
List.of(
HttpFormatParser::parseUrl,
HttpFormatParser::parseInline
)
);
sanityCheckUri();
if (propertyTemplate.isStatic()) {
cachedOp = apply(0);
} else {
cachedOp = null;
}
}
// :/?#[]@ !$&'()*+,;=
/**
* Try to catch situations in which the user put invalid characters in some part of the URI.
* In this case, the only safe thing to try seems to be to automatically urldecode
*/
private void sanityCheckUri() {
Map<String, String> command = propertyTemplate.getCommand(0L);
if (command.containsKey("uri")) {
String uriSpec = command.get("uri");
URI uri = null;
try {
uri = new URI(uriSpec);
} catch (URISyntaxException e) {
throw new BasicError(e.getMessage() + ", either use URLEncode in your bindings for values which could " +
"contain invalid URI characters, or modify the static portions of your op template to use the" +
" appropriate encodings.");
}
}
}
@Override
public HttpOp apply(long value) {
// If the request is invariant, simply return it, since it is thread-safe
if (this.cachedOp != null) {
return this.cachedOp;
}
Map<String, String> cmd = propertyTemplate.getCommand(value);
HttpRequest.Builder builder = HttpRequest.newBuilder();
HttpRequest.BodyPublisher bodyPublisher = cmd.containsKey("body") ?
HttpRequest.BodyPublishers.ofString(cmd.remove("body"))
: HttpRequest.BodyPublishers.noBody();
String method = cmd.containsKey("method") ? cmd.remove("method") : "GET";
builder.method(method, bodyPublisher);
if (cmd.containsKey("version")) {
String versionName = cmd.remove("version")
.replaceAll("/1.1", "_1_1")
.replaceAll("/2.0", "_2");
HttpClient.Version version = HttpClient.Version.valueOf(versionName);
builder.version(version);
}
if (cmd.containsKey("uri")) {
URI uri = URI.create(cmd.remove("uri"));
builder.uri(uri);
}
Pattern ok_status = Pattern.compile(Optional.ofNullable(cmd.remove("ok-status")).orElse(DEFAULT_OK_STATUS));
Pattern ok_body = Optional.ofNullable(cmd.remove("ok-body")).map(Pattern::compile).orElse(null);
String timeoutStr = cmd.remove("timeout");
if (timeoutStr != null) {
builder.timeout(Duration.of(Long.parseLong(timeoutStr), ChronoUnit.MILLIS));
}
// At this point, the only things left in the list must be headers,
// but we check them for upper-case conventions as a sanity check for the user
for (String headerName : cmd.keySet()) {
if (headerName.charAt(0) >= 'A' && headerName.charAt(0) <= 'Z') {
String headerValue = cmd.get(headerName);
builder = builder.header(headerName, headerValue);
} else {
throw new BasicError("HTTP request parameter '" + headerName + "' was not recognized as a basic request parameter, and it is not capitalized to indicate that it is a header.");
}
}
// cmd.clear();
// if (cmd.size()>0) {
// throw new BasicError("Some provided request fields were not used: " + cmd.toString());
// }
//
HttpRequest request = builder.build();
return new HttpOp(request, ok_status, ok_body);
}
@Override
public String toString() {
return "ReadyHttpOp{" +
"template=" + propertyTemplate +
", cachedOp=" + cachedOp +
'}';
}
}

View File

@ -1,183 +0,0 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.activitytype.http;
import com.codahale.metrics.Timer;
import io.nosqlbench.activitytype.cmds.HttpOp;
import io.nosqlbench.engine.api.activityapi.core.SyncAction;
import io.nosqlbench.engine.api.activityapi.errorhandling.modular.ErrorDetail;
import io.nosqlbench.engine.api.activityapi.planning.OpSequence;
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
import io.nosqlbench.engine.api.activityimpl.OpDispenser;
import io.nosqlbench.nb.api.errors.BasicError;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.FileNotFoundException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Path;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.LongFunction;
public class HttpAction implements SyncAction {
private final static Logger logger = LogManager.getLogger(HttpAction.class);
private final HttpActivity httpActivity;
private final int slot;
private int maxTries = 1;
private OpSequence<OpDispenser<? extends HttpOp>> sequencer;
private HttpClient client;
private final HttpResponse.BodyHandler<String> bodyreader = HttpResponse.BodyHandlers.ofString();
public HttpAction(ActivityDef activityDef, int slot, HttpActivity httpActivity) {
this.slot = slot;
this.httpActivity = httpActivity;
}
@Override
public void init() {
this.sequencer = httpActivity.getSequencer();
this.client = initClient(httpActivity.getClientScope());
this.maxTries = httpActivity.getActivityDef().getParams().getOptionalInteger("maxtries").orElse(10);
}
private HttpClient initClient(ClientScope clientScope) {
return httpActivity.getClient().apply(Thread.currentThread());
}
@Override
public int runCycle(long cycle) {
int tries = 0;
// The request to be used must be constructed from the template each time.
HttpOp httpOp = null;
// The bind timer captures all the time involved in preparing the
// operation for execution, including data generation as well as
// op construction
try (Timer.Context bindTime = httpActivity.bindTimer.time()) {
LongFunction<? extends HttpOp> readyOp = sequencer.apply(cycle);
httpOp = readyOp.apply(cycle);
} catch (Exception e) {
if (httpActivity.isDiagnosticMode()) {
if (httpOp != null) {
httpActivity.console.summarizeRequest("ERRORED REQUEST", e, httpOp.request, System.out, cycle,
System.nanoTime());
} else {
System.out.println("---- REQUEST was null");
}
}
throw new RuntimeException("while binding request in cycle " + cycle + ": " + e.getMessage(), e);
} finally {
}
int resultCode=0;
while (tries < maxTries) {
tries++;
CompletableFuture<HttpResponse<String>> responseFuture;
try (Timer.Context executeTime = httpActivity.executeTimer.time()) {
responseFuture = client.sendAsync(httpOp.request, this.bodyreader);
} catch (Exception e) {
throw new RuntimeException("while waiting for response in cycle " + cycle + ":" + e.getMessage(), e);
}
HttpResponse<String> response = null;
long startat = System.nanoTime();
Exception error = null;
try {
response = responseFuture.get(httpActivity.getTimeoutMillis(), TimeUnit.MILLISECONDS);
httpActivity.statusCodeHisto.update(response.statusCode());
if (httpOp.ok_status != null) {
if (!httpOp.ok_status.matcher(String.valueOf(response.statusCode())).matches()) {
throw new InvalidStatusCodeException(cycle, httpOp.ok_status, response.statusCode());
}
}
if (httpOp.ok_body != null) {
if (!httpOp.ok_body.matcher(response.body()).matches()) {
throw new InvalidResponseBodyException(cycle, httpOp.ok_body, response.body());
}
}
} catch (Exception e) {
error = e;
// error = new RuntimeException("while waiting for response in cycle " + cycleValue + ":" + e.getMessage(), e);
} finally {
long nanos = System.nanoTime() - startat;
httpActivity.resultTimer.update(nanos, TimeUnit.NANOSECONDS);
if (error == null) {
httpActivity.resultSuccessTimer.update(nanos, TimeUnit.NANOSECONDS);
}
if (httpActivity.isDiagnosticMode()) {
httpActivity.console.summarizeRequest("request", error, httpOp.request, System.out, cycle, nanos);
if (response != null) {
httpActivity.console.summarizeResponseChain(error, response, System.out, cycle, nanos);
} else {
System.out.println("---- RESPONSE was null");
}
System.out.println();
}
// TODO: use this as a documented example for how to add error handling to a new activity
if (error == null) {
break; // break out of the tries loop without retrying, because there was no error
} else {
// count and log exception types
ErrorDetail detail = httpActivity.getErrorHandler().handleError(error, cycle, nanos);
resultCode=detail.resultCode;
if (!detail.isRetryable()) {
break; // break out of the tries loop without retrying, because the error handler said so
}
}
}
}
httpActivity.triesHisto.update(tries);
return resultCode;
}
private HttpRequest.BodyPublisher bodySourceFrom(Map<String, String> cmdMap) {
if (cmdMap.containsKey("body")) {
String body = cmdMap.remove("body");
return HttpRequest.BodyPublishers.ofString(body);
} else if (cmdMap.containsKey("file")) {
try {
String file = cmdMap.get("file");
Path path = Path.of(file);
return HttpRequest.BodyPublishers.ofFile(path);
} catch (FileNotFoundException e) {
throw new BasicError("Could not find file content for request at " + cmdMap.get("file"));
}
} else {
return HttpRequest.BodyPublishers.noBody();
}
}
}

View File

@ -1,147 +0,0 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.activitytype.http;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Timer;
import io.nosqlbench.activitytype.cmds.HttpOp;
import io.nosqlbench.activitytype.cmds.ReadyHttpOp;
import io.nosqlbench.engine.api.activityapi.core.Activity;
import io.nosqlbench.engine.api.activityapi.core.ActivityDefObserver;
import io.nosqlbench.engine.api.activityapi.errorhandling.modular.NBErrorHandler;
import io.nosqlbench.engine.api.activityapi.planning.OpSequence;
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
import io.nosqlbench.engine.api.activityimpl.OpDispenser;
import io.nosqlbench.engine.api.activityimpl.SimpleActivity;
import io.nosqlbench.engine.api.metrics.ActivityMetrics;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.net.http.HttpClient;
import java.util.function.Function;
public class HttpActivity extends SimpleActivity implements Activity, ActivityDefObserver {
private final static Logger logger = LogManager.getLogger(HttpActivity.class);
private final ActivityDef activityDef;
public HttpConsoleFormats console;
// Used when sclientScope == ClientScope.activity
private HttpClient activityClient;
private ClientScope clientScope = ClientScope.activity;
public Timer bindTimer;
public Timer executeTimer;
public Histogram triesHisto;
public Timer resultTimer;
public Meter rowCounter;
public Histogram skippedTokens;
public Timer resultSuccessTimer;
public Histogram statusCodeHisto;
private OpSequence<OpDispenser<? extends HttpOp>> sequencer;
private boolean diagnosticsEnabled;
private long timeout = Long.MAX_VALUE;
private NBErrorHandler errorhandler;
public HttpActivity(ActivityDef activityDef) {
super(activityDef);
this.activityDef = activityDef;
}
@Override
public void initActivity() {
super.initActivity();
bindTimer = ActivityMetrics.timer(activityDef, "bind",this.getHdrDigits());
executeTimer = ActivityMetrics.timer(activityDef, "execute", this.getHdrDigits());
resultTimer = ActivityMetrics.timer(activityDef, "result", this.getHdrDigits());
triesHisto = ActivityMetrics.histogram(activityDef, "tries", this.getHdrDigits());
rowCounter = ActivityMetrics.meter(activityDef, "rows");
statusCodeHisto = ActivityMetrics.histogram(activityDef, "statuscode",this.getHdrDigits());
skippedTokens = ActivityMetrics.histogram(activityDef, "skipped-tokens",this.getHdrDigits());
resultSuccessTimer = ActivityMetrics.timer(activityDef, "result-success", this.getHdrDigits());
this.sequencer = createOpSequence(ReadyHttpOp::new, false);
setDefaultsFromOpSequence(sequencer);
onActivityDefUpdate(activityDef);
}
@Override
public synchronized void onActivityDefUpdate(ActivityDef activityDef) {
super.onActivityDefUpdate(activityDef);
this.console = getParams().getOptionalString("diag")
.map(s -> HttpConsoleFormats.apply(s, this.console))
.orElseGet(() -> HttpConsoleFormats.apply(null, null));
this.diagnosticsEnabled = console.isDiagnosticMode();
this.timeout = getParams().getOptionalLong("timeout").orElse(Long.MAX_VALUE);
getParams().getOptionalString("client_scope")
.map(ClientScope::valueOf)
.ifPresent(this::setClientScope);
}
public long getTimeoutMillis() {
return timeout;
}
private void setClientScope(ClientScope clientScope) {
this.clientScope = clientScope;
}
public ClientScope getClientScope() {
return clientScope;
}
public synchronized Function<Thread, HttpClient> getClient() {
switch (getClientScope()) {
case thread:
return t -> newClient();
case activity:
if (this.activityClient == null) {
this.activityClient = newClient();
}
return t -> this.activityClient;
default:
throw new RuntimeException("unable to recognize client scope: " + getClientScope());
}
}
public HttpClient newClient() {
HttpClient.Builder builder = HttpClient.newBuilder();
HttpClient.Redirect follow_redirects = getParams().getOptionalString("follow_redirects")
.map(String::toUpperCase)
.map(HttpClient.Redirect::valueOf)
.map(r -> {
logger.debug("follow_redirects=>" + r);
return r;
}).orElse(HttpClient.Redirect.NORMAL);
builder = builder.followRedirects(follow_redirects);
return builder.build();
}
public OpSequence<OpDispenser<? extends HttpOp>> getSequencer() {
return sequencer;
}
public boolean isDiagnosticMode() {
return diagnosticsEnabled;
}
}

View File

@ -1,43 +0,0 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.activitytype.http;
import io.nosqlbench.engine.api.activityapi.core.ActionDispenser;
import io.nosqlbench.engine.api.activityapi.core.ActivityType;
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
import io.nosqlbench.nb.annotations.Service;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Service(value = ActivityType.class, selector = "http")
public class HttpActivityType implements ActivityType<HttpActivity> {
private static final Logger logger = LogManager.getLogger(HttpActivityType.class);
@Override
public ActionDispenser getActionDispenser(HttpActivity activity) {
if (activity.getParams().getOptionalString("async").isPresent()) {
throw new RuntimeException("The async http driver is not online yet.");
}
return new HttpActionDispenser(activity);
}
@Override
public HttpActivity getActivity(ActivityDef activityDef) {
return new HttpActivity(activityDef);
}
}

View File

@ -1,22 +0,0 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.activitytype.http;
public class ResponseError extends RuntimeException {
public ResponseError(String s) {
}
}

View File

@ -1,77 +0,0 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.activitytype.http.async;
import io.nosqlbench.activitytype.cmds.HttpAsyncOp;
import io.nosqlbench.activitytype.cmds.HttpOp;
import io.nosqlbench.activitytype.http.HttpActivity;
import io.nosqlbench.engine.api.activityapi.core.BaseAsyncAction;
import io.nosqlbench.engine.api.activityapi.core.ops.fluent.opfacets.TrackedOp;
import io.nosqlbench.engine.api.activityapi.planning.OpSequence;
import io.nosqlbench.engine.api.activityimpl.OpDispenser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.net.http.HttpClient;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;
import java.util.function.LongFunction;
public class HttpAsyncAction extends BaseAsyncAction<HttpAsyncOp, HttpActivity> {
private final static Logger logger = LogManager.getLogger(HttpAsyncAction.class);
private OpSequence<OpDispenser<? extends HttpOp>> sequencer;
private HttpClient client;
private CompletableFuture<HttpResponse<Void>> future;
public HttpAsyncAction(HttpActivity httpActivity, int slot) {
super(httpActivity,slot);
}
@Override
public void startOpCycle(TrackedOp<HttpAsyncOp> opc) {
HttpAsyncOp opdata = opc.getOpData();
HttpOp op = opdata.getOp();
opc.start();
future = opdata.getAction().client.sendAsync(op.request, HttpResponse.BodyHandlers.discarding());
}
public void init() {
this.sequencer = activity.getSequencer();
this.client = activity.getClient().apply(Thread.currentThread());
}
@Override
public LongFunction<HttpAsyncOp> getOpInitFunction() {
return l -> {
LongFunction<? extends HttpOp> readyHttpOp = sequencer.apply(l);
return new HttpAsyncOp(this,readyHttpOp,l,client);
};
}
// @Override
// public boolean enqueue(TrackedOp<HttpAsyncOp> opc) {
// HttpAsyncOp opData = opc.getOpData();
// opData.op.
// return false;
// }
}

View File

@ -1,140 +0,0 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.activitytype.cmds;
import io.nosqlbench.engine.api.activityconfig.StatementsLoader;
import io.nosqlbench.engine.api.activityconfig.yaml.OpTemplate;
import io.nosqlbench.engine.api.activityconfig.yaml.StmtsDocList;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
public class ReadyHttpOpTest {
@Test
public void testOnelineSpec() {
StmtsDocList docs = StatementsLoader.loadString("" +
"statements:\n" +
" - s1: method=get uri=http://localhost/\n",
Map.of()
);
OpTemplate stmtDef = docs.getStmts().get(0);
ReadyHttpOp readyReq = new ReadyHttpOp(stmtDef);
HttpOp staticReq = readyReq.apply(3);
}
@Test
public void testRFCFormMinimal() {
StmtsDocList docs = StatementsLoader.loadString("" +
"statements:\n" +
" - s1: get http://localhost/",
Map.of()
);
OpTemplate stmtDef = docs.getStmts().get(0);
ReadyHttpOp readyReq = new ReadyHttpOp(stmtDef);
HttpOp staticReq = readyReq.apply(3);
}
@Test
public void testRFCFormVersioned() {
StmtsDocList docs = StatementsLoader.loadString("" +
"statements:\n" +
" - s1: get http://localhost/ HTTP/1.1",
Map.of()
);
OpTemplate stmtDef = docs.getStmts().get(0);
ReadyHttpOp readyReq = new ReadyHttpOp(stmtDef);
HttpOp staticReq = readyReq.apply(3);
}
@Test
public void testRFCFormHeaders() {
StmtsDocList docs = StatementsLoader.loadString("" +
"statements:\n" +
" - s1: |\n" +
" get http://localhost/\n" +
" Content-Type: application/json" +
"",
Map.of()
);
OpTemplate stmtDef = docs.getStmts().get(0);
ReadyHttpOp readyReq = new ReadyHttpOp(stmtDef);
HttpOp staticReq = readyReq.apply(3);
}
@Test
public void testRFCFormBody() {
StmtsDocList docs = StatementsLoader.loadString("" +
"statements:\n" +
" - s1: |\n" +
" get http://localhost/\n" +
" \n" +
" body1",
Map.of()
);
OpTemplate stmtDef = docs.getStmts().get(0);
ReadyHttpOp readyReq = new ReadyHttpOp(stmtDef);
HttpOp staticReq = readyReq.apply(3);
}
@Test
public void testRFCAllValuesTemplated() {
// This can not be fully resolved in the unit testing context, but it could be
// in the integrated testing context. It is sufficient to verify parsing here.
StmtsDocList docs = StatementsLoader.loadString("" +
"statements:\n" +
" - s1: |\n" +
" {method} {scheme}://{host}/{path}?{query} {version}\n" +
" Header1: {header1val}\n" +
" \n" +
" {body}\n" +
"\n" +
"bindings: \n" +
" method: StaticString('test')\n" +
" scheme: StaticString('test')\n" +
" host: StaticString('test')\n" +
" path: StaticString('test')\n" +
" query: StaticString('test')\n" +
" version: StaticString('test')\n" +
" header1val: StaticString('test')\n" +
" body: StaticString('test')\n",
Map.of()
);
OpTemplate stmtDef = docs.getStmts().get(0);
Map<String, String> parse = HttpFormatParser.parseInline(stmtDef.getStmt().orElseThrow());
assertThat(parse).containsAllEntriesOf(
Map.of(
"method", "{method}",
"uri", "{scheme}://{host}/{path}?{query}",
"version", "{version}",
"Header1","{header1val}",
"body","{body}"
)
);
}
}

View File

@ -1,35 +0,0 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.nosqlbench.activitytype.http;
import io.nosqlbench.engine.api.activityapi.core.Action;
import io.nosqlbench.engine.api.activityapi.core.ActionDispenser;
import io.nosqlbench.engine.api.activityimpl.ActivityDef;
import org.junit.jupiter.api.Test;
public class HttpActivityTypeTest {
@Test
public void testHttpActivity() {
HttpActivityType httpAt = new HttpActivityType();
ActivityDef ad = ActivityDef.parseActivityDef("driver=http; yaml=http-google.yaml; port=80; cycles=1;");
HttpActivity httpActivity = httpAt.getActivity(ad);
httpActivity.initActivity();
ActionDispenser actionDispenser = httpAt.getActionDispenser(httpActivity);
Action action = actionDispenser.getAction(1);
}
}

View File

@ -70,6 +70,12 @@
<version>4.17.15-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.nosqlbench</groupId>
<artifactId>adapter-http</artifactId>
<version>4.17.15-SNAPSHOT</version>
</dependency>
</dependencies>
<build>

View File

@ -54,6 +54,7 @@
<module>adapters-api</module>
<!-- driver modules -->
<module>adapter-http</module>
<module>adapter-cqld4</module>
<module>adapter-dynamodb</module>
<module>adapter-diag</module>
@ -85,7 +86,6 @@
<modules>
<module>nb</module>
<module>driver-tcp</module>
<module>driver-http</module>
<module>driver-kafka</module>
<module>driver-jmx</module>
<module>driver-jdbc</module>