request.getInputStream为空的改进办法

最近公司的一个小项目进行模块优化,需求是:跟踪记录每个IP请求的源数据和响应数据。当源数据包括requestBody的时候,从HttpServletRequest里面获取requestBody我们采用的是以下代码:

@RequestMapping(value = "/{user}/welcome", method = RequestMethod.POST)
public ModelAndView queryUpdateStatus(@PathVariable String user,
			HttpServletRequest request) throws IOException{
    Map<String, Object> json =new HashMap<String, Object>();
    int size = request.getContentLength();
    InputStream is = request.getInputStream();
    byte[] reqBodyBytes = StreamUtils.readBytes(is, size);
    String res = new String(reqBodyBytes);
    System.out.println("requestBody:"+res);
    
    return new ModelAndView(new JsonView(),json);    
}

当然也有另外一种更快捷的办法,就是引入一个JAR包apktool.jar,使用以下代码也能获得requestBody:

@RequestMapping(value = "/{user}/welcome", method = RequestMethod.POST)
public ModelAndView queryUpdateStatus(@PathVariable String user,
			HttpServletRequest request) throws IOException{
    Map<String, Object> json =new HashMap<String, Object>();
    String res = IOUtils.toString(request.getInputStream(),"UTF-8");
    System.out.println("requestBody:"+res);
    
    return new ModelAndView(new JsonView(),json);    
}

现在为了实现跟踪记录的需求,为所有控制器进行AOP拦截:

@Component
@Aspect
public class LogAspect {

	protected final Logger log = Logger.getLogger(getClass());

	@Around("execution(* demo.web.controller..*.*(..))")
	public Object around(ProceedingJoinPoint jp) throws Throwable {
		String businessName = jp.getSignature().getName();
		String fileName = jp.getSignature().getDeclaringTypeName();
		String params = initAssist(jp);
		int lastIndex = fileName.lastIndexOf(".");
		int cIndex = fileName.lastIndexOf("Controller");
		fileName = fileName.substring(lastIndex + 1, cIndex);
		String requestLog = "actionLog_request:fileName:" + fileName + "...businessName:" + businessName
				+ "...params " + params;
		log.info(requestLog);

		Object returnValue = null;
		try {
			returnValue = jp.proceed(jp.getArgs());

			if (returnValue != null) {
				if (returnValue instanceof ModelAndView) {
					JSONObject jsonObj = JSONObject
							.fromObject(((ModelAndView) returnValue).getModel());
					log.info(requestLog+"||actionLog_return:" + jsonObj.toString());
				}
			} else {
				log.info("return:null");
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			log.info("abc log err");
			e.printStackTrace();
			String message = e.getMessage();
//			throw e;
		}
		return returnValue;
	}

	private String initAssist(JoinPoint jp) {
		String result = "";
		Class clazz = jp.getTarget().getClass();
		String method = jp.getSignature().getName();
		ClassPool pool = ClassPool.getDefault();
		pool.insertClassPath(new ClassClassPath(this.getClass()));
		CtClass cc;
		try {
			cc = pool.get(clazz.getName());
			CtMethod cm = cc.getDeclaredMethod(method);
			Object[] args = jp.getArgs();// 外部传参,不包括内部参数
			String[] paramNames = matchParam(cm, cm.getParameterTypes().length);
			for (int i = 0; i < args.length; i++) {
				if(args[i]!=null){
					try {
						if(args[i] instanceof String){

							result += paramNames[i] + "-->" + args[i].toString() + ",";
						}
						if(args[i] instanceof HttpServletRequest){

							HttpServletRequest req = (HttpServletRequest)args[i];
							//获取requestHeader
							Map<String, String> requestHeader = new HashMap<String, String>();
							Enumeration<String> headerNames = req.getHeaderNames();
							while (headerNames.hasMoreElements()) {
								String key = (String) headerNames
										.nextElement();
								String value = req.getHeader(key);
								requestHeader.put(key, value);
							}
							//获取requestBody
							String requestBody=IOUtils.toString(req.getInputStream());
							//获取requestParameter
					        Map<String, String> requestParams = new HashMap<String, String>();
							Enumeration<String> pNames = req.getParameterNames();
							while (pNames.hasMoreElements()) {
								String key = (String) pNames
										.nextElement();
								String value = req.getHeader(key);
								requestParams.put(key, value);
							}
						}
					} catch (Exception e) {
						e.printStackTrace();

					}
				}else{
					result += paramNames[i] + "-->" + null + ",";
				}
			}

		} catch (NotFoundException e) {
			log.error(clazz.getName(), e);
		}
		return result;
	}

	private String[] matchParam(CtMethod cm, Integer arrLength) {
		try {
			String[] paramNames = new String[arrLength];
			MethodInfo methodInfo = cm.getMethodInfo();
			CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
			LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute
					.getAttribute(LocalVariableAttribute.tag);// 含有内部参数
			if (attr == null) {
				log.error("");
				return null;
			}

			for (int i = 0, j = 0; i < attr.tableLength(); i++) {
				if ("this".equals(attr.variableName(i))) {
					continue;
				} else {
					if (j < paramNames.length) {
						paramNames[j] = attr.variableName(i);
						j++;
					}
				}

			}

			return paramNames;
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
		return null;
	}

}

此时启动服务,请求对应接口调试时候你会发现如下情况:

[demo.web.filter.LogAspect] - requestBody:abc=abc

[demo.web.controller.DemoController] - requestBody:

结论:第二次使用request.getInputStream()是无法获取到任何信息。

为了实现需求,我们使用了springmvc中的注解 @RequestBody ,控制器代码修改为以下所示:

@RequestMapping(value = "/{user}/welcome", method = RequestMethod.POST)
public ModelAndView queryUpdateStatus(@PathVariable String user,
			HttpServletRequest request,@RequestBody String requestBodyString) throws IOException{
    Map<String, Object> json =new HashMap<String, Object>();
    System.out.println("requestBody:"+requestBodyString);
    
    return new ModelAndView(new JsonView(),json);    
}

启动服务,请求对应接口调试时候你会发现如下情况:

[demo.web.filter.LogAspect] - requestBody:

[demo.web.controller.DemoController] - requestBody:abc=abc

结论:springmvc已经把request的输入流转换为对应的数据,因此,输入流已经被springmvc读过,程序第二次读流操作必定为空。

现在只需要把日志拦截器拦截requestbody的代码部分换成拦截requestBodyString就可以了。

第一次写博文,有写得不好的地方见谅。

时间: 2024-08-06 05:24:27

request.getInputStream为空的改进办法的相关文章

敏捷测试中发现的一些问题及改进办法

最近产品出现了几个不大不小的问题,时间点却偏偏是在距离产品发布不到一个月!!在解决完问题后,不禁要思考一下:到底哪里出了问题? 下面是对最近出现的问题的反思和一些改进办法: 问题 1:遗漏重要需求 敏捷团队中需求的获取有很多种方式,大体的来源分为: a. 最终客户(需求和反馈) b. 行业标准 c. 竞争产品 d. 团队贡献和创新 e. 其他 我们遇到的问题是有一部分客户对域里的用户权限限制很高,不是我们常用的有域管理员权限,这是我们没有考虑到也从来没接触过的的使用方式,以至于产品根本无法在一些

request.getParameter() 、 request.getInputStream()和request.getReader() 使用体会

request.getParameter(). request.getInputStream().request.getReader()这三种方法是有冲突的,因为流只能被读一次.比如:当form表单内容采用 enctype=application/x-www-form-urlencoded编码时,先通过调用request.getParameter() 方法得到参数后,再调用request.getInputStream()或request.getReader()已经得不到流中的内容,因为在调用 r

NuGet Package Explorer上传时报:failed to process request:&#39;Method Not Allowed&#39;错误解决办法

相关日志:PUT /api/v2/package - 1000 -  NuGet+Package+Explorer/3.15.0.0+(Microsoft+Windows+NT+6.2.9200.0) - 405 0 0 0 解决办法Web.config里面加入设置: <system.webServer> <modules>     <remove name="WebDAVModule" />   </modules>   <han

dedecms织梦后台发布文章提示“标题不能为空”的解决办法

V5.7登录后台后,发布英文标题没问题,发布中文会提示“标题不能为空”. 原因:htmlspecialchars在php5.4默认为utf8编码,gbk编码字符串经 htmlspecialchars 转义后的中文字符串为空,也就是标题为空. 解决办法:给htmlspecialchars添加ENT_COMPAT ,'GB2312'参数修改编码默认值. 具体修改页面: 1.dede/article_add.php 和 dede/article_edit.php将$title = htmlspecia

关于request.getInputStream方法上传文件

之前有做过一个上传图片的功能,找到的资料全部都是用框架做的,当时为了尽快完成任务,就拿过来修修改改直接放线上了.对于我这样的菜鸟,任务虽然完成了,可当前更重要的是个人的技术提升.然后我又开始乱想了.经理说平时把基础多看看,嗯,上班也有一年了,年后我也写了日记进行了深刻的反思,然后重新给自己定位.理所当然的是最开始的一层,就是买家具用的人,对于各种技术都只是研究它是做什么的,怎么使用.只能拿过来用,也只会拿过来用.能力还不足以吃透它的底层和核心.再多的技术拿过来都只是会用.虽然这是做项目必备,而我

SpringMvc出现No mapping found for HTTP request with URI的终极解决办法

No mapping found for HTTP request with URI 出现这个问题的原因是在web.xml中配置错了,如: <servlet> <servlet-name>springMVCDispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init

mac 下eclipse创建Android项目为空的解决办法。

最近把电脑换成mac了,由于10.10后的os x,不自带java sdk了,没办法只有到官网上下载jdk1.7, (为什么不下载1.6,或1.8.1.6太旧了不支持Android 5.0 的一些属性,1.8 太新,怕和Android有些不兼容) 配置Android studio:直接是傻瓜式的配置,下载完成安装就行了. 配置eclipse 安装完后配置adt,直接使用离线安装的形式(不知道离线安装请百度),忽视掉中间弹出的是否继续 窗口.(sdk是自己识别的,已经下载好) 貌似一切正常,但是在

关于pl/sql打开后database为空的问题解决办法

前置条件:楼主是在虚拟机里面进行安装oracle和pl/sql的,所以我的安装后,发现我的pl/sql显示的database是空的,当然楼主会检查我的tnsnames.ora是不是配置正确了,但是检查后发现是正常的,依然显示database是空的,报错页面如图1. 图1 然后下面说下解决办法. 1.由于之前虚拟机磁盘空间不足,楼主就去新增了一个E盘,所以我的oracle是装在E盘的,但是我的instantclient_11_2是装在虚拟机的C盘的,此处应该同步. 保证和之前安装的oracle在同

mysql中CONCAT值为空的问题解决办法

在mysql中concat函数有一个特点就是有一个值为null那么不管第二个字符有多少内容都返回为空了,这个特性让我们在实例应用中可能觉得不方便,但实现就是这样我们需要使用其它办法来解决. 天在做opencart开发的时候,需要对用户表中用户的电话号码和区号连接起来,于是使用了concat方法,  代码如下 复制代码 SELECT CONCAT(isdcode,telephone) FROM gb_customer 竟然发现很多NULL列,telephone明明是有值的,于是查询了相关conca