最近为项目组提供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. WebDriver API元素的定位

    一.以下截图为用FireBug定位的用火狐(Firefox)浏览器打开的百度首页,下面所讲述的八种定位方法,就是以该截图中的百度输入框为例子. ①.FireBug是Firefox浏览器下的开发类插件, ...

  2. sizzle分析记录:关于querySelectorAll兼容问题

    querySelector和querySelectorAll是W3C提供的新的查询接口 目前几乎主流浏览器均支持了他们.包括 IE8(含) 以上版本. Firefox. Chrome.Safari.O ...

  3. JAVA基础代码分享--模拟人机猜拳系统

    问题描述: 一.主要功能: .电脑与人互动,实现“剪刀.石头.布”的游戏: 1.1 角色登陆: ******************** ***欢迎进入猜拳游戏*** **************** ...

  4. google浏览器截图工具 Open Screenshot(代码截图)

    有的时候特别想把自己写的代码保存为图片,但是代码的内容很长,用普通的截图工具只能一次一次的拼接起来,太麻烦了.这里使用了Google的截图插件,很好用. 或者使用360极速浏览器-->保存网页也 ...

  5. 跟着9张思维导图学习Javascript

    学习的道路就是要不断的总结归纳,好记性不如烂笔头,so,下面将 po 出我收集的 9 张 javascript 相关的思维导图(非原创). 思维导图小tips: 思维导图又叫心智图,是表达发射性思维的 ...

  6. UWP开发之Mvvmlight实践三:简单MVVM实例开发(图文详解付代码)

    在做MVVM各种框架对比之前,我觉得有必要先自己做一个简单的MVVM实现案例比较好,这样就可以看到自己实现的时候有那些不方便的地方.而各种框架又是怎么解决我们这些麻烦的. 案例介绍:用户登录画面,没有 ...

  7. Deadlock Detecting--转

    /* Java Threads, 3rd Edition By Scott Oaks, Henry Wong 3rd Edition September 2004 ISBN: 0-596-00782- ...

  8. WebSocket 介绍(一)

    WebSocket 发起单个请求,服务端不需要等待客服端,客户端在任何时候也能发消息到服务端,减少了轮询时候的延迟.经历一次连接后,服务器能给客户端发多次.下图是轮询与WebSocket的区别. 基于 ...

  9. js构建ui的统一异常处理方案(四)

    上一篇我们介绍了统一异常处理方案的设计方案,这一篇我们将直接做一个小例子,验证我们的设计方案. 例子是一个todo的列表界面(页面代码参考于https://github.com/zongxiao/Dj ...

  10. Android之仿ele地图定位效果

    PS:最近项目要求,希望在选择地址的时候能够仿ele来实现定位效果.因此就去做了一下.不过ele使用高德地图实现的,我是用百度地图实现的.没办法,公司说用百度那就用百度的吧.个人觉得高德应该更加的精准 ...