All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ssssssss.magicapi.controller.RequestHandler Maven / Gradle / Ivy

There is a newer version: 2.1.1
Show newest version
package org.ssssssss.magicapi.controller;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.ssssssss.magicapi.config.MagicConfiguration;
import org.ssssssss.magicapi.config.MappingHandlerMapping;
import org.ssssssss.magicapi.config.Valid;
import org.ssssssss.magicapi.config.WebSocketSessionManager;
import org.ssssssss.magicapi.context.CookieContext;
import org.ssssssss.magicapi.context.RequestContext;
import org.ssssssss.magicapi.context.SessionContext;
import org.ssssssss.magicapi.exception.ValidateException;
import org.ssssssss.magicapi.interceptor.RequestInterceptor;
import org.ssssssss.magicapi.logging.MagicLoggerContext;
import org.ssssssss.magicapi.model.*;
import org.ssssssss.magicapi.modules.ResponseModule;
import org.ssssssss.magicapi.provider.ResultProvider;
import org.ssssssss.magicapi.script.ScriptManager;
import org.ssssssss.magicapi.utils.PatternUtils;
import org.ssssssss.script.MagicScriptContext;
import org.ssssssss.script.MagicScriptDebugContext;
import org.ssssssss.script.exception.MagicScriptAssertException;
import org.ssssssss.script.exception.MagicScriptException;
import org.ssssssss.script.functions.ObjectConvertExtension;
import org.ssssssss.script.parsing.Span;
import org.ssssssss.script.parsing.ast.literal.BooleanLiteral;
import org.ssssssss.script.reflection.JavaInvoker;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS;
import static org.ssssssss.magicapi.config.MessageType.BREAKPOINT;
import static org.ssssssss.magicapi.model.Constants.*;

public class RequestHandler extends MagicController {

	private static final Logger logger = LoggerFactory.getLogger(RequestHandler.class);

	private final ResultProvider resultProvider;

	private static final Map EMPTY_MAP = new HashMap<>();

	public RequestHandler(MagicConfiguration configuration) {
		super(configuration);
		this.resultProvider = configuration.getResultProvider();
	}

	/**
	 * 测试入口、实际请求入口
	 */
	@ResponseBody
	@Valid(requireLogin = false)    // 无需验证是否要登录
	public Object invoke(HttpServletRequest request, HttpServletResponse response,
						 @PathVariable(required = false) Map pathVariables,
						 @RequestParam(required = false) Map parameters) throws Throwable {
		String sessionId = null;
		boolean requestedFromTest = configuration.isEnableWeb() && (sessionId = request.getHeader(HEADER_REQUEST_SESSION)) != null;
		RequestEntity requestEntity = new RequestEntity(request, response, requestedFromTest, parameters, pathVariables);
		if (requestEntity.getApiInfo() == null) {
			logger.error("{}找不到对应接口", request.getRequestURI());
			return buildResult(requestEntity, API_NOT_FOUND, "接口不存在");
		}
		Map headers = new HashMap() {
			@Override
			public Object get(Object key) {
				return request.getHeader(key.toString());
			}
		};
		requestEntity.setHeaders(headers);
		List paths = new ArrayList<>(requestEntity.getApiInfo().getPaths());
		MappingHandlerMapping.findGroups(requestEntity.getApiInfo().getGroupId())
				.stream()
				.flatMap(it -> it.getPaths().stream())
				.filter(it -> !paths.contains(it))
				.forEach(paths::add);
		Object bodyValue = readRequestBody(requestEntity.getRequest());
		try {
			// 验证参数
			doValidate("参数", requestEntity.getApiInfo().getParameters(), parameters, PARAMETER_INVALID);
			// 验证 header
			doValidate("header", requestEntity.getApiInfo().getHeaders(), headers, HEADER_INVALID);
			// 验证 path
			doValidate("path", paths, requestEntity.getPathVariables(), PATH_VARIABLE_INVALID);
			BaseDefinition requestBody = requestEntity.getApiInfo().getRequestBodyDefinition();
			if (requestBody != null && requestBody.getChildren().size() > 0) {
				requestBody.setName(StringUtils.defaultIfBlank(requestBody.getName(), "root"));
				doValidate(VAR_NAME_REQUEST_BODY, Collections.singletonList(requestBody), new HashMap() {{
					put(requestBody.getName(), bodyValue);
				}}, BODY_INVALID);
			}
		} catch (ValidateException e) {
			return resultProvider.buildResult(requestEntity, RESPONSE_CODE_INVALID, e.getMessage());
		} catch (Throwable root) {
			return processException(requestEntity, root);
		}
		MagicScriptContext context = createMagicScriptContext(requestEntity, bodyValue);
		requestEntity.setMagicScriptContext(context);
		RequestContext.setRequestEntity(requestEntity);
		Object value;
		// 执行前置拦截器
		if ((value = doPreHandle(requestEntity)) != null) {
			return value;
		}
		if (requestedFromTest) {
			try {
				MagicLoggerContext.SESSION.set(sessionId);
				return invokeRequest(requestEntity);
			} finally {
				MagicLoggerContext.SESSION.remove();
				WebSocketSessionManager.remove(sessionId);
			}
		} else {
			return invokeRequest(requestEntity);
		}
	}

	private Object buildResult(RequestEntity requestEntity, JsonCode code, Object data) {
		return resultProvider.buildResult(requestEntity, code.getCode(), code.getMessage(), data);
	}


	private boolean doValidateBody(String comment, BaseDefinition parameter, Map parameters, JsonCode jsonCode, Class target) {
		if (!parameter.isRequired() && parameters.isEmpty()) {
			return true;
		}
		if (parameter.isRequired() && !BooleanLiteral.isTrue(parameters.get(parameter.getName()))) {
			throw new ValidateException(jsonCode, StringUtils.defaultIfBlank(parameter.getError(), String.format("%s[%s]为必填项", comment, parameter.getName())));
		}
		Object value = parameters.get(parameter.getName());
		if (value != null && !target.isAssignableFrom(value.getClass())) {
			throw new ValidateException(jsonCode, StringUtils.defaultIfBlank(parameter.getError(), String.format("%s[%s]数据类型错误", comment, parameter.getName())));
		}
		return false;
	}

	private  void doValidate(String comment, List validateParameters, Map parameters, JsonCode jsonCode) {
		parameters = parameters != null ? parameters : EMPTY_MAP;
		for (BaseDefinition parameter : validateParameters) {
			// 针对requestBody多层级的情况
			if (VAR_NAME_REQUEST_BODY_VALUE_TYPE_OBJECT.equalsIgnoreCase(parameter.getDataType().getJavascriptType())) {
				if (doValidateBody(comment, parameter, parameters, jsonCode, Map.class)) {
					continue;
				}
				doValidate(VAR_NAME_REQUEST_BODY, parameter.getChildren(), (Map) parameters.get(parameter.getName()), jsonCode);
			} else if (VAR_NAME_REQUEST_BODY_VALUE_TYPE_ARRAY.equalsIgnoreCase(parameter.getDataType().getJavascriptType())) {
				if (doValidateBody(comment, parameter, parameters, jsonCode, List.class)) {
					continue;
				}
				List list = (List) parameters.get(parameter.getName());
				if (list != null) {
					for (Object value : list) {
						List definitions = parameter.getChildren();
						doValidate(VAR_NAME_REQUEST_BODY, definitions, new HashMap() {{
							put("", value);
						}}, jsonCode);
					}
				}

			} else if (StringUtils.isNotBlank(parameter.getName())) {
				String requestValue = StringUtils.defaultIfBlank(Objects.toString(parameters.get(parameter.getName()), EMPTY), Objects.toString(parameter.getDefaultValue(), EMPTY));
				if (StringUtils.isBlank(requestValue)) {
					if (!parameter.isRequired()) {
						continue;
					}
					throw new ValidateException(jsonCode, StringUtils.defaultIfBlank(parameter.getError(), String.format("%s[%s]为必填项", comment, parameter.getName())));
				}
				try {
					Object value = convertValue(parameter.getDataType(), parameter.getName(), requestValue);
					if (VALIDATE_TYPE_PATTERN.equals(parameter.getValidateType())) {    // 正则验证
						String expression = parameter.getExpression();
						if (StringUtils.isNotBlank(expression) && !PatternUtils.match(Objects.toString(value, EMPTY), expression)) {
							throw new ValidateException(jsonCode, StringUtils.defaultIfBlank(parameter.getError(), String.format("%s[%s]不满足正则表达式", comment, parameter.getName())));
						}
					}
					parameters.put(parameter.getName(), value);
				} catch (Exception e) {
					throw new ValidateException(jsonCode, StringUtils.defaultIfBlank(parameter.getError(), String.format("%s[%s]不合法", comment, parameter.getName())));
				}
			}
		}
		// 取出表达式验证的参数
		List validates = validateParameters.stream().filter(it -> VALIDATE_TYPE_EXPRESSION.equals(it.getValidateType()) && StringUtils.isNotBlank(it.getExpression())).collect(Collectors.toList());
		for (BaseDefinition parameter : validates) {
			MagicScriptContext context = new MagicScriptContext();
			// 将其他参数也放置脚本中,以实现“依赖”的情况
			context.putMapIntoContext(parameters);
			Object value = parameters.get(parameter.getName());
			if (value != null) {
				// 设置自身变量
				context.set(EXPRESSION_DEFAULT_VAR_NAME, value);
				if (!BooleanLiteral.isTrue(ScriptManager.executeExpression(parameter.getExpression(), context))) {
					throw new ValidateException(jsonCode, StringUtils.defaultIfBlank(parameter.getError(), String.format("%s[%s]不满足表达式", comment, parameter.getName())));
				}
			}
		}
	}

	/**
	 * 转换参数类型
	 */
	private Object convertValue(DataType dataType, String name, String value) {
		if (dataType == null) {
			return value;
		}
		try {
			if (dataType.isNumber()) {
				BigDecimal decimal = ObjectConvertExtension.asDecimal(value, null);
				if (decimal == null) {
					throw new IllegalArgumentException();
				}
				return dataType.getInvoker().invoke0(decimal, null);
			} else {
				JavaInvoker invoker = dataType.getInvoker();
				if (invoker != null) {
					List params = new ArrayList<>();
					if (dataType.isNeedName()) {
						params.add(name);
					}
					if (dataType.isNeedValue()) {
						params.add(value);
					}
					return invoker.invoke0(null, null, params.toArray());
				}
			}
			return value;
		} catch (Throwable throwable) {
			throw new IllegalArgumentException();
		}
	}

	private Object invokeRequest(RequestEntity requestEntity) throws Throwable {
		try {
			Object result = ScriptManager.executeScript(requestEntity.getApiInfo().getScript(), requestEntity.getMagicScriptContext());
			Object value = result;
			// 执行后置拦截器
			if ((value = doPostHandle(requestEntity, value)) != null) {
				return value;
			}
			// 对返回结果包装处理
			return response(requestEntity, result);
		} catch (Throwable root) {
			return processException(requestEntity, root);
		} finally {
			RequestContext.remove();
		}
	}

	private Object processException(RequestEntity requestEntity, Throwable root) throws Throwable {
		MagicScriptException se = null;
		Throwable parent = root;
		do {
			if (parent instanceof MagicScriptAssertException) {
				MagicScriptAssertException sae = (MagicScriptAssertException) parent;
				return resultProvider.buildResult(requestEntity, sae.getCode(), sae.getMessage());
			}
			if (parent instanceof MagicScriptException) {
				se = (MagicScriptException) parent;
			}
		} while ((parent = parent.getCause()) != null);
		if (configuration.isThrowException()) {
			throw root;
		}
		logger.error("接口{}请求出错", requestEntity.getRequest().getRequestURI(), root);
		if (se != null && requestEntity.isRequestedFromTest()) {
			Span.Line line = se.getLine();
			requestEntity.getResponse().setHeader(ACCESS_CONTROL_EXPOSE_HEADERS, HEADER_RESPONSE_WITH_MAGIC_API);
			requestEntity.getResponse().setHeader(HEADER_RESPONSE_WITH_MAGIC_API, CONST_STRING_TRUE);
			return new JsonBodyBean<>(-1000, se.getSimpleMessage(), resultProvider.buildException(requestEntity, se), line == null ? null : Arrays.asList(line.getLineNumber(), line.getEndLineNumber(), line.getStartCol(), line.getEndCol()));
		}
		return resultProvider.buildException(requestEntity, root);
	}

	/**
	 * 读取RequestBody
	 */
	private Object readRequestBody(HttpServletRequest request) throws IOException {
		if (configuration.getHttpMessageConverters() != null && request.getContentType() != null) {
			MediaType mediaType = MediaType.valueOf(request.getContentType());
			Class clazz = Object.class;
			try {
				for (HttpMessageConverter converter : configuration.getHttpMessageConverters()) {
					if (converter.canRead(clazz, mediaType)) {
						return converter.read(clazz, new ServletServerHttpRequest(request));
					}
				}
			} catch (HttpMessageNotReadableException ignored) {
				return null;
			}
		}
		return null;
	}

	/**
	 * 构建 MagicScriptContext
	 */
	private MagicScriptContext createMagicScriptContext(RequestEntity requestEntity, Object requestBody) {
		List breakpoints = requestEntity.getRequestedBreakpoints();
		// 构建脚本上下文
		MagicScriptContext context;
		// TODO 安全校验
		if (requestEntity.isRequestedFromDebug()) {
			MagicScriptDebugContext debugContext = new MagicScriptDebugContext(breakpoints);
			String sessionId = requestEntity.getRequestedSessionId();
			debugContext.setTimeout(configuration.getDebugTimeout());
			debugContext.setId(sessionId);
			debugContext.setCallback(variables -> WebSocketSessionManager.sendBySessionId(sessionId, BREAKPOINT, variables));
			WebSocketSessionManager.createSession(sessionId, debugContext);
			context = debugContext;
		} else {
			context = new MagicScriptContext();
		}
		Object wrap = requestEntity.getApiInfo().getOptionValue(Options.WRAP_REQUEST_PARAMETERS.getValue());
		if (wrap != null && StringUtils.isNotBlank(wrap.toString())) {
			context.set(wrap.toString(), requestEntity.getParameters());
		}
		context.putMapIntoContext(requestEntity.getParameters());
		context.putMapIntoContext(requestEntity.getPathVariables());
		context.set(VAR_NAME_COOKIE, new CookieContext(requestEntity.getRequest()));
		context.set(VAR_NAME_HEADER, requestEntity.getHeaders());
		context.set(VAR_NAME_SESSION, new SessionContext(requestEntity.getRequest().getSession()));
		context.set(VAR_NAME_PATH_VARIABLE, requestEntity.getPathVariables());
		if (requestBody != null) {
			context.set(VAR_NAME_REQUEST_BODY, requestBody);
		}
		return context;
	}

	/**
	 * 包装返回结果
	 */
	private Object response(RequestEntity requestEntity, Object value) {
		if (value instanceof ResponseEntity) {
			return value;
		} else if (value instanceof ResponseModule.NullValue) {
			return null;
		}
		return resultProvider.buildResult(requestEntity, value);
	}

	/**
	 * 执行后置拦截器
	 */
	private Object doPostHandle(RequestEntity requestEntity, Object value) throws Exception {
		for (RequestInterceptor requestInterceptor : configuration.getRequestInterceptors()) {
			Object target = requestInterceptor.postHandle(requestEntity, value);
			if (target != null) {
				return target;
			}
		}
		return null;
	}

	/**
	 * 执行前置拦截器
	 */
	private Object doPreHandle(RequestEntity requestEntity) throws Exception {
		for (RequestInterceptor requestInterceptor : configuration.getRequestInterceptors()) {
			Object value = requestInterceptor.preHandle(requestEntity);
			if (value != null) {
				return value;
			}
		}
		return null;
	}

}