Rest组件简介
rest组件可以用于开发restful接口、发起接口请求调用等。其作用等同于spring mvc + rest template。在学习过程中可以类比这两个库来学习。类比spring mvc的功能,后续用接口开发来表达;类比rest template的功能,后续用接口调用来表述。
开发指南
spring boot项目中引入相关依赖:
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-rest-starter</artifactId>
<version>x.x.x</version>
<!-- use the same version as your Camel core version -->
</dependency>
使用eeif框架的直接引入framework-idp即可
接口定义
基本使用格式如下:
语法:
rest:method:path[:uriTemplate]?[options]
示例:
<from uri="rest:get:/v1/user/info:{userId}?host={{app.server.info}}"/>
method支持9种请求方式get, post, put, delete, patch, head, trace, connect, options,在rest ful接口定义中通常使用get, post, put, delete。
path定义遵循rest规范包括路径/请求参数/路径参数的定义规范。请求header、请求body遵循eip整体语法,可以直接在流程中定义。
上面定义好的接口路径 最终的访问地址如下
http(s)://{ip:port}/{contextPath}/{camelContextPath}/v1/user/info/{userId}
这里面要有两个上下文根,一个是spring的,一个是camel的,具体配置参考如下:
########################################################
### 服务基本参数设置
########################################################
server:
servlet:
context-path: /idp
########################################################
### Camel配置
########################################################
camel:
servlet:
mapping:
context-path: /api/*
那么最终的接口访问路径就是:
http(s)://{ip:port}/idp/api/v1/user/info/{userId}
接口参数
在学习中参考参考Spring MVC来针对性的看下Rest组件的多种使用方式,Spring MVC中@RequestParam、@RequestBody、@RequestHeader、@PathVariable。
处理的Request的不同内容部分分为四类:(主要讲解常用类型)
A、处理requet uri 部分(这里指uri template中variable,不含queryString部分)的注解: @PathVariable;
B、处理request header部分的注解: @RequestHeader, @CookieValue;
C、处理request body部分的注解:@RequestParam, @RequestBody;
D、处理attribute类型是注解: @SessionAttributes, @ModelAttribute;
- @PathVariable
当使用@RequestMapping URI template 样式映射时, 即 someUrl/{paramId}, 这时的paramId可通过 @Pathvariable注解绑定它传过来的值到方法的参数上。
camel rest组件中 uriTemplate中定义的
rest:method:path[:uriTemplate]?[options]
# 例如 其中的sessionId userId
<from uri="rest:get:/v1/session/info:{sessionId}/{userId}"/>
# 对应的信息可以在header中读取
<transform>
<simple>${header.sessionId} ${header.userId}</simple>
</transform>
配置后可以在组件中获取对应的参数信息:
<route id="helloRestApi">
<from uri="rest:get:helloRestApi:/{me}?queryParameters={msg}{age}"/>
<transform>
<simple>{header.me}, age is {body}"/>
</route>
- @RequestParam
A) 常用来处理简单类型的绑定,通过 Request.getParameter() 获取的String可直接转换为简单类型的情况( String--> 简单类型的转换操作由ConversionService配置的转换器来完成);因为使用request.getParameter()方式获取参数,所 以可以处理get 方式中queryString的值,也可以处理post方式中 body data的值;
B)用来处理Content-Type: 为 application/x-www-form-urlencoded编码的内容,提交方式GET、POST;
C) 该注解有两个属性: value、required; value用来指定要传入值的id名称,required用来指示参数是否必须绑定;
rest:method:path[:uriTemplate]?[options]
# 例如 其中的sessionId userId
<from uri="rest:get:/v1/session/info?queryParameters={sessionId}{userId}"/>
- @RequestBody
该注解常用来处理Content-Type: 不是application/x-www-form-urlencoded编码的内容,例如application/json, application/xml。因为Spring MVC配置有FormHttpMessageConverter,所以也可以用来处理 application/x-www-form-urlencoded的内容,处理完的结果放在一个MultiValueMap<String, String>里。
body信息不需要额外定义,就可以直接使用, ${body}获取body内容
- @RequestHeader
header信息不用单独定义,可以直接${header.xxxx}获取对应的变量值。
另外Camel Message Header中会存放一些其他额外的请求信息,比如请求方式,一般来说Camel消息体的Header内容相对来说比较少。额外更多信息需要自己定义Processor获取,比如想获取请求的源地址。
- @ModelAttribute
该注解有两个用法,一个是用于方法上,一个是用于参数上;
用于方法上时:通常用来在处理@RequestMapping之前,为请求绑定需要从后台查询的model;用于参数上时:用来通过名称对应,把相应名称的值绑定到注解的参数bean上;要绑定的值来源于。
该情况在camle中可以用于请求的对象类型进行绑定
数据处理
使用内置组件处理
常见的内置组件包括simple、contant、header、property组件,可以对象属性进行基本的逻辑处理。
simple组件
<route id="simple_body_detail">
<from uri="rest:post:rest/simple/detail?queryParameters={message}"/>
<process ref="remoteIpProcessor"></process>
<!-- simple 获取json内容时 注意需要un marshal之后才能操作 ${body[name]} -->
<unmarshal><json library="Jackson"></json></unmarshal>
<setBody>
<simple>
{
"code": 200,
"data": {
"message": ${header.message},
"name": ${body[name]}
},
"remoteIp": ${exchangeProperty.RemoteAddress},
"name": ${exchangeProperty.name},
"body": ${body}
}
</simple>
</setBody>
<log message="${body}"/>
</route>
header语法
property语法
自定义process
routes定义了一系列的处理流程,prceoss是对每一步流程处理的具体逻辑实现。可以通过processor创建、修改processor的exchange对象,包括header、body、exchange property等属性信息。
import com.egoo.eeip.eeif.framework.base.util.server.IdGeneratorUtil;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
/**
* com.egoo.eeip.idp.server.process
*
* @author egoo
* @date 2022/9/26
*/
public class DemoProcess implements Processor {
@Override
public void process(Exchange exchange) throws Exception {
exchange.getIn().setHeader("seqNo", IdGeneratorUtil.nextId());
}
}
在camel context中注册对应processor
<bean id="remoteIpProcessor" class="com.egoo.eeip.eeif.framework.idp.processor.servlet.ServletRemoteIpProcessor"></bean>
路由中定一个和使用对应的processor
<route>
<processor ref="remoteIpProcessor"> </processor>
</route>
接口调用
报文组装
调用其他接口,就需要按照其他接口的接口格式进行数据包装,包括接口请求的方式method、请求头、请求体、请求的路径参数等进行组装。
示例程序如下:get请求 http://{{host}}/server/api/helloRestApi?msg=nihao&age=13 去调用post请求获取数据并返回结果
[图片上传失败...(image-54e7ab-1667102449354)]
代码配置如下:
<route id="helloRestApiCall">
<from uri="rest:get:helloRestApiCall?queryString={msg}{age}"/>
<setBody>
<simple>{"msg": "${header.msg}", "age": ${header.age}}</simple>
</setBody>
<removeHeaders pattern="*"></removeHeaders>
<setHeader name="CamelHttpMethod">
<constant>POST</constant>
</setHeader>
<setHeader name="Content-Type">
<constant>application/json;charset=UTF-8</constant>
</setHeader>
<log message="rest post ${body}"/>
<to uri="rest:post:/idp/v1/demo/map?host=http://localhost:9888&bridgeEndpoint=true"/>
</route>
本地post请求:
@RestController
@RequestMapping(value="/v1/demo", produces="application/json")
@Api(value="demo测试接口", description="demo测试接口")
@Slf4j
public class testController {
@PostMapping("/map")
@ResponseBody
public Object post(@RequestBody Map map){
System.out.println(map.toString());
map.put("code",0);
map.put("message","成功了");
return map;
}
}
结果处理
通常是对返回的报文进行重新整理满足不同业务前端的接口需求。结果处理通常使用jsonata组件来进行转换。
文件上传
通过MimeMultipart格式转换来处理,注意该组件依赖邮件组件,通过unmarshal将multipart格式转换成不带附件的普通格式,marshal转换程multipart格式。
<route>
<from uri="rest:post:upload"></from>
<unmarshal>
<mimeMultipart></mimeMultipart>
</unmarshal>
<to uri="file:download?fileName=test.png"></to>
<!-- 避免request header中默认的content type作为返回值返回 -->
<setHeader name="Content-Type"><constant>application/json</constant></setHeader>
<setBody>
<simple>{"code": 200, "msg": "upload file success"}</simple>
</setBody>
</route>
遗留问题:
- 获取文件名称
一般在转换前要获取到文件名,用于保存的时候使用,mimeMultipart并未提供相关功能。参考自定义processor来处理。
<route id="multipart_file_processor">
<from uri="rest:post:upload1"></from>
<process ref="multipartFileProcessor"></process>
<log message="${headers} ${body}"></log>
<to uri="file:download/tmp?fileName=${header.CamelFileName}"></to>
<setHeader name="Content-Type"><constant>application/json</constant></setHeader>
<setBody>
<simple>{"code": 200, "msg": "upload file success"}</simple>
</setBody>
</route>
- 一次上传多个文件
上传文件通过marshal格式化后,通过body读取相关内容,读取的内容需要根据boundary分割
<route>
<from uri="rest:post:upload1/{path}"></from>
<marshal>
<mimeMultipart></mimeMultipart>
</marshal>
<log message="${body}"></log>
</route>
收到的body内容如下所示:每一个item内容Content-Dispositoin,由header中的Content-Type中的boundary进行分割。
Content-Type=multipart/form-data; boundary=--------------------------065816312379776045391551
[图片上传失败...(image-a87caf-1667102449354)]
针对报文编写内容解析来完成报文解析处理。详细参考eeif框架 MultipartFileProcessor。
- 文件和其他参数同时用multipart方式,建议其他参数用路径参数或者query参数
<route>
<from uri="rest:post:upload/{path}"></from>
<unmarshal>
<mimeMultipart></mimeMultipart>
</unmarshal>
<to uri="file:download?fileName=/${header.path}/test.png"></to>
<setHeader name="Content-Type"><constant>application/json</constant></setHeader>
<setBody>
<simple>{"code": 200, "msg": "upload file success"}</simple>
</setBody>
</route>
接口管理
通过rest-api和swagger ui管理rest api接口
参考资料
- https://www.openapis.org/