05-Quarkus 搭建Rest服务-01

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
表单参数@RestFormContent-Type为 application/x-www-form-urlencoded 时body中的参数
矩阵参数RestMatrixkey=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.请求和响应值封装

MessageBodyReaderand 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;
    }

}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值