[ASTERIXDB-2013][HYR] non-POST x-www-form-urlencoded, config null value handling
- handle null config values when marshall/unmarshalling
- support non-post x-www-form-urlencoded directly
- don't assume POSTs are x-www-form-urlencoded
- add support to TestExecutor for POST/PUT form-urlencoded parameters
Change-Id: I670b815a5276d870f7d538d1ce9d8bef2d0fcf4f
Reviewed-on: https://asterix-gerrit.ics.uci.edu/1911
Reviewed-by: Till Westmann <tillw@apache.org>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Contrib: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryResultApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryResultApiServlet.java
index 42e23ba..901aff8 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryResultApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryResultApiServlet.java
@@ -41,7 +41,7 @@
public class QueryResultApiServlet extends AbstractQueryApiServlet {
private static final Logger LOGGER = Logger.getLogger(QueryResultApiServlet.class.getName());
- public QueryResultApiServlet(ConcurrentMap<String, Object> ctx, String[] paths, IApplicationContext appCtx) {
+ public QueryResultApiServlet(ConcurrentMap<String, Object> ctx, IApplicationContext appCtx, String... paths) {
super(appCtx, ctx, paths);
}
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
index 9ee064e..1cec616 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
@@ -62,8 +62,6 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
-
-import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
public class QueryServiceServlet extends AbstractQueryApiServlet {
@@ -317,9 +315,7 @@
}
private RequestParameters getRequestParameters(IServletRequest request) throws IOException {
- final String contentTypeParam = request.getHttpRequest().headers().get(HttpHeaderNames.CONTENT_TYPE);
- int sep = contentTypeParam.indexOf(';');
- final String contentType = sep < 0 ? contentTypeParam.trim() : contentTypeParam.substring(0, sep).trim();
+ final String contentType = HttpUtil.getContentTypeOnly(request);
RequestParameters param = new RequestParameters();
param.host = host(request);
param.path = servletPath(request);
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryStatusApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryStatusApiServlet.java
index 71dddc0..cec65f7 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryStatusApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryStatusApiServlet.java
@@ -41,7 +41,7 @@
public class QueryStatusApiServlet extends AbstractQueryApiServlet {
private static final Logger LOGGER = Logger.getLogger(QueryStatusApiServlet.class.getName());
- public QueryStatusApiServlet(ConcurrentMap<String, Object> ctx, String[] paths, IApplicationContext appCtx) {
+ public QueryStatusApiServlet(ConcurrentMap<String, Object> ctx, IApplicationContext appCtx, String... paths) {
super(appCtx, ctx, paths);
}
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/hyracks/bootstrap/CCApplication.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/hyracks/bootstrap/CCApplication.java
index 3627974..e8636c8 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/hyracks/bootstrap/CCApplication.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/hyracks/bootstrap/CCApplication.java
@@ -287,9 +287,9 @@
case Servlets.RUNNING_REQUESTS:
return new QueryCancellationServlet(ctx, paths);
case Servlets.QUERY_STATUS:
- return new QueryStatusApiServlet(ctx, paths, appCtx);
+ return new QueryStatusApiServlet(ctx, appCtx, paths);
case Servlets.QUERY_RESULT:
- return new QueryResultApiServlet(ctx, paths, appCtx);
+ return new QueryResultApiServlet(ctx, appCtx, paths);
case Servlets.QUERY_SERVICE:
return new QueryServiceServlet(ctx, paths, appCtx, SQLPP,
ccExtensionManager.getCompilationProvider(SQLPP), getStatementExecutorFactory(),
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestExecutor.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestExecutor.java
index 10b528f..1079832 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestExecutor.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/TestExecutor.java
@@ -65,6 +65,7 @@
import org.apache.asterix.testframework.context.TestFileContext;
import org.apache.asterix.testframework.xml.ComparisonEnum;
import org.apache.asterix.testframework.xml.TestCase.CompilationUnit;
+import org.apache.asterix.testframework.xml.TestCase.CompilationUnit.Parameter;
import org.apache.asterix.testframework.xml.TestGroup;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
@@ -102,16 +103,17 @@
// see
// https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers/417184
private static final long MAX_URL_LENGTH = 2000l;
- private static final Pattern JAVA_BLOCK_COMMENT_PATTERN =
- Pattern.compile("/\\*.*\\*/", Pattern.MULTILINE | Pattern.DOTALL);
+ private static final Pattern JAVA_BLOCK_COMMENT_PATTERN = Pattern.compile("/\\*.*\\*/",
+ Pattern.MULTILINE | Pattern.DOTALL);
private static final Pattern JAVA_LINE_COMMENT_PATTERN = Pattern.compile("//.*$", Pattern.MULTILINE);
private static final Pattern SHELL_LINE_COMMENT_PATTERN = Pattern.compile("#.*$", Pattern.MULTILINE);
private static final Pattern REGEX_LINES_PATTERN = Pattern.compile("^(-)?/(.*)/([im]*)$");
- private static final Pattern POLL_TIMEOUT_PATTERN =
- Pattern.compile("polltimeoutsecs=(\\d+)(\\D|$)", Pattern.MULTILINE);
+ private static final Pattern POLL_TIMEOUT_PATTERN = Pattern.compile("polltimeoutsecs=(\\d+)(\\D|$)",
+ Pattern.MULTILINE);
private static final Pattern POLL_DELAY_PATTERN = Pattern.compile("polldelaysecs=(\\d+)(\\D|$)", Pattern.MULTILINE);
private static final Pattern HANDLE_VARIABLE_PATTERN = Pattern.compile("handlevariable=(\\w+)");
private static final Pattern VARIABLE_REF_PATTERN = Pattern.compile("\\$(\\w+)");
+ private static final Pattern HTTP_PARAM_PATTERN = Pattern.compile("param (\\w+)=(.*)", Pattern.MULTILINE);
public static final int TRUNCATE_THRESHOLD = 16384;
@@ -166,10 +168,10 @@
public void runScriptAndCompareWithResult(File scriptFile, PrintWriter print, File expectedFile, File actualFile,
ComparisonEnum compare) throws Exception {
System.err.println("Expected results file: " + expectedFile.toString());
- BufferedReader readerExpected =
- new BufferedReader(new InputStreamReader(new FileInputStream(expectedFile), "UTF-8"));
- BufferedReader readerActual =
- new BufferedReader(new InputStreamReader(new FileInputStream(actualFile), "UTF-8"));
+ BufferedReader readerExpected = new BufferedReader(
+ new InputStreamReader(new FileInputStream(expectedFile), "UTF-8"));
+ BufferedReader readerActual = new BufferedReader(
+ new InputStreamReader(new FileInputStream(actualFile), "UTF-8"));
boolean regex = false;
try {
if (ComparisonEnum.BINARY.equals(compare)) {
@@ -352,10 +354,10 @@
public void runScriptAndCompareWithResultRegex(File scriptFile, File expectedFile, File actualFile)
throws Exception {
String lineExpected, lineActual;
- try (BufferedReader readerExpected =
- new BufferedReader(new InputStreamReader(new FileInputStream(expectedFile), "UTF-8"));
- BufferedReader readerActual =
- new BufferedReader(new InputStreamReader(new FileInputStream(actualFile), "UTF-8"))) {
+ try (BufferedReader readerExpected = new BufferedReader(
+ new InputStreamReader(new FileInputStream(expectedFile), "UTF-8"));
+ BufferedReader readerActual = new BufferedReader(
+ new InputStreamReader(new FileInputStream(actualFile), "UTF-8"))) {
StringBuilder actual = new StringBuilder();
while ((lineActual = readerActual.readLine()) != null) {
actual.append(lineActual).append('\n');
@@ -510,8 +512,7 @@
}
}
- public InputStream executeQuery(String str, OutputFormat fmt, URI uri, List<CompilationUnit.Parameter> params)
- throws Exception {
+ public InputStream executeQuery(String str, OutputFormat fmt, URI uri, List<Parameter> params) throws Exception {
HttpUriRequest method = constructHttpMethod(str, uri, "query", false, params);
// Set accepted output response type
method.setHeader("Accept", fmt.mimeType());
@@ -523,21 +524,19 @@
return executeQueryService(str, fmt, uri, new ArrayList<>(), false);
}
- public InputStream executeQueryService(String str, OutputFormat fmt, URI uri,
- List<CompilationUnit.Parameter> params, boolean jsonEncoded) throws Exception {
+ public InputStream executeQueryService(String str, OutputFormat fmt, URI uri, List<Parameter> params,
+ boolean jsonEncoded) throws Exception {
return executeQueryService(str, fmt, uri, params, jsonEncoded, null, false);
}
- public InputStream executeQueryService(String str, OutputFormat fmt, URI uri,
- List<CompilationUnit.Parameter> params, boolean jsonEncoded, Predicate<Integer> responseCodeValidator)
- throws Exception {
+ public InputStream executeQueryService(String str, OutputFormat fmt, URI uri, List<Parameter> params,
+ boolean jsonEncoded, Predicate<Integer> responseCodeValidator) throws Exception {
return executeQueryService(str, fmt, uri, params, jsonEncoded, responseCodeValidator, false);
}
- protected InputStream executeQueryService(String str, OutputFormat fmt, URI uri,
- List<CompilationUnit.Parameter> params, boolean jsonEncoded, Predicate<Integer> responseCodeValidator,
- boolean cancellable) throws Exception {
- final List<CompilationUnit.Parameter> newParams = upsertParam(params, "format", fmt.mimeType());
+ protected InputStream executeQueryService(String str, OutputFormat fmt, URI uri, List<Parameter> params,
+ boolean jsonEncoded, Predicate<Integer> responseCodeValidator, boolean cancellable) throws Exception {
+ final List<Parameter> newParams = upsertParam(params, "format", fmt.mimeType());
HttpUriRequest method = jsonEncoded ? constructPostMethodJson(str, uri, "statement", newParams)
: constructPostMethodUrl(str, uri, "statement", newParams);
// Set accepted output response type
@@ -549,12 +548,11 @@
return response.getEntity().getContent();
}
- protected List<CompilationUnit.Parameter> upsertParam(List<CompilationUnit.Parameter> params, String name,
- String value) {
+ protected List<Parameter> upsertParam(List<Parameter> params, String name, String value) {
boolean replaced = false;
- List<CompilationUnit.Parameter> result = new ArrayList<>();
- for (CompilationUnit.Parameter param : params) {
- CompilationUnit.Parameter newParam = new CompilationUnit.Parameter();
+ List<Parameter> result = new ArrayList<>();
+ for (Parameter param : params) {
+ Parameter newParam = new Parameter();
newParam.setName(param.getName());
if (name.equals(param.getName())) {
newParam.setValue(value);
@@ -565,7 +563,7 @@
result.add(newParam);
}
if (!replaced) {
- CompilationUnit.Parameter newParam = new CompilationUnit.Parameter();
+ Parameter newParam = new Parameter();
newParam.setName(name);
newParam.setValue(value);
result.add(newParam);
@@ -574,7 +572,7 @@
}
private HttpUriRequest constructHttpMethod(String statement, URI uri, String stmtParam, boolean postStmtAsParam,
- List<CompilationUnit.Parameter> otherParams) throws URISyntaxException {
+ List<Parameter> otherParams) throws URISyntaxException {
if (statement.length() + uri.toString().length() < MAX_URL_LENGTH) {
// Use GET for small-ish queries
return constructGetMethod(uri, upsertParam(otherParams, stmtParam, statement));
@@ -585,32 +583,49 @@
}
}
- private HttpUriRequest constructGetMethod(URI endpoint, List<CompilationUnit.Parameter> params) {
+ private HttpUriRequest constructGetMethod(URI endpoint, List<Parameter> params) {
RequestBuilder builder = RequestBuilder.get(endpoint);
- for (CompilationUnit.Parameter param : params) {
+ for (Parameter param : params) {
builder.addParameter(param.getName(), param.getValue());
}
builder.setCharset(StandardCharsets.UTF_8);
return builder.build();
}
- private HttpUriRequest constructGetMethod(URI endpoint, OutputFormat fmt, List<CompilationUnit.Parameter> params) {
+ private HttpUriRequest buildRequest(String method, URI uri, List<Parameter> params) {
+ RequestBuilder builder = RequestBuilder.create(method);
+ builder.setUri(uri);
+ for (Parameter param : params) {
+ builder.addParameter(param.getName(), param.getValue());
+ }
+ builder.setCharset(StandardCharsets.UTF_8);
+ return builder.build();
+ }
+
+ private HttpUriRequest buildRequest(String method, URI uri, OutputFormat fmt, List<Parameter> params) {
+ HttpUriRequest request = buildRequest(method, uri, params);
+ // Set accepted output response type
+ request.setHeader("Accept", fmt.mimeType());
+ return request;
+ }
+
+ private HttpUriRequest constructGetMethod(URI endpoint, OutputFormat fmt, List<Parameter> params) {
HttpUriRequest method = constructGetMethod(endpoint, params);
// Set accepted output response type
method.setHeader("Accept", fmt.mimeType());
return method;
}
- private HttpUriRequest constructPostMethod(URI uri, List<CompilationUnit.Parameter> params) {
+ private HttpUriRequest constructPostMethod(URI uri, List<Parameter> params) {
RequestBuilder builder = RequestBuilder.post(uri);
- for (CompilationUnit.Parameter param : params) {
+ for (Parameter param : params) {
builder.addParameter(param.getName(), param.getValue());
}
builder.setCharset(StandardCharsets.UTF_8);
return builder.build();
}
- private HttpUriRequest constructPostMethod(URI uri, OutputFormat fmt, List<CompilationUnit.Parameter> params) {
+ private HttpUriRequest constructPostMethod(URI uri, OutputFormat fmt, List<Parameter> params) {
HttpUriRequest method = constructPostMethod(uri, params);
// Set accepted output response type
method.setHeader("Accept", fmt.mimeType());
@@ -618,10 +633,10 @@
}
protected HttpUriRequest constructPostMethodUrl(String statement, URI uri, String stmtParam,
- List<CompilationUnit.Parameter> otherParams) {
+ List<Parameter> otherParams) {
RequestBuilder builder = RequestBuilder.post(uri);
if (stmtParam != null) {
- for (CompilationUnit.Parameter param : upsertParam(otherParams, stmtParam, statement)) {
+ for (Parameter param : upsertParam(otherParams, stmtParam, statement)) {
builder.addParameter(param.getName(), param.getValue());
}
builder.addParameter(stmtParam, statement);
@@ -634,14 +649,14 @@
}
protected HttpUriRequest constructPostMethodJson(String statement, URI uri, String stmtParam,
- List<CompilationUnit.Parameter> otherParams) {
+ List<Parameter> otherParams) {
if (stmtParam == null) {
throw new NullPointerException("Statement parameter required.");
}
RequestBuilder builder = RequestBuilder.post(uri);
ObjectMapper om = new ObjectMapper();
ObjectNode content = om.createObjectNode();
- for (CompilationUnit.Parameter param : upsertParam(otherParams, stmtParam, statement)) {
+ for (Parameter param : upsertParam(otherParams, stmtParam, statement)) {
content.put(param.getName(), param.getValue());
}
try {
@@ -654,23 +669,26 @@
}
public InputStream executeJSONGet(OutputFormat fmt, URI uri) throws Exception {
- return executeJSONGet(fmt, uri, code -> code == HttpStatus.SC_OK);
+ return executeJSON(fmt, "GET", uri, Collections.emptyList());
}
- public InputStream executeJSONGet(OutputFormat fmt, URI uri, Predicate<Integer> responseCodeValidator)
+ public InputStream executeJSONGet(OutputFormat fmt, URI uri, List<Parameter> params,
+ Predicate<Integer> responseCodeValidator) throws Exception {
+ return executeJSON(fmt, "GET", uri, params, responseCodeValidator);
+ }
+
+ public InputStream executeJSON(OutputFormat fmt, String method, URI uri, List<Parameter> params) throws Exception {
+ return executeJSON(fmt, method, uri, params, code -> code == HttpStatus.SC_OK);
+ }
+
+ public InputStream executeJSON(OutputFormat fmt, String method, URI uri, Predicate<Integer> responseCodeValidator)
throws Exception {
- HttpUriRequest request = constructGetMethod(uri, fmt, new ArrayList<>());
- HttpResponse response = executeAndCheckHttpRequest(request, responseCodeValidator);
- return response.getEntity().getContent();
+ return executeJSON(fmt, method, uri, Collections.emptyList(), responseCodeValidator);
}
- public InputStream executeJSONPost(OutputFormat fmt, URI uri) throws Exception {
- return executeJSONPost(fmt, uri, code -> code == HttpStatus.SC_OK);
- }
-
- public InputStream executeJSONPost(OutputFormat fmt, URI uri, Predicate<Integer> responseCodeValidator)
- throws Exception {
- HttpUriRequest request = constructPostMethod(uri, fmt, new ArrayList<>());
+ public InputStream executeJSON(OutputFormat fmt, String method, URI uri, List<Parameter> params,
+ Predicate<Integer> responseCodeValidator) throws Exception {
+ HttpUriRequest request = buildRequest(method, uri, fmt, params);
HttpResponse response = executeAndCheckHttpRequest(request, responseCodeValidator);
return response.getEntity().getContent();
}
@@ -679,8 +697,8 @@
// Insert and Delete statements are executed here
public void executeUpdate(String str, URI uri) throws Exception {
// Create a method instance.
- HttpUriRequest request =
- RequestBuilder.post(uri).setEntity(new StringEntity(str, StandardCharsets.UTF_8)).build();
+ HttpUriRequest request = RequestBuilder.post(uri).setEntity(new StringEntity(str, StandardCharsets.UTF_8))
+ .build();
// Execute the method.
executeAndCheckHttpRequest(request);
@@ -690,10 +708,10 @@
public InputStream executeAnyAQLAsync(String statement, boolean defer, OutputFormat fmt, URI uri,
Map<String, Object> variableCtx) throws Exception {
// Create a method instance.
- HttpUriRequest request =
- RequestBuilder.post(uri).addParameter("mode", defer ? "asynchronous-deferred" : "asynchronous")
- .setEntity(new StringEntity(statement, StandardCharsets.UTF_8))
- .setHeader("Accept", fmt.mimeType()).build();
+ HttpUriRequest request = RequestBuilder.post(uri)
+ .addParameter("mode", defer ? "asynchronous-deferred" : "asynchronous")
+ .setEntity(new StringEntity(statement, StandardCharsets.UTF_8)).setHeader("Accept", fmt.mimeType())
+ .build();
String handleVar = getHandleVariable(statement);
@@ -719,8 +737,8 @@
// create function statement
public void executeDDL(String str, URI uri) throws Exception {
// Create a method instance.
- HttpUriRequest request =
- RequestBuilder.post(uri).setEntity(new StringEntity(str, StandardCharsets.UTF_8)).build();
+ HttpUriRequest request = RequestBuilder.post(uri).setEntity(new StringEntity(str, StandardCharsets.UTF_8))
+ .build();
// Execute the method.
executeAndCheckHttpRequest(request);
@@ -730,8 +748,8 @@
// and returns the contents as a string
// This string is later passed to REST API for execution.
public String readTestFile(File testFile) throws Exception {
- BufferedReader reader =
- new BufferedReader(new InputStreamReader(new FileInputStream(testFile), StandardCharsets.UTF_8));
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(new FileInputStream(testFile), StandardCharsets.UTF_8));
String line;
StringBuilder stringBuilder = new StringBuilder();
String ls = System.getProperty("line.separator");
@@ -786,8 +804,8 @@
private static String getProcessOutput(Process p) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- Future<Integer> future =
- Executors.newSingleThreadExecutor().submit(() -> IOUtils.copy(p.getInputStream(), new OutputStream() {
+ Future<Integer> future = Executors.newSingleThreadExecutor()
+ .submit(() -> IOUtils.copy(p.getInputStream(), new OutputStream() {
@Override
public void write(int b) throws IOException {
baos.write(b);
@@ -966,6 +984,7 @@
break;
case "get":
case "post":
+ case "put":
expectedResultFile = (queryCount.intValue() >= expectedResultFileCtxs.size()) ? null
: expectedResultFileCtxs.get(queryCount.intValue()).getFile();
actualResultFile = expectedResultFile == null ? null
@@ -1071,11 +1090,12 @@
String handleVar = getHandleVariable(statement);
final String trimmedPathAndQuery = stripLineComments(stripJavaComments(statement)).trim();
final String variablesReplaced = replaceVarRef(trimmedPathAndQuery, variableCtx);
+ final List<Parameter> params = extractParameters(statement);
InputStream resultStream;
if ("http".equals(extension)) {
- resultStream = executeHttp(reqType, variablesReplaced, fmt);
+ resultStream = executeHttp(reqType, variablesReplaced, fmt, params);
} else if ("uri".equals(extension)) {
- resultStream = executeURI(reqType, URI.create(variablesReplaced), fmt);
+ resultStream = executeURI(reqType, URI.create(variablesReplaced), fmt, params);
} else {
throw new IllegalArgumentException("Unexpected format for method " + reqType + ": " + extension);
}
@@ -1100,7 +1120,7 @@
public void executeQuery(OutputFormat fmt, String statement, Map<String, Object> variableCtx, String reqType,
File testFile, File expectedResultFile, File actualResultFile, MutableInt queryCount, int numResultFiles,
- List<CompilationUnit.Parameter> params, ComparisonEnum compare) throws Exception {
+ List<Parameter> params, ComparisonEnum compare) throws Exception {
InputStream resultStream = null;
if (testFile.getName().endsWith("aql")) {
if (reqType.equalsIgnoreCase("query")) {
@@ -1158,7 +1178,7 @@
long limitTime = startTime + TimeUnit.SECONDS.toMillis(timeoutSecs);
ctx.setType(ctx.getType().substring("poll".length()));
boolean expectedException = false;
- Exception finalException;
+ Exception finalException = null;
LOGGER.fine("polling for up to " + timeoutSecs + " seconds w/ " + retryDelaySecs + " second(s) delay");
int responsesReceived = 0;
final ExecutorService executorService = Executors.newSingleThreadExecutor();
@@ -1177,11 +1197,16 @@
if (responsesReceived == 0) {
throw new Exception(
"Poll limit (" + timeoutSecs + "s) exceeded without obtaining *any* result from server");
+ } else if (finalException != null) {
+ throw new Exception("Poll limit (" + timeoutSecs
+ + "s) exceeded without obtaining expected result; last exception:", finalException);
} else {
throw new Exception("Poll limit (" + timeoutSecs + "s) exceeded without obtaining expected result");
}
} catch (Exception e) {
+ LOGGER.log(Level.FINE, "received exception on poll", e);
+ responsesReceived++;
if (isExpected(e, cUnit)) {
expectedException = true;
finalException = e;
@@ -1250,21 +1275,27 @@
return tmpStmt;
}
- protected InputStream executeHttp(String ctxType, String endpoint, OutputFormat fmt) throws Exception {
- String[] split = endpoint.split("\\?");
- URI uri = createEndpointURI(split[0], split.length > 1 ? split[1] : null);
- return executeURI(ctxType, uri, fmt);
+ protected static List<Parameter> extractParameters(String statement) {
+ List<Parameter> params = new ArrayList<>();
+ final Matcher m = HTTP_PARAM_PATTERN.matcher(statement);
+ while (m.find()) {
+ final Parameter param = new Parameter();
+ param.setName(m.group(1));
+ param.setValue(m.group(2));
+ params.add(param);
+ }
+ return params;
}
- private InputStream executeURI(String ctxType, URI uri, OutputFormat fmt) throws Exception {
- switch (ctxType) {
- case "get":
- return executeJSONGet(fmt, uri);
- case "post":
- return executeJSONPost(fmt, uri);
- default:
- throw new AssertionError("Not implemented: " + ctxType);
- }
+ protected InputStream executeHttp(String ctxType, String endpoint, OutputFormat fmt, List<Parameter> params)
+ throws Exception {
+ String[] split = endpoint.split("\\?");
+ URI uri = createEndpointURI(split[0], split.length > 1 ? split[1] : null);
+ return executeURI(ctxType, uri, fmt, params);
+ }
+
+ private InputStream executeURI(String ctxType, URI uri, OutputFormat fmt, List<Parameter> params) throws Exception {
+ return executeJSON(fmt, ctxType.toUpperCase(), uri, params);
}
private void killNC(String nodeId, CompilationUnit cUnit) throws Exception {
diff --git a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/config/IOption.java b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/config/IOption.java
index 834d73c..b8e7635 100644
--- a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/config/IOption.java
+++ b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/config/IOption.java
@@ -20,6 +20,9 @@
import java.util.function.Function;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.text.WordUtils;
+
public interface IOption {
String name();
@@ -64,6 +67,10 @@
return name().toLowerCase().replace("_", ".");
}
+ default String json() {
+ return StringUtils.remove(WordUtils.capitalize("z" + name().toLowerCase(), '_').substring(1), '_');
+ }
+
default String toIniString() {
return "[" + section().sectionName() + "] " + ini();
}
diff --git a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/config/IOptionType.java b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/config/IOptionType.java
index 1bd6097..d2a254f 100644
--- a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/config/IOptionType.java
+++ b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/config/IOptionType.java
@@ -18,6 +18,8 @@
*/
package org.apache.hyracks.api.config;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
public interface IOptionType<T> {
/**
* @throws IllegalArgumentException when the supplied string cannot be interpreted
@@ -34,6 +36,11 @@
}
/**
+ * @return the value in a format suitable for serialized JSON
+ */
+ void serializeJSONField(String fieldName, Object value, ObjectNode node);
+
+ /**
* @return the value in a format suitable for serialized ini file
*/
default String serializeToIni(Object value) {
diff --git a/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-common/pom.xml b/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-common/pom.xml
index 5120047..80ef088 100644
--- a/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-common/pom.xml
+++ b/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-common/pom.xml
@@ -75,5 +75,9 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
</dependencies>
</project>
diff --git a/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-common/src/main/java/org/apache/hyracks/control/common/config/ConfigManager.java b/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-common/src/main/java/org/apache/hyracks/control/common/config/ConfigManager.java
index fcaee6d..a595301 100644
--- a/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-common/src/main/java/org/apache/hyracks/control/common/config/ConfigManager.java
+++ b/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-common/src/main/java/org/apache/hyracks/control/common/config/ConfigManager.java
@@ -425,7 +425,7 @@
}
public List<String> getNodeNames() {
- return Collections.unmodifiableList(new ArrayList(nodeSpecificMap.keySet()));
+ return Collections.unmodifiableList(new ArrayList<>(nodeSpecificMap.keySet()));
}
public IApplicationConfig getNodeEffectiveConfig(String nodeId) {
diff --git a/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-common/src/main/java/org/apache/hyracks/control/common/config/OptionTypes.java b/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-common/src/main/java/org/apache/hyracks/control/common/config/OptionTypes.java
index 02b9325..1e92a7a 100644
--- a/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-common/src/main/java/org/apache/hyracks/control/common/config/OptionTypes.java
+++ b/hyracks-fullstack/hyracks/hyracks-control/hyracks-control-common/src/main/java/org/apache/hyracks/control/common/config/OptionTypes.java
@@ -21,14 +21,20 @@
import java.net.MalformedURLException;
import java.util.logging.Level;
+import org.apache.commons.lang3.StringUtils;
import org.apache.hyracks.api.config.IOptionType;
import org.apache.hyracks.util.StorageUtil;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
public class OptionTypes {
public static final IOptionType<Integer> INTEGER_BYTE_UNIT = new IOptionType<Integer>() {
@Override
public Integer parse(String s) {
+ if (s == null) {
+ return null;
+ }
long result1 = StorageUtil.getByteValue(s);
if (result1 > Integer.MAX_VALUE || result1 < Integer.MIN_VALUE) {
throw new IllegalArgumentException(
@@ -46,12 +52,17 @@
public String serializeToHumanReadable(Object value) {
return value + " (" + StorageUtil.toHumanReadableSize((int)value) + ")";
}
+
+ @Override
+ public void serializeJSONField(String fieldName, Object value, ObjectNode node) {
+ node.put(fieldName, (int)value);
+ }
};
public static final IOptionType<Long> LONG_BYTE_UNIT = new IOptionType<Long>() {
@Override
public Long parse(String s) {
- return StorageUtil.getByteValue(s);
+ return s == null ? null : StorageUtil.getByteValue(s);
}
@Override
@@ -63,6 +74,11 @@
public String serializeToHumanReadable(Object value) {
return value + " (" + StorageUtil.toHumanReadableSize((long)value) + ")";
}
+
+ @Override
+ public void serializeJSONField(String fieldName, Object value, ObjectNode node) {
+ node.put(fieldName, (long)value);
+ }
};
public static final IOptionType<Integer> INTEGER = new IOptionType<Integer>() {
@@ -75,6 +91,11 @@
public Class<Integer> targetType() {
return Integer.class;
}
+
+ @Override
+ public void serializeJSONField(String fieldName, Object value, ObjectNode node) {
+ node.put(fieldName, (int)value);
+ }
};
public static final IOptionType<Double> DOUBLE = new IOptionType<Double>() {
@@ -87,6 +108,11 @@
public Class<Double> targetType() {
return Double.class;
}
+
+ @Override
+ public void serializeJSONField(String fieldName, Object value, ObjectNode node) {
+ node.put(fieldName, (double)value);
+ }
};
public static final IOptionType<String> STRING = new IOptionType<String>() {
@@ -99,6 +125,11 @@
public Class<String> targetType() {
return String.class;
}
+
+ @Override
+ public void serializeJSONField(String fieldName, Object value, ObjectNode node) {
+ node.put(fieldName, (String)value);
+ }
};
public static final IOptionType<Long> LONG = new IOptionType<Long>() {
@@ -111,6 +142,11 @@
public Class<Long> targetType() {
return Long.class;
}
+
+ @Override
+ public void serializeJSONField(String fieldName, Object value, ObjectNode node) {
+ node.put(fieldName, (long)value);
+ }
};
public static final IOptionType<Boolean> BOOLEAN = new IOptionType<Boolean>() {
@@ -123,12 +159,17 @@
public Class<Boolean> targetType() {
return Boolean.class;
}
+
+ @Override
+ public void serializeJSONField(String fieldName, Object value, ObjectNode node) {
+ node.put(fieldName, (boolean)value);
+ }
};
public static final IOptionType<Level> LEVEL = new IOptionType<Level>() {
@Override
public Level parse(String s) {
- return Level.parse(s);
+ return s == null ? null : Level.parse(s);
}
@Override
@@ -137,20 +178,25 @@
}
@Override
- public Object serializeToJSON(Object value) {
- return ((Level)value).getName();
+ public String serializeToJSON(Object value) {
+ return value == null ? null : ((Level)value).getName();
}
@Override
public String serializeToIni(Object value) {
return ((Level)value).getName();
}
+
+ @Override
+ public void serializeJSONField(String fieldName, Object value, ObjectNode node) {
+ node.put(fieldName, serializeToJSON(value));
+ }
};
public static final IOptionType<String []> STRING_ARRAY = new IOptionType<String []>() {
@Override
public String [] parse(String s) {
- return s.split("\\s*,\\s*");
+ return s == null ? null : s.split("\\s*,\\s*");
}
@Override
@@ -162,13 +208,18 @@
public String serializeToIni(Object value) {
return String.join(",", (String [])value);
}
+
+ @Override
+ public void serializeJSONField(String fieldName, Object value, ObjectNode node) {
+ node.put(fieldName, value == null ? null : StringUtils.join((String [])value, ','));
+ }
};
public static final IOptionType<java.net.URL> URL = new IOptionType<java.net.URL>() {
@Override
public java.net.URL parse(String s) {
try {
- return new java.net.URL(s);
+ return s == null ? null : new java.net.URL(s);
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
@@ -178,8 +229,12 @@
public Class<java.net.URL> targetType() {
return java.net.URL.class;
}
- };
+ @Override
+ public void serializeJSONField(String fieldName, Object value, ObjectNode node) {
+ node.put(fieldName, value == null ? null : String.valueOf(value));
+ }
+ };
private OptionTypes() {
}
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/api/IServletRequest.java b/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/api/IServletRequest.java
index 610c3d1..be201df 100644
--- a/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/api/IServletRequest.java
+++ b/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/api/IServletRequest.java
@@ -18,6 +18,9 @@
*/
package org.apache.hyracks.http.api;
+import java.util.Map;
+import java.util.Set;
+
import io.netty.handler.codec.http.FullHttpRequest;
/**
@@ -38,6 +41,20 @@
String getParameter(CharSequence name);
/**
+ * Get the names of all request parameters
+ *
+ * @return the list of parameter names
+ */
+ Set<String> getParameterNames();
+
+ /**
+ * Get the all request parameters
+ *
+ * @return the parameters
+ */
+ Map<String, String> getParameters();
+
+ /**
* Get a request header
*
* @param name
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/BaseRequest.java b/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/BaseRequest.java
index 5b354af..0c633cf 100644
--- a/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/BaseRequest.java
+++ b/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/BaseRequest.java
@@ -19,8 +19,11 @@
package org.apache.hyracks.http.server;
import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.apache.hyracks.http.api.IServletRequest;
import org.apache.hyracks.http.server.utils.HttpUtil;
@@ -54,6 +57,21 @@
}
@Override
+ public Set<String> getParameterNames() {
+ return Collections.unmodifiableSet(parameters.keySet());
+ }
+
+ @Override
+ public Map<String, String> getParameters() {
+ HashMap<String, String> paramMap = new HashMap<>();
+ for (String name : parameters.keySet()) {
+ paramMap.put(name, HttpUtil.getParameter(parameters, name));
+
+ }
+ return Collections.unmodifiableMap(paramMap);
+ }
+
+ @Override
public String getHeader(CharSequence name) {
return request.headers().get(name);
}
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/FormUrlEncodedRequest.java b/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/FormUrlEncodedRequest.java
new file mode 100644
index 0000000..743a2c4
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/FormUrlEncodedRequest.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.hyracks.http.server;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.hyracks.http.api.IServletRequest;
+import org.apache.hyracks.http.server.utils.HttpUtil;
+
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.QueryStringDecoder;
+import io.netty.handler.codec.http.multipart.Attribute;
+import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
+import io.netty.handler.codec.http.multipart.InterfaceHttpData;
+import io.netty.handler.codec.http.multipart.MixedAttribute;
+
+public class FormUrlEncodedRequest extends BaseRequest implements IServletRequest {
+
+ private final List<String> names;
+ private final List<String> values;
+
+ public static IServletRequest create(FullHttpRequest request) throws IOException {
+ List<String> names = new ArrayList<>();
+ List<String> values = new ArrayList<>();
+ HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(request);
+ try {
+ List<InterfaceHttpData> bodyHttpDatas = decoder.getBodyHttpDatas();
+ for (InterfaceHttpData data : bodyHttpDatas) {
+ if (data.getHttpDataType().equals(InterfaceHttpData.HttpDataType.Attribute)) {
+ Attribute attr = (MixedAttribute) data;
+ names.add(data.getName());
+ values.add(attr.getValue());
+ }
+ }
+ } finally {
+ decoder.destroy();
+ }
+ return new FormUrlEncodedRequest(request, new QueryStringDecoder(request.uri()).parameters(), names, values);
+ }
+
+ protected FormUrlEncodedRequest(FullHttpRequest request, Map<String, List<String>> parameters, List<String> names,
+ List<String> values) {
+ super(request, parameters);
+ this.names = names;
+ this.values = values;
+ }
+
+ @Override
+ public String getParameter(CharSequence name) {
+ for (int i = 0; i < names.size(); i++) {
+ if (name.equals(names.get(i))) {
+ return values.get(i);
+ }
+ }
+ return HttpUtil.getParameter(parameters, name);
+ }
+
+ @Override
+ public Set<String> getParameterNames() {
+ HashSet<String> paramNames = new HashSet<>();
+ paramNames.addAll(parameters.keySet());
+ paramNames.addAll(names);
+ return Collections.unmodifiableSet(paramNames);
+ }
+
+ @Override
+ public Map<String, String> getParameters() {
+ HashMap<String, String> paramMap = new HashMap<>();
+ paramMap.putAll(super.getParameters());
+ for (int i = 0; i < names.size(); i++) {
+ paramMap.put(names.get(i), values.get(i));
+ }
+
+ return Collections.unmodifiableMap(paramMap);
+ }
+}
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/PostRequest.java b/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/PostRequest.java
deleted file mode 100644
index 1dcb088..0000000
--- a/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/PostRequest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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 org.apache.hyracks.http.server;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.apache.hyracks.http.api.IServletRequest;
-import org.apache.hyracks.http.server.utils.HttpUtil;
-
-import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.QueryStringDecoder;
-import io.netty.handler.codec.http.multipart.Attribute;
-import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
-import io.netty.handler.codec.http.multipart.InterfaceHttpData;
-import io.netty.handler.codec.http.multipart.MixedAttribute;
-
-public class PostRequest extends BaseRequest implements IServletRequest {
-
- private static final Logger LOGGER = Logger.getLogger(PostRequest.class.getName());
-
- private final List<String> names;
- private final List<String> values;
-
- public static IServletRequest create(FullHttpRequest request) throws IOException {
- List<String> names = new ArrayList<>();
- List<String> values = new ArrayList<>();
- HttpPostRequestDecoder decoder = null;
- try {
- decoder = new HttpPostRequestDecoder(request);
- } catch (Exception e) {
- //ignore. this means that the body of the POST request does not have key value pairs
- LOGGER.log(Level.WARNING, "Failed to decode a post message. Fix the API not to have queries as POST body",
- e);
- }
- if (decoder != null) {
- try {
- List<InterfaceHttpData> bodyHttpDatas = decoder.getBodyHttpDatas();
- for (InterfaceHttpData data : bodyHttpDatas) {
- if (data.getHttpDataType().equals(InterfaceHttpData.HttpDataType.Attribute)) {
- Attribute attr = (MixedAttribute) data;
- names.add(data.getName());
- values.add(attr.getValue());
- }
- }
- } finally {
- decoder.destroy();
- }
- }
- return new PostRequest(request, new QueryStringDecoder(request.uri()).parameters(), names, values);
- }
-
- protected PostRequest(FullHttpRequest request, Map<String, List<String>> parameters, List<String> names,
- List<String> values) {
- super(request, parameters);
- this.names = names;
- this.values = values;
- }
-
- @Override
- public String getParameter(CharSequence name) {
- for (int i = 0; i < names.size(); i++) {
- if (name.equals(names.get(i))) {
- return values.get(i);
- }
- }
- return HttpUtil.getParameter(parameters, name);
- }
-}
diff --git a/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/utils/HttpUtil.java b/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/utils/HttpUtil.java
index c11deef..2babc73 100644
--- a/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/utils/HttpUtil.java
+++ b/hyracks-fullstack/hyracks/hyracks-http/src/main/java/org/apache/hyracks/http/server/utils/HttpUtil.java
@@ -26,11 +26,11 @@
import org.apache.hyracks.http.api.IServletRequest;
import org.apache.hyracks.http.api.IServletResponse;
import org.apache.hyracks.http.server.BaseRequest;
-import org.apache.hyracks.http.server.PostRequest;
+import org.apache.hyracks.http.server.FormUrlEncodedRequest;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpRequest;
public class HttpUtil {
@@ -47,6 +47,7 @@
public static class ContentType {
public static final String APPLICATION_ADM = "application/x-adm";
public static final String APPLICATION_JSON = "application/json";
+ public static final String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded";
public static final String CSV = "text/csv";
public static final String IMG_PNG = "image/png";
public static final String TEXT_HTML = "text/html";
@@ -57,7 +58,7 @@
}
public static String getParameter(Map<String, List<String>> parameters, CharSequence name) {
- List<String> parameter = parameters.get(name);
+ List<String> parameter = parameters.get(String.valueOf(name));
if (parameter == null) {
return null;
} else if (parameter.size() == 1) {
@@ -72,7 +73,17 @@
}
public static IServletRequest toServletRequest(FullHttpRequest request) throws IOException {
- return request.method() == HttpMethod.POST ? PostRequest.create(request) : BaseRequest.create(request);
+ return ContentType.APPLICATION_X_WWW_FORM_URLENCODED.equals(getContentTypeOnly(request))
+ ? FormUrlEncodedRequest.create(request) : BaseRequest.create(request);
+ }
+
+ public static String getContentTypeOnly(IServletRequest request) {
+ return getContentTypeOnly(request.getHttpRequest());
+ }
+
+ public static String getContentTypeOnly(HttpRequest request) {
+ String contentType = request.headers().get(HttpHeaderNames.CONTENT_TYPE);
+ return contentType == null ? null : contentType.split(";")[0];
}
public static String getRequestBody(IServletRequest request) {