当前位置: 首页 > java >正文

对于过滤器中使用getInputStream()、getParameter()接收参数接收不到的一些知识,以及解决方法。

昨天,我需要做一个从主项目分离出来的项目对主项目的功能的调用,但是在写Http发送Post请求时,遇到了主项目接收不到参数的情况,从而引起了我对项目接收参数的一些探讨。

我们知道,对于spring项目接收参数用的最多的方式应该是request.getParameter(“xx”),这种方式了把,不论在过滤器Interceptor的preHandle()做拦截是获取参数处理,还是controller用各种注解获取参数比如@RequestParam,@RequestParam(这个注解是获取url后面的参数,下面的post的请求形式上是参数是放在URL后面的,所以能够使用该注解获取)等等。

我们主项目中使用的就在过滤器中使用request.getParameter(“xx”),在controller中使用@RequestParam,获取的参数,今天我在子项目中要调用主项目的一个接口时,需要传一些参数,我就按照平时的发送Http请求写了,代码如下(注意,一些涉及到私密的 我给屏蔽了 ):

/*** 发送https请求* * @param requestUrl 请求地址* @param requestMethod 请求方法(get,post)* @param outputStr 请求参数* @return JSONObject 返回一个json对象*/public static JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr){JSONObject jsonObject = null;System.out.println("----请求参数"+outputStr);try {URL url = new URL(requestUrl);if (url.toString().startsWith("https")){//https请求路径HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();//创建SSLContext对象,并使用我们指定的信任管理器初始化TrustManager[] tm = { new MyX509TrustManager() };SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");sslContext.init(null, tm, new java.security.SecureRandom());//从上述的SSLContext对象中得到SSLSocketFactorySSLSocketFactory ssf = sslContext.getSocketFactory();conn.setSSLSocketFactory(ssf);conn.setDoInput(true);conn.setDoOutput(true);conn.setUseCaches(false);//设置请求方式conn.setRequestMethod(requestMethod);//当outputStr不为null的时候,向输出流写数据if(outputStr != null){OutputStream outputStream = conn.getOutputStream();outputStream.write(outputStr.getBytes("UTF-8"));outputStream.close();}HttpsURLConnection httpConn = conn;//从输入流获取数据InputStream inputStream = null;if (httpConn.getResponseCode() >= 400) {//如果报错,将错误信息写入到输入流中inputStream = httpConn.getErrorStream();} else {inputStream = httpConn.getInputStream();}InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");BufferedReader bufferedReader = new BufferedReader(inputStreamReader);String str = null;StringBuffer buffer = new StringBuffer();while((str = bufferedReader.readLine()) != null){buffer.append(str);}//释放资源bufferedReader.close();inputStreamReader.close();inputStream.close();httpConn.disconnect();conn.disconnect();System.out.println("HTTP请求返回信息:"+buffer.toString());jsonObject = JSON.parseObject(buffer.toString());}else{//http请求HttpURLConnection conn = (HttpURLConnection)url.openConnection();conn.setDoInput(true);conn.setDoOutput(true);conn.setUseCaches(false);//设置请求方式conn.setRequestMethod(requestMethod);//当outputStr不为null的时候,向输出流写数据if(outputStr != null){OutputStream outputStream = conn.getOutputStream();outputStream.write(outputStr.getBytes("UTF-8"));outputStream.close();}HttpURLConnection httpConn = conn;//从输入流获取数据InputStream inputStream = null;if (httpConn.getResponseCode() >= 400) {//如果报错,将错误信息写入到输入流中inputStream = httpConn.getErrorStream();} else {inputStream = httpConn.getInputStream();}InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");BufferedReader bufferedReader = new BufferedReader(inputStreamReader);String str = null;StringBuffer buffer = new StringBuffer();while((str = bufferedReader.readLine()) != null){buffer.append(str);}//释放资源bufferedReader.close();inputStreamReader.close();inputStream.close();httpConn.disconnect();conn.disconnect();System.out.println("HTTP请求返回信息:" + buffer.toString());jsonObject = JSON.parseObject(buffer.toString());}} catch (ConnectException ce) {ce.printStackTrace();log.error("连接超时:{}",ce);} catch (Exception e) {e.printStackTrace();log.error("https请求异常:{}", e);}return jsonObject;}

上面的请求参数是一个json字符串数据,用于请求参数,

但是单元测试的时候,这个http请求总是返回说参数不存在的400错误。

从上面的代码可以看出,我明明是把请求参数写入到了输出流当中了。然后我从主项目的过滤器中使用request.getParameter(“xx”),获取 是一个null值。

刚开始我以为是我的数据没有写进来,但是后来我在主项目中使用流读取参数,确实是能够读取到参数的。这就是问题所在,说明我是把请求参数写入进来了。但是获取不到。

所以我就开始寻找相关的信息,后来从别的博客以及资料中,了解到好像request.getParameter(“xx”)这种获取参数的方法,仅仅对于form表单提交的请求有效,并且form表单还需要设置enctype=”application/x-www-form-urlencoded”是编码方式,这个是form的默认编码方式,所以如果不是设置的其他的编码格式就能够获取到。

知道了这个,我就开始在我的http方法中添加了:

conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

设置了请求编码类型,然后再请求,发现一点用没有,还是报参数不存在的错误。但是我从公司的swagger-ui上测试接口是能够测试通的。。

后来我使用Fiddler工具监听了swagger-ui调用接口的请求,发现,接口传输的参数不是在request的body区域内,而是拼接到了URL上面,也就是说虽然这是一个POST请求,但是参数的传输还是在URL上面。然后我就查询各种资料,然后问了公司的前端工程师、安卓工程师,他们调用也没有问题,我看了一下他们的调用代码,,前端工程师他也是先将参数处理成一个URL后面的字符串,不过他不是将这个字符串拼接到URL后面,而是把这个字符串写入到Http的body里面。安卓的就是把这个json写入到body属性中。

然后我就开始修改我的http请求,模拟form表单提交的方式发送Http请求(从这个可以看出来,java模拟form表单提交与普通的http请求的区别,就是下面这个 还有一个请求头的content-type的设置问题),在请求前对参数进行处理:

// 构建请求参数StringBuffer sb = new StringBuffer();if (outputStr != null) {Map params = JSONObject.parseObject(outputStr);for (Object e : params.keySet()) {sb.append("&");sb.append(e);sb.append("=");sb.append(params.get(e));}sb.substring(0, sb.length() - 1);}

将原来的请求参数拼接成以下格式的请求参数:
&key1=xxx&key2=xxx&key3=xxx

然后在将拼接好的请求参数写入到request中。单元测试发现主项目中使用request.getParameter能够获取到参数了。

虽然这个问题解决了,但是对于项目接收参数还是有很多疑问,比如说在过滤器中如何使用流接收参数,以及为什么在过滤器或者其他地方或去过参数之后controller里面就再也获取不到参数了。。

首先说第二个问题,为什么在过滤器或者其他地方或去过参数之后controller里面就再也获取不到参数了。。

这个问题主要是一个HttpServletRequest的一个不知道是不是bug的问题,就是对于一个request请求来说,它的参数输入流只能读取一次,读取之后流中的数据便没有了,而无论我们从过滤器中也好还是三方的一些功能里面也好还是controller,只要它需要使用到request中的参数,他就只能从流中读取。所以如果在controller之前,有对象都去过request中的流,那么controller中就再也读取不到参数了。。

那么这种问题如何处理呢,现在使用最多的一种方式便是我们现将流读出来,然后在写进去。这样后面的方法在读取的时候就能够读取了。

这也就是第一个问题过滤器中如何使用流接收参数

我们在过滤器中使用流读取参数,我们需要考虑我们读完之后,后面的是不是也能读到。我们不能做那种我们自己读完了一时爽,然后让后面的人懵逼去吧的事情。。。

下面是解决方法:

既然原生的ServletRequest有这样的问题,那么我们可以自己写一个ServletRequest,能够提供重复对取请求参数流的方法,这个就需要继承HttpServletRequestWrapper方法。

package ***.***.***.***.common;/*** Created by yefuliang on 2017/10/25.*/import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;/*** 保存流** @author yefuliang 2017年10月25日*/
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {private final byte[] body;//保存流的字节数组public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {super(request);String sessionStream = getBodyString(request);//读取流中的参数body = sessionStream.getBytes(Charset.forName("UTF-8"));}/*** 获取请求Body** @param request* @return*/public String getBodyString(final ServletRequest request) {StringBuilder sb = new StringBuilder();InputStream inputStream = null;BufferedReader reader = null;try {inputStream = cloneInputStream(request.getInputStream());reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));String line = "";while ((line = reader.readLine()) != null) {sb.append(line);}}catch (IOException e) {e.printStackTrace();}finally {if (inputStream != null) {try {inputStream.close();}catch (IOException e) {e.printStackTrace();}}if (reader != null) {try {reader.close();}catch (IOException e) {e.printStackTrace();}}}return sb.toString();}/*** Description: 复制输入流</br>** @param inputStream* @return</br>*/public InputStream cloneInputStream(ServletInputStream inputStream) {ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int len;try {while ((len = inputStream.read(buffer)) > -1) {byteArrayOutputStream.write(buffer, 0, len);}byteArrayOutputStream.flush();}catch (IOException e) {e.printStackTrace();}InputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());return byteArrayInputStream;}@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(getInputStream()));}@Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream bais = new ByteArrayInputStream(body);return new ServletInputStream() {@Overridepublic int read() throws IOException {return bais.read();}};}
}

上面继承HttpServletRequestWrapper的方法写好了,我们就可以使用了,具体的使用方法如下:
1.首先在拦截器中将原来的ServletRequest替换掉:

// 防止流读取一次后就没有了, 所以需要将流继续写出去
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(httpServletRequest);HttpServletResponse resp = (HttpServletResponse) servletResponse;
ResponseWrapper mResp = new ResponseWrapper(resp); // 包装响应对象 resp 并缓存响应数据filterChain.doFilter(requestWrapper, mResp);

可以对比下以前的doFilter()方法:filterChain.doFilter(request, response);
可以发现我上面的代码对request和response都进行了封装,封装response主要是我这个项目还需要对返回的参数进行处理,因为别的地方都读取不到reponse中的值,所以在这里重新封装了response用与读取返回值,具体的怎么封装可以看我的另一篇博客过滤器通过HttpServletResponseWrapper包装HttpServletResponse实现获取response中的返回数据,以及对数据进行gzip压缩。

2.在过滤器中如果需要读取参数:

JSONObject parameterMap = JSON.parseObject(new BodyReaderHttpServletRequestWrapper(request).getBodyString(request));
String dataFrom = String.valueOf(parameterMap.get("dataFrom"));

parameterMap 就是请求的参数json。

3.如何在controller中获取q请求的json数据
可以参考下我下面的方法,使用@RequestBody 将请求参数转换成后面的类型的参数,后面的可以是一个Bean,也可以是一个json,也可以是一个string,看你传输的数据了:

    @RequestMapping("/***/manageUserGag")public String manageUserGag(@RequestBody JSONObject request){return ***Impl.manageUserGag(request.toJSONString());}

写到这里我对项目接收参数的认识更加清晰了,不知道对各位有没有帮助,如果有什么问题 欢迎联系。

http://www.xdnf.cn/news/11387.html

相关文章:

  • TLSF算法概念,原理,内存碎片问题分析
  • UML 类关系(详解)——依赖、关联、聚合、组合、泛化
  • “IT小百科”之“电脑开机密码忘记了怎么办”
  • mentohust 使用
  • 打造优质的灵修生活
  • ActiveSync同步使用方法
  • OGG|Oracle GoldenGate 基础知识介绍(二)
  • 介绍一个很不错的电影网站
  • css实现两端对齐的3种方法
  • 机动战士高达观影顺序
  • 光耦合器知识概述
  • HTTP 和 HTTPS 的区别(面试常考题),计算机专业学生必备
  • 堆栈溢出
  • (译)追本溯源 —— C之精神
  • JAVA常用类—————StringTokenizer类
  • 重磅发布!吴恩达 AI 完整课程资源超级大汇总!
  • 微信web开发者工具
  • .wav文件详解,PCM数据格式,.wav生成C语言数组
  • Oracle数据库导入工具IMP详解与用法
  • java porm.xml_如何通过Maven仓库安装Spire系列的Java产品
  • java复习 02
  • 苹果CMS采集资源站
  • cognos入门
  • random()随机函数
  • System V消息队列报Resource temporarily unavailable 错误
  • C#-TimeSpan格式化字符串格式
  • GoAhead4 - 用户认证
  • 五种多目标优化算法(MOFA、NSWOA、MOJS、MOAHA、MOPSO)性能对比(提供MATLAB代码)
  • jxl使用总结(三)
  • Git命令操作【全系列】