Support configuring multiple values in http header/query parameter/form-data #246

This commit is contained in:
Francis Dong
2021-07-16 17:13:06 +08:00
committed by dxfeng10
parent 5aa53fec9b
commit 60f2122b14
5 changed files with 326 additions and 132 deletions

View File

@@ -17,6 +17,7 @@
package we.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -36,6 +37,10 @@ import org.springframework.util.MultiValueMap;
*/
public class MapUtil {
private static final String KEY = "key";
private static final String VALUE = "value";
public static HttpHeaders toHttpHeaders(Map<String, Object> params) {
HttpHeaders headers = new HttpHeaders();
@@ -66,7 +71,6 @@ public class MapUtil {
return headers;
}
public static MultiValueMap<String, String> toMultiValueMap(Map<String, Object> params) {
MultiValueMap<String, String> mvmap = new LinkedMultiValueMap<>();
@@ -124,15 +128,16 @@ public class MapUtil {
return mvmap;
}
/**
* Extract form data from multipart map exclude file
*
* @param params
* @param fileKeyPrefix
* @param filePartMap Map
* @return
*/
public static Map<String, Object> extractFormData(MultiValueMap<String, Part> params, String fileKeyPrefix, Map<String, FilePart> filePartMap) {
public static Map<String, Object> extractFormData(MultiValueMap<String, Part> params, String fileKeyPrefix,
Map<String, FilePart> filePartMap) {
HashMap<String, Object> m = new HashMap<>();
if (params == null || params.isEmpty()) {
return m;
@@ -174,11 +179,13 @@ public class MapUtil {
/**
* Replace file field with FilePart object
*
* @param params
* @param fileKeyPrefix
* @param filePartMap
*/
public static void replaceWithFilePart(MultiValueMap<String, Object> params, String fileKeyPrefix, Map<String, FilePart> filePartMap) {
public static void replaceWithFilePart(MultiValueMap<String, Object> params, String fileKeyPrefix,
Map<String, FilePart> filePartMap) {
if (params == null || params.isEmpty() || filePartMap == null || filePartMap.isEmpty()) {
return;
}
@@ -255,9 +262,24 @@ public class MapUtil {
return rs;
}
public static MultiValueMap<String, Object> upperCaseKey(MultiValueMap<String, Object> m) {
MultiValueMap<String, Object> rs = new LinkedMultiValueMap<>();
if (m == null || m.isEmpty()) {
return rs;
}
for (Entry<String, List<Object>> entry : m.entrySet()) {
rs.put(entry.getKey().toUpperCase(), entry.getValue());
}
return rs;
}
/**
* Set value by pathsupport multiple levelsega.b.c <br>
* Do NOT use this method if field name contains a dot <br>
*
* @param data
* @param path
* @param value
@@ -287,6 +309,7 @@ public class MapUtil {
/**
* Get value by path, support multiple levelsega.b.c <br>
* Do NOT use this method if field name contains a dot <br>
*
* @param data
* @param path
* @return
@@ -316,6 +339,7 @@ public class MapUtil {
/**
* Merge maps, merge src to target
*
* @param target
* @param src
* @return
@@ -339,4 +363,70 @@ public class MapUtil {
return target;
}
/**
* Convert list to map and merge multiple values<br>
* Example: <br>
* List as:<br>
* [{"key": "abc", "value": "aaa"},{"key": "abc", "value": "xyz"},{"key":
* "a123", "value": 666}]<br>
* Merge Result:<br>
* {"abc": ["aaa","xyz"], "a123": 666} <br>
*
* @param obj
* @return
*/
@SuppressWarnings("unchecked")
public static Map<String, Object> list2Map(Object obj) {
// Compatible with older version configuration
if (obj instanceof Map) {
return (Map<String, Object>) obj;
}
if (obj instanceof List) {
Map<String, Object> rs = new HashMap<>();
List<Map<String, Object>> list = (List<Map<String, Object>>) obj;
if (list == null || list.size() == 0) {
return rs;
}
for (Map<String, Object> m : list) {
String k = m.get(KEY).toString();
Object v = m.get(VALUE);
if (rs.containsKey(k)) {
List<Object> vals = null;
if (rs.get(k) instanceof List) {
vals = (List<Object>) rs.get(k);
} else {
vals = new ArrayList<>();
vals.add(rs.get(k));
}
vals.add(v);
rs.put(k, vals);
} else {
rs.put(k, v);
}
}
return rs;
}
return null;
}
/**
* Convert list to MultiValueMap<br>
* List format example:<br>
* [{"key": "abc", "value": "aaa"},{"key": "abc", "value": "xyz"},,{"key":
* "a123", "value": 666}]<br>
*
* @param list
* @return
*/
public static MultiValueMap<String, Object> listToMultiValueMap(List<Map<String, Object>> list) {
MultiValueMap<String, Object> mvmap = new LinkedMultiValueMap<>();
if (list == null || list.size() == 0) {
return mvmap;
}
for (Map<String, Object> m : list) {
mvmap.add(m.get(KEY).toString(), m.get(VALUE));
}
return mvmap;
}
}

View File

@@ -32,6 +32,7 @@ import reactor.core.publisher.Mono;
import we.exception.ExecuteScriptException;
import we.exception.RedirectException;
import we.exception.StopAndResponseException;
import we.fizz.exception.FizzRuntimeException;
import we.flume.clients.log4j2appender.LogService;
import we.legacy.RespEntity;
import we.util.JacksonUtils;
@@ -81,6 +82,19 @@ public class FilterExceptionHandlerConfig {
return resp.writeWith(Mono.just(resp.bufferFactory().wrap(rs.toString().getBytes())));
}
}
if (t instanceof FizzRuntimeException) {
FizzRuntimeException ex = (FizzRuntimeException) t;
resp.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
RespEntity rs = null;
String reqId = exchange.getRequest().getId();
if (ex.getStepContext() != null && ex.getStepContext().returnContext()) {
rs = new RespEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), t.getMessage(), reqId, ex.getStepContext());
return resp.writeWith(Mono.just(resp.bufferFactory().wrap(JacksonUtils.writeValueAsString(rs).getBytes())));
} else {
rs = new RespEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), t.getMessage(), reqId);
return resp.writeWith(Mono.just(resp.bufferFactory().wrap(rs.toString().getBytes())));
}
}
Mono<Void> vm;
Object fc = exchange.getAttributes().get(WebUtils.FILTER_CONTEXT);
if (fc == null) { // t came from flow control filter

View File

@@ -1,11 +1,37 @@
package we.fizz.exception;
import we.fizz.StepContext;
public class FizzRuntimeException extends RuntimeException {
private StepContext<String, Object> stepContext;
public FizzRuntimeException(String message) {
super(message);
}
public FizzRuntimeException(String message, Throwable cause) {
super(message, cause);
this.setStackTrace(cause.getStackTrace());
}
public FizzRuntimeException(String message, StepContext<String, Object> stepContext) {
super(message);
this.stepContext = stepContext;
}
public FizzRuntimeException(String message, Throwable cause, StepContext<String, Object> stepContext) {
super(message, cause);
this.setStackTrace(cause.getStackTrace());
this.stepContext = stepContext;
}
public StepContext<String, Object> getStepContext() {
return stepContext;
}
public void setStepContext(StepContext<String, Object> stepContext) {
this.stepContext = stepContext;
}
}

View File

@@ -19,13 +19,13 @@ package we.fizz.input;
import java.util.*;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.stream.Collectors;
import org.noear.snack.ONode;
import we.constants.CommonConstants;
import we.fizz.StepContext;
import we.fizz.exception.FizzRuntimeException;
import we.util.MapUtil;
/**
@@ -105,14 +105,15 @@ public class PathMapping {
return target.toObject(Map.class);
}
@SuppressWarnings("unchecked")
public static ONode transform(ONode ctxNode, Map<String, Object> rules, boolean supportMultiLevels) {
ONode target = ONode.load(new HashMap());
if (rules.isEmpty()) {
return target;
}
Map<String, String> rs = new HashMap<>();
Map<String, String> types = new HashMap<>();
Map<String, Object> rs = new HashMap<>();
Map<String, Object> types = new HashMap<>();
for (Entry<String, Object> entry : rules.entrySet()) {
if (entry.getValue() instanceof String) {
String val = (String) entry.getValue();
@@ -123,6 +124,25 @@ public class PathMapping {
} else {
rs.put(entry.getKey(), val);
}
} else if (entry.getValue() instanceof List) {
List<Object> values = (List<Object>) entry.getValue();
List<String> vList = new ArrayList<>();
List<String> tList = new ArrayList<>();
for (Object v : values) {
if (v instanceof String) {
String val = (String) v;
Optional<String> optType = typeList.stream().filter(s -> val.startsWith(s + " ")).findFirst();
if (optType.isPresent()) {
vList.add(val.substring(optType.get().length() + 1));
tList.add(optType.get());
} else {
vList.add(val);
tList.add(null);
}
}
}
rs.put(entry.getKey(), vList);
types.put(entry.getKey(), tList);
}
}
@@ -134,8 +154,46 @@ public class PathMapping {
Object starValObj = null;
String starEntryKey = null;
for (Entry<String, String> entry : rs.entrySet()) {
String path = entry.getValue();
for (Entry<String, Object> entry : rs.entrySet()) {
if (entry.getValue() instanceof String) {
String path = (String) entry.getValue();
String type = (String) types.get(entry.getKey());
Object obj = getRefValue(ctxNode, type, path);
if (CommonConstants.WILDCARD_STAR.equals(entry.getKey())) {
starValObj = obj;
starEntryKey = entry.getKey();
} else {
setByPath(target, entry.getKey(), obj, supportMultiLevels);
}
} else if (entry.getValue() instanceof List) {
List<String> refs = (List<String>) entry.getValue();
List<String> tList = (List<String>) types.get(entry.getKey());
List<Object> refValList = new ArrayList<>();
for (int i = 0; i < refs.size(); i++) {
String path = refs.get(i);
String type = tList.get(i);
Object obj = getRefValue(ctxNode, type, path);
// Only header form-data and query Parameter support multiple values, merge result into
// one a list
if (obj instanceof List) {
refValList.addAll((List<Object>) obj);
} else {
refValList.add(obj);
}
}
setByPath(target, entry.getKey(), refValList, supportMultiLevels);
}
}
if(starEntryKey != null) {
setByPath(target, starEntryKey, starValObj, supportMultiLevels);
}
return target;
}
private static Object getRefValue(ONode ctxNode, String type, String path) {
Object obj = null;
try {
String p = path;
String defaultValue = null;
if (path.indexOf("|") != -1) {
@@ -143,15 +201,23 @@ public class PathMapping {
defaultValue = path.substring(path.indexOf("|") + 1);
}
ONode val = select(ctxNode, handlePath(p));
Object obj = null;
if (val != null && !val.isNull()) {
obj = val;
} else {
obj = defaultValue;
}
if (obj != null && types.containsKey(entry.getKey())) {
switch (types.get(entry.getKey())) {
if (obj != null && type != null) {
obj = cast(obj, type);
}
} catch (Exception e) {
e.printStackTrace();
throw new FizzRuntimeException(String.format("path mapping errer: %s , path mapping data: %s %s", e.getMessage(), type, path), e);
}
return obj;
}
private static Object cast(Object obj, String type) {
switch (type) {
case "Integer":
case "int": {
if (obj instanceof ONode) {
@@ -205,20 +271,7 @@ public class PathMapping {
break;
}
}
}
if (CommonConstants.WILDCARD_STAR.equals(entry.getKey())) {
starValObj = obj;
starEntryKey = entry.getKey();
} else {
setByPath(target, entry.getKey(), obj, supportMultiLevels);
}
}
if(starEntryKey != null) {
setByPath(target, starEntryKey, starValObj, supportMultiLevels);
}
return target;
return obj;
}
public static ONode select(ONode ctxNode, String path) {
@@ -270,7 +323,14 @@ public class PathMapping {
}
Map<String, Object> rs = new HashMap<>();
for (Entry<String, Object> entry : rules.entrySet()) {
if (!(entry.getValue() instanceof String)) {
if (entry.getValue() instanceof List) {
List<Object> values = (List<Object>) entry.getValue();
for (Object v : values) {
if (!(v instanceof String)) {
rs.put(entry.getKey(), v);
}
}
} else if (!(entry.getValue() instanceof String) && entry.getValue() instanceof Map) {
rs.put(entry.getKey(), entry.getValue());
}
}
@@ -418,6 +478,7 @@ public class PathMapping {
*/
public static Map<String, Object> transform(ONode ctxNode, StepContext<String, Object> stepContext,
Map<String, Object> fixed, Map<String, Object> mappingRules, boolean supportMultiLevels) {
try {
if (fixed != null && fixed.containsKey(CommonConstants.WILDCARD_TILDE)) {
Object val = fixed.get(CommonConstants.WILDCARD_TILDE);
fixed = new HashMap<>();
@@ -434,15 +495,18 @@ public class PathMapping {
}
if (mappingRules != null) {
// 路径映射
ONode target = PathMapping.transform(ctxNode, mappingRules, supportMultiLevels);
ONode target = transform(ctxNode, mappingRules, supportMultiLevels);
// 脚本转换
Map<String, Object> scriptRules = PathMapping.getScriptRules(mappingRules);
Map<String, Object> scriptRules = getScriptRules(mappingRules);
Map<String, Object> scriptResult = ScriptHelper.executeScripts(target, scriptRules, ctxNode, stepContext, supportMultiLevels);
if (scriptResult != null && !scriptResult.isEmpty()) {
result = MapUtil.merge(result, scriptResult);
}
}
return result;
}catch(FizzRuntimeException e) {
throw new FizzRuntimeException(e.getMessage(), e, stepContext);
}
}
public static Map<String, Object> convertPath(Map<String, Object> fixed, boolean supportMultiLevels) {

View File

@@ -143,8 +143,8 @@ public class RequestInput extends RPCInput implements IInput{
// headers
Map<String, Object> headers = PathMapping.transform(ctxNode, stepContext,
MapUtil.upperCaseKey((Map<String, Object>) requestMapping.get("fixedHeaders")),
MapUtil.upperCaseKey((Map<String, Object>) requestMapping.get("headers")), false);
MapUtil.upperCaseKey(MapUtil.list2Map(requestMapping.get("fixedHeaders"))),
MapUtil.upperCaseKey(MapUtil.list2Map(requestMapping.get("headers"))), false);
if (headers.containsKey(CommonConstants.WILDCARD_TILDE)
&& headers.get(CommonConstants.WILDCARD_TILDE) instanceof Map) {
request.put("headers", headers.get(CommonConstants.WILDCARD_TILDE));
@@ -154,8 +154,8 @@ public class RequestInput extends RPCInput implements IInput{
// params
params.putAll(PathMapping.transform(ctxNode, stepContext,
(Map<String, Object>) requestMapping.get("fixedParams"),
(Map<String, Object>) requestMapping.get("params"), false));
MapUtil.list2Map(requestMapping.get("fixedParams")),
MapUtil.list2Map(requestMapping.get("params")), false));
if (params.containsKey(CommonConstants.WILDCARD_TILDE)
&& params.get(CommonConstants.WILDCARD_TILDE) instanceof Map) {
request.put("params", params.get(CommonConstants.WILDCARD_TILDE));
@@ -170,8 +170,8 @@ public class RequestInput extends RPCInput implements IInput{
supportMultiLevels = false;
}
Map<String,Object> body = PathMapping.transform(ctxNode, stepContext,
(Map<String, Object>) requestMapping.get("fixedBody"),
(Map<String, Object>) requestMapping.get("body"), supportMultiLevels);
MapUtil.list2Map(requestMapping.get("fixedBody")),
MapUtil.list2Map(requestMapping.get("body")), supportMultiLevels);
if (body.containsKey(CommonConstants.WILDCARD_TILDE)) {
request.put("body", body.get(CommonConstants.WILDCARD_TILDE));
} else {