Rest services
从本篇文章开始讲如何使用Quarkus搭建Web服务,如搭建REST 服务、参数校验、过滤器、拦截器、权限角色、API文档、异步路由等等。
本章目标
- 1.搭建一个REST服务
- 2.定义一个接口
- 3.访问上下文
- 4.阻塞和非阻塞模式
- 5.全局异常处理
- 6.过滤器
- 7.请求和响应值封装
- 8.拦截器
- 9.参数转换
1. 搭建一个REST 服务
在我们前面的文章中就应经使用了Quarkus的Web服务,在以前的代码中我们引入了quarkus-resteasy
这个依赖是JAX-RS的实现,但是最近读文档时发现了一个新的依赖quarkus-resteasy-reactive
,从名字就可以看出这是一个响应式的实现。我们使用这个框架就是看中了它的响应式,所以我们用响应式的这个实现。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
其实这样就可以写我们的REST服务了。
2. 定义一个HTTP接口
这个我们应该也很熟悉了
@Path("")
public class Endpoint {
@GET
public String hello(){
return "Hello, World!";
}
}
@Path: 用在类上,表示将当前类下所有的配置了Http Method注解修饰的方法暴露为一个Endpoint,也就是一个接口。
@GET 就是一个HTTP method 注解
2.1 常用的HTTP 注解
注解 | 介绍 |
---|---|
@GET | 表示一个GET请求 |
@HEAD | 和没有Body的GET相同 HEAD |
@POST | 表示一个POST请求 |
@PUT | 表示一个PUT请求 |
@DELETE | 表示一个DELETE请求 |
还有比如@OPTIONS等,基本不会用上,就不写了。除了用官方提供的我们还可以自定义注解:
@Retention(RetentionPolicy.RUNTIME)
@HttpMethod("FROMAGE")
@interface FROMAGE {
}
2.2 指定请求和响应的ContentType
这个和SpringMVC中的用法差不多,提供了@Consumes
和 @Produces
。 所有的Type类型都封装了MediaType中。
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
2.3 提取请求中各种属性的方法
@Path("/cheeses/{type}")
public class Endpoint {
@POST
public String allParams(@RestPath String type,
@RestMatrix String variant,
@RestQuery String age,
@RestCookie String level,
@RestHeader("X-Cheese-Secret-Handshake")
String secretHandshake,
@RestForm String smell){
return type + "/" + variant + "/" + age + "/" + level + "/" + secretHandshake + "/" + smell;
}
}
请求:
POST /cheeses;variant=goat/tomme?age=matured HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Cookie: level=hardcore
X-Cheese-Secret-Handshake: fist-bump
smell=strong
Http元素 | 注解 | 备注 |
---|---|---|
路径参数 | @RestPath (or nothing) | URL中的参数类似/http/{path} 也可以通过@PathParam接收 |
查询参数 | @RestQuery | ?key=value |
Header | @RestHeader | |
Cookie | @RestCookie | |
表单参数 | @RestForm | Content-Type为 application/x-www-form-urlencoded 时body中的参数 |
矩阵参数 | RestMatrix | key=value;key=value 这种形式 参考 |
2.4 请求参数和响应值
可以参考上一节,不过一般Rest服务的话我们都有以aplication/json
作为我们的Content-Type,这样的话需要加上下面的注解:
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
同时需要注意的是,我们需要将依赖换成 quarkus-resteasy-reactive-jackson 、quarkus-resteasy-reactive-jsonb 、 quarkus-resteasy-jackson 、 quarkus-resteasy-jsonb
注意区分是否是reactive模式的和选择哪种序列化方式。
2.5.全局异常处理
/**
* @version v1.0
* @Description: Rest service 全局异常处理
* @Author: TongRui乀
* @Date: 2021/1/24 2:46 下午
*
* 返回值只支持 Response 或者 Uni<Response>
*/
@Slf4j
public class GlobalExceptionHandler {
@ServerExceptionMapper
public Response globalExceptionHandler(Exception e){
log.error("global exception ", e);
return Response.status(404).entity("not found").build();
}
}
这里需要注意的是,异常处理方法的返回值 只能是Resonse 或者是异步模式下Uni
2.6.过滤器
这里提供了两种过滤器实现方式,但原理应该是一样的。一个是基于注解@ServerRequestFilter
注解,另一种方式是实现ContainerRequestFilter
接口。
@Slf4j
@Provider
public class LoggingFilter implements ContainerRequestFilter {
@Context
UriInfo info;
@Context
HttpServerRequest request;
@Override
public void filter(ContainerRequestContext context) {
final String method = context.getMethod();
final String path = info.getPath();
final String address = request.remoteAddress().toString();
log.info("Request {} {} from IP {}", method, path, address);
}
}
@Slf4j
public class RestServiceFilter {
@ServerRequestFilter(preMatching = true)
public void preMatchingFilter(ContainerRequestContext requestContext) {
log.info("Request Filter method:{}, path:{}", requestContext.getMethod(), requestContext.getUriInfo().getPath());
// make sure we don't lose cheese lovers
if("yes".equals(requestContext.getHeaderString("Cheese"))) {
requestContext.setRequestUri(URI.create("/cheese"));
}
}
//@ServerRequestFilter
public Response getFilter(ContainerRequestContext requestContext) {
// only allow GET methods for now
log.info("Request Filter method:{}, path:{}", requestContext.getMethod(), requestContext.getUriInfo().getPath());
if(requestContext.getMethod().equalsIgnoreCase(HttpMethod.GET) && !requestContext.getUriInfo().getPath().contains("flumes")) {
// todo 提示不能使用这个方法终端线程
//requestContext.abortWith(Response.status(Response.Status.METHOD_NOT_ALLOWED).build());
// return null 表示继续执行
return null;
}
return Response.status(404).entity("only get method permited").build();
}
@ServerResponseFilter
public void getFilter(ContainerResponseContext responseContext) {
log.info("Response Filter");
Object entity = responseContext.getEntity();
if(entity instanceof String) {
// make it shout
responseContext.setEntity(((String)entity).toUpperCase());
}
}
}
2.7.请求和响应值封装
MessageBodyReader
and MessageBodyWriter
@Provider
public class MessageReaderAndWrite implements MessageBodyReader<FroMage>, MessageBodyWriter<FroMage> {
@Override
public boolean isReadable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
return type == FroMage.class;
}
@Override
public FroMage readFrom(Class<FroMage> aClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> multivaluedMap, InputStream inputStream) throws IOException, WebApplicationException {
String message = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
if(message.contains("_Formage_")){
return new FroMage(message.substring(9));
}
throw new RuntimeException("param invalid");
}
@Override
public boolean isWriteable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
return type == FroMage.class;
}
@Override
public void writeTo(FroMage froMage, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> multivaluedMap, OutputStream outputStream) throws IOException, WebApplicationException {
outputStream.write(("_Formage_" + froMage.getName()).getBytes());
}
}
2.8 拦截器
同时也提供了 Reader 和 Writer的拦截器
@Provider
public class FroMageIOInterceptor implements ReaderInterceptor, WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
System.err.println("Before writing " + context.getEntity());
context.proceed();
System.err.println("After writing " + context.getEntity());
}
@Override
public Object aroundReadFrom(ReaderInterceptorContext context)
throws IOException, WebApplicationException {
System.err.println("Before reading " + context.getGenericType());
Object entity = context.proceed();
System.err.println("After reading " + entity);
return entity;
}
}
2.9.参数转换
@Provider
class MyConverterProvider implements ParamConverterProvider {
@Override
public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType,
Annotation[] annotations) {
// declare a converter for this type
if(rawType == Converter.class) {
return (ParamConverter<T>) new MyConverter();
}
return null;
}
}