最近为项目组提供rest api 时遇到了关于接口参数的传递问题,主要是没有充分考虑到第三方调用者的使用方式,应该尽量的去兼容公司之前提供出去的接口调用方式,这样可以降低第三方调用者的学习成本,尽管之前的方式并不是那么的推荐,好的做法是即兼容老的做法也支持推荐的做法。

对于基于http post接口,Content-type我会优先选择application/json,但公司之前提供的接口恰恰采用了application/x-www-form-urlencoded,它是表单默认的提交类型,基于key/value形式提交到服务端的。spring mvc是如何接收下面两种经典数据的? (至于form-data,它即可以传键值对也可以上传文件,这里不涉及到文件所以只讨论下面两种):

  • Content-type=application/json:需要在参数上增加@RequestBody这个注解,说明参数是从http的requestbody中获取。

下图中的参数,是标准的json格式,对前端js非常友好。

  • Content-type=application/x-www-form-urlencoded,参数上不能增加@RequestBody的注解

下图的可以看出参数形式与get请求时,URL后面的参数格式


为什么不推荐采用application/x-www-form-urlencoded这种类型,它有如下问题:

  • 测试困难,就别想通过postman这类工具测试:提交到服务端实际上是一个MultiValueMap,如果value中的对象也是一个对象,那么在构建这个参数时就非常困难,看下它的过程

    • 采用key1=value1&key2=value2这种形式将所有参数拼接起来,从一长串字符中想了解每个参数的含义没有个好眼力怕是不行。
    • value要进行编码,编码之后的对调试者不友好。
    • value是复杂对象的情况更加糟糕,一般只能通过程序来序列化得到参数,想手写基本不可能。
  • 客户端调用复杂

需要去构建List<NameValuePair>,一般页面传递的参数都是一个实体对象Model,需要额外的将这个Model转换成List<NameValuePair>,如果这个对象复杂,那么构建这个Key/Value就够人烦的了。这里给一个java通过apache httpclient调用的对比,看看哪一个简单。

    • application/x-www-form-urlencoded

需要手工将model转换成NameValuePair。

    • application/json

这里只需要Model即可,不需要二次转换,结构也非常清楚。

  • key/value的语言表达形式没有json强,下面两种你更加喜欢哪一个呢?

    • 字符串

post man这类模似http请求的工具中,如果key对应的value是个对象,那么你需要通过工具得到它的序列化之后的字符串然后填写到字段中,想想都烦。如果你说我不需要通过这些模似工具测试,那就另当别论

    • json

  • 数据结构更加复杂

如果需要提交的对象非常复杂,属性非常多,如果将所有的属性都构建到MultiValueMap中,那个Map的构建会非常复杂,试想如果对象有多级嵌套对象呢。所有为了避免这个问题,我们将需要提交的业务对象做为一个key来存储,value就是对象序列化之后的字符串。再加了一些非业务参数,比如安全方面的token等参数,有效的降低了MultiValueMap构建的复杂度。但这种方式相对于json的传递方式来讲层次更深。如下图,我们的参数多了一层,jsonParam。

如果解决呢?
不能不兼容现有的模式,但又想支持json,焦点就是在参数的接收上,让其能够完美的兼容上述两种参数传递,这里可以从HttpMessageConverter着手,这个就是用来将请求的参数映射到spring mvc方法中的实体参数的。我们可以编写一个自定义的类,内部借用FormHttpMessageConverter来接收MultiValueMap,即使方法参数上增加了@RequestBody的注解,也会走我们自定义的converter,就有机会去重新给参数赋值。

这个方法中需要解决一个问题,就是客户端传递时每个参数都是当成字符串来处理的,这种导致我们通过FormHtppMessageConverter转换成Map时,原本是对象的属性被识别成字符串,而不是object,结果就是在反序列化时会出错。好在,上面我们将需要提交的对象包装了一次,产生一个公共的object参数jsonParam,只需要处理这一个特殊对象。做法就是从Map取出jsonParam,然后对其内容进行反序列化,更新Map值,再次进行反序列化就正常了。
  
     上图中的做法目前有如下问题

  • 序列化的字段是约定好的,也是基于我们的post model基本上来处理的,是针对性的converter
  • 代码最后面调用的jackon的convertValue,对需要反序列化的对象类型有要求,好像不支持泛型类型,比如这种类型的就不行: CommonParamInfoDto<SearchParamInfo<ProductSearchInfo>>

完整的conveter代码如下,其实主要代码就是上图贴图中的那么对特定字段的序列化处理,其它的方法都是默认即可。

public class ObjectHttpMessageConverter implements HttpMessageConverter<Object> {

    private final FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
private final ObjectMapper objectMapper = new ObjectMapper(); private static final LinkedMultiValueMap<String, ?> LINKED_MULTI_VALUE_MAP = new LinkedMultiValueMap<>();
private static final Class<? extends MultiValueMap<String, ?>> LINKED_MULTI_VALUE_MAP_CLASS
= (Class<? extends MultiValueMap<String, ?>>) LINKED_MULTI_VALUE_MAP.getClass(); @Override
public boolean canRead(Class clazz, MediaType mediaType) {
return objectMapper.canSerialize(clazz) && formHttpMessageConverter.canRead(MultiValueMap.class, mediaType);
} @Override
public boolean canWrite(Class clazz, MediaType mediaType) {
return false;
} @Override
public List<MediaType> getSupportedMediaTypes() {
return formHttpMessageConverter.getSupportedMediaTypes();
} @Override
public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
Map input = formHttpMessageConverter.read(LINKED_MULTI_VALUE_MAP_CLASS, inputMessage).toSingleValueMap();
String jsonParamKey="jsonParam";
if(input.containsKey(jsonParamKey)) {
String jsonParam = input.get(jsonParamKey).toString();
SearchParamInfo<Object> searchParamInfo = new SearchParamInfo<Object>();
Object jsonParamObj = JsonHelper.json2Object(jsonParam, searchParamInfo.getClass());
input.put("jsonParam", jsonParamObj);
}
Object objResult= objectMapper.convertValue(input, clazz);
return objResult;
} @Override
public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws UnsupportedOperationException {
throw new UnsupportedOperationException("");
}
}

配置,写好了conveter之后,需要在配置文件中配置上才能生效。

最后,我们的方法就可以这样写,即可以支持 key/value对,也支持json

我的目的在于api的参数即能支持application/x-www-form-urlencoded也能支持application/json,上面是我目前能想到的办法,如果大家有其它更好的办法多多指点。

我又可以愉快的使用post man测试了。而且可以推荐第三方调用者优先使用json,我相信这种即能简化编程又方便调试的优点应该能够吸引它们。

rest api参数与content-type的更多相关文章

  1. 细说 Web API参数绑定和模型绑定

    今天跟大家分享下在Asp.NET Web API中Controller是如何解析从客户端传递过来的数据,然后赋值给Controller的参数的,也就是参数绑定和模型绑定. Web API参数绑定就是简 ...

  2. asp.net web api参数

    翻译自:http://www.c-sharpcorner.com/article/parameter-binding-in-asp-net-web-api/ 主要自己学习下,说是翻译,主要是把文章的意 ...

  3. Parameter Binding in ASP.NET Web API(参数绑定)

    Parameter Binding in ASP.NET Web API(参数绑定) 导航 阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnbl ...

  4. select2 api参数中文文档

    select2 api参数的文档   具体参数可以参考一下: 参数 类型 描述 Width 字符串 控制 宽度 样式属性的Select2容器div minimumInputLength int 最小数 ...

  5. org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported或其他Content type不支持处理

    很久没从头到尾搭框架,今天搭的过程中,springmvc controller方法入参用@RequestBody自动绑定参数时一直提示各种 not supported 排查问题有两个解决路径: 1)使 ...

  6. Java之POI读取Excel的Package should contain a content type part [M1.13]] with root cause异常问题解决

    Java之POI读取Excel的Package should contain a content type part [M1.13]] with root cause异常问题解决 引言: 在Java中 ...

  7. Jmeter发送post请求报错Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported

    常识普及: Content-type,在Request Headers里面,告诉服务器,我们发送的请求信息格式,在JMeter中,信息头存储在信息头管理器中,所以在做接口测试的时候,我们维护Conte ...

  8. 接入WxPusher微信推送服务出现错误:Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported

    背景 使用WxPusher微信推送服务 ,可以及时的将服务的一些运行异常信息,发送到自己的微信上,方便了解服务的运行状态(PS:这个服务是免费的). 你可以在这里看到WxPusher微信推送服务的接入 ...

  9. Jsoup问题---获取http协议请求失败 org.jsoup.UnsupportedMimeTypeException: Unhandled content type. Must be text/*, application/xml, or application/xhtml+xml.

    Jsoup问题---获取http协议请求失败 1.问题:用Jsoup在获取一些网站的数据时,起初获取很顺利,但是在访问某浪的数据是Jsoup报错,应该是请求头里面的请求类型(ContextType)不 ...

  10. Jsoup获取部分页面数据失败 org.jsoup.UnsupportedMimeTypeException: Unhandled content type. Must be text/*, application/xml, or application/xhtml+xml.

    用Jsoup在获取一些网站的数据时,起初获取很顺利,但是在访问某浪的数据是Jsoup报错,应该是请求头里面的请求类型(ContextType)不符合要求. 请求代码如下: private static ...

随机推荐

  1. Chrome开发者工具不完全指南(四、性能进阶篇)

    前言 Profiles面板功能的作用主要是监控网页中各种方法执行时间和内存的变化,简单来说它就是Timeline的数字化版本.它的功能选项卡不是很多(只有三个),操作起来比较前面的几块功能版本来说简单 ...

  2. 2013 duilib入门简明教程 -- 第一个程序 Hello World(3)

    小伙伴们有点迫不及待了么,来看一看Hello World吧: 新建一个空的win32项目,新建一个main.cpp文件,将以下代码复制进去: #include <windows.h> #i ...

  3. ComboTree 的json格式和引用

    在easyui内,用 <select>实现combotree. <td ><select class="easyui-combotree" url=& ...

  4. SQL注入

    @org.junit.Test public void testLogin() { CardDAO cd=new CardDAO(); if(cd.dengru("' or 1 = 1--& ...

  5. AngularJS移动开发中的各种坑

    捂脸,辛酸泪ing...... 本文主要涉及部分在移动设备上特有的问题. 相对来说,Jquery侧重DOM操作,AngularJS是以视图模型和双向绑定为核心的. DOM操作的问题 避免使用 jQue ...

  6. MongoDB replica set IDs do not match

    在搭建MongoDB(版本 3.2.9)的Replica Set时,使用 rs.status() 查看Replica Set的状态,发现一个成员异常:replica set IDs do not ma ...

  7. Ado.Net Destination 用法

    Ado Net Destination Component 使用Ado net Connection manager,其Data Access Mode 只有一种, table or view,组件的 ...

  8. EasyUI DateTimeBox设置默认时间的注意点

  9. 【WP开发】加密篇:双向加密

    说起双向加密,如果以前在.NET开发中弄过加/解密的朋友都不会陌生,常用的算法有DES.AES等.在RT应用程序中,也提供了加密相关的API,算法自然是一样的,只是API的封装方式不同罢了,因为RT不 ...

  10. OpenCASCADE BRep vs. OpenNURBS BRep

    OpenCASCADE BRep vs. OpenNURBS BRep eryar@163.com Abstract. BRep short for Boundary Representation. ...