解决 Spring RestTemplate post传递参数时报错问题
今天跟同事接口联调,使用RestTemplate请求服务端的post接口(使用python开发)。诡异的是,post请求,返回500InternalServerError,而使用get请求,返回正常。代码如下:
HashMaphashMap=Maps.newHashMap(); hashMap.put("data",JSONObject.toJSONString(params)); url="http://mydomain/dataDownLoad.cgi?data={data}"; json=restTemplate.getForObject(url,String.class,hashMap); System.out.println("getjson:"+json); url="http://mydomain/dataDownLoad.cgi"; json=restTemplate.postForObject(url,hashMap,String.class); System.out.println("hasmappostjson:"+json);
结果为:
getjson:{'status':0,'statusInfo':{'global':'OK'},'data':'http://mydomain/dataDownLoad.cgi?downLoadData=358300d5f9e1cc512efc178caaa0b061'} 500InternalServerError
最后经过另一位同学帮忙排查,发现RestTemplate在postForObject时,不可使用HashMap。而应该是MultiValueMap。改为如下:
MultiValueMapparamMap=newLinkedMultiValueMap<>(); paramMap.add("data",JSONObject.toJSONString(params)); url="http://mydomain/dataDownLoad.cgi"; json=restTemplate.postForObject(url,paramMap,String.class); System.out.println("postjson:"+json);
结果为:
postjson:{'status':0,'statusInfo':{'global':'OK'},'data':'http://mydomain/dataDownLoad.cgi?downLoadData=f2fc328513886e51b3b67d35043985ae'}
然后我想起之前使用RestTemplate发起post请求时,使用POJO作为参数,是可行的。再次测试:
url="http://mydomain/dataDownLoad.cgi"; PostDatapostData=newPostData(); postData.setData(JSONObject.toJSONString(params)); json=restTemplate.postForObject(url,paramMap,String.class); System.out.println("postDatajson:"+json);
返回:500InternalServerError。
到现在为止接口调通了。但问题的探究才刚刚开始。
RestTemplate的post参数为什么使用MultiValueMap而不能使用HashMap?
为什么post接口,get请求也可以正确返回?
为什么java服务端可以接收POJO参数,python服务端不可以?python服务端使用CGI(CommonGatewayInterface),与cgi有关系吗?
何为MultiValueMap
IDEA中command+N,搜索类MultiValueMap,发现apache的commons-collections包有一个MultiValueMap类,spring-core包中有一个接口MultiValueMap,及其实现类LinkedMultiValueMap。显然看spring包。
首先看LinkedMultiValueMap,实现MultiValueMap接口,只有一个域:Map
publicinterfaceMultiValueMapextendsMap >{ VgetFirst(Kkey);//targetMap.get(key).get(0) voidadd(Kkey,Vvalue);//targetMap.get(key).add(value) voidset(Kkey,Vvalue);//targetMap.set(key,Lists.newLinkedList(value)) voidsetAll(Map values);//将普通map转为LinkedMultiValueMap Map toSingleValueMap();//只保留所有LinkedList的第一个值,转为LinkedHashMap }
综上,LinkedMultiValueMap实际就是Key-LinkedList的map。
RestTemplate怎么处理post参数
首先查看RestTemplate源码,首先将请求封装成HttpEntityRequestCallback类对象,然后再处理请求。
Override publicTpostForObject(Stringurl,Objectrequest,Class responseType,Object...uriVariables) throwsRestClientException{ //请求包装成httpEntityCallback RequestCallbackrequestCallback=httpEntityCallback(request,responseType); HttpMessageConverterExtractor responseExtractor= newHttpMessageConverterExtractor (responseType,getMessageConverters(),logger); //处理请求 returnexecute(url,HttpMethod.POST,requestCallback,responseExtractor,uriVariables); }
那么HttpEntityRequestCallback是什么样的呢?如下,实际是把请求数据放在了一个HttpEntity中。如果requestBody是HttpEntity类型,就直接转;否则,放在HttpEntity的body中。
//请求内容封装在一个HttpEntity对象中。 privateHttpEntityRequestCallback(ObjectrequestBody,TyperesponseType){ super(responseType); if(requestBodyinstanceofHttpEntity){ this.requestEntity=(HttpEntity>)requestBody; } elseif(requestBody!=null){ this.requestEntity=newHttpEntity
接着看一下HttpEntity源码:
publicclassHttpEntity{ privatefinalHttpHeadersheaders; privatefinalTbody; publicHttpEntity(Tbody){ this.body=body; } } publicclassHttpHeadersimplementsMultiValueMap ,Serializable{ ...... }
至此,与MultiValueMap联系上了。
基于本次问题,我们不考虑post数据参数是HttpEntity类型的,只考虑普通POJO。那么,postForObject中对post数据的第一步处理,就是放在一个HttpEntity类型(header为MultiValueMap类型,body为泛型)的body中。
再看处理请求的部分:
ObjectrequestBody=requestEntity.getBody(); Class>requestType=requestBody.getClass(); HttpHeadersrequestHeaders=requestEntity.getHeaders(); MediaTyperequestContentType=requestHeaders.getContentType(); for(HttpMessageConverter>messageConverter:getMessageConverters()){ if(messageConverter.canWrite(requestType,requestContentType)){ if(!requestHeaders.isEmpty()){ httpRequest.getHeaders().putAll(requestHeaders); } ((HttpMessageConverter
通过配置的HttpMessageConverter来处理。
text/html;charset=UTF-8 application/json
符合要求的只有ViewAwareJsonMessageConverter,其自定义处理如下。post数据中hashMap只含有data一个key,不含status字段,所以会跳过写的操作,即post请求带不上参数。如果修改代码,当不含status字段时,按照父类方法处理,则服务端可以得到参数。
protectedvoidwriteInternal(Objectobject,HttpOutputMessageoutputMessage)throwsIOException,HttpMessageNotWritableException{ if(objectinstanceofMap){ Mapmap=(Map)object; HashMapstatusInfo=newHashMap(); //不含有status字段,跳过 Objectstatus=map.get("status"); if(status!=null){ intcode=Integer.parseInt(String.valueOf(status)); if(0!=code){ super.writeInternal(object,outputMessage); }else{ statusInfo.put("global","OK"); map.put("statusInfo",statusInfo); super.writeInternal(object,outputMessage); } } }else{ super.writeInternal(object,outputMessage); } }
而使用MultiValueMap会由FormHttpMessageConverter正确处理。
首先判断是否可以执行写操作,如果可以,执行写操作。
@Override publicbooleancanWrite(Class>clazz,MediaTypemediaType){ if(!MultiValueMap.class.isAssignableFrom(clazz)){ returnfalse; } if(mediaType==null||MediaType.ALL.equals(mediaType)){ returntrue; } for(MediaTypesupportedMediaType:getSupportedMediaTypes()){ if(supportedMediaType.isCompatibleWith(mediaType)){ returntrue; } } returnfalse; } @Override @SuppressWarnings("unchecked") publicvoidwrite(MultiValueMapmap,MediaTypecontentType,HttpOutputMessageoutputMessage) throwsIOException,HttpMessageNotWritableException{ if(!isMultipart(map,contentType)){//LinkedList中是否含有多个数据 //只是普通的K-V,写form writeForm((MultiValueMap )map,contentType,outputMessage); } else{ writeMultipart((MultiValueMap )map,outputMessage); } }
既如此,那么post参数为POJO时,如何呢?
POJO也会被ViewAwareJsonMessageConverter处理,在其writeInternal中,object不是map,所以调用super.writeInternal(object,outputMessage),如下:
@Override protectedvoidwriteInternal(Objectobj,HttpOutputMessageoutputMessage)throwsIOException,HttpMessageNotWritableException{ OutputStreamout=outputMessage.getBody(); Stringtext=JSON.toJSONString(obj,features); byte[]bytes=text.getBytes(charset); out.write(bytes); }
如果注释掉ViewAwareJsonMessageConverter,跟踪发现,会报错,返回没有合适的HttpMessageConverter处理。
使用ViewAwareJsonMessageConverter和使用FormHttpMessageConverter写数据的格式是不一样的,所以,postPOJO后,会返回错误,但实际已将参数传递出去。
所以,对于我们配置的RestTemplate来说,post参数可以是map(有字段要求),也可以是POJO。即,输入输出数据由RestTemplate配置的messageConverters决定。
至此,我们已经清楚了第一个问题,剩下的问题同样的思路。跟踪一下getForObject的处理路径。get方式请求时,把所有的参数拼接在url后面,发给服务端,就可以把参数带到服务端。
剩下的问题就是python服务端是怎么处理请求的。首先研究一下CGI。
何为CGI
通用网关接口(CGI,CommonGatewayInterface)是一种Web服务器和服务器端程序进行交互的协议。CGI完全独立于编程语言,操作系统和Web服务器。这个协议可以用vb,c,php,python来实现。
工作方式如图所示:
browser->webServer:HTTPprotocol
webServer->CGI脚本:通过CGI管理模块调用脚本
CGI脚本->CGI脚本:执行脚本程序
CGI脚本->webServer:返回结果
webServer->browser:HTTPprotocol
web服务器获取了请求cgi服务的http请求后,启动cgi脚本,并将http协议参数和客户端请求参数转为cgi协议的格式,传给cgi脚本。cgi脚本执行完毕后,将数据返回给web服务器,由web服务器返回给客户端。
cgi脚本怎么获取参数呢?
CGI脚本从环境变量QUERY_STRING中获取GET请求的数据
CGI脚本从stdin(标准输入)获取POST请求的数据,数据长度存在环境变量CONTENT_LENGTH中。
了解CGI大概是什么东东后,看一下python实现的CGI。
python的CGI模块,要获取客户端的post参数,可以使用cgi.FieldStorage()方法。FieldStorage相当于python中的字典,支持多个方法。可以支持一般的key-value,也可以支持key-List
至此,本问题主要是在于程序怎么传递参数,对于springrestTemplate而言,就是messageConverters怎么配置的。
更多关于RestTemplatepost传递参数时报错问题文章大家看看下面的相关链接
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。