M64-REQUEST
in Non-Sense with 0 comment

M64-REQUEST

in Non-Sense with 0 comment

0x00 概述

这两天在整理框架,发现自己手里大约有小十套不同的后端框架了,有mvc的,有cloud的,有netty的,有vertx的,还有手写的servlet的,虽然久远了点,但是试了下还能用...现在spring是主流,顺手就整理了一波相关的,升级下军火库,捎带着记录下一些感觉有意思的封装,其实都大同小异。

前后端分离是目前的比较基础的架构需求,分离后,主要是充分利用HTTP的无状态,同时结合幂等,TOKEN等,可轻松实现扩展性,移植性,复用性,动态平衡等等,进一步可实现函数化服务,服务网格,serverless等,从领域角度,更低的耦合,更高的内聚,更丰富的组合。

0x01 结构

HTTP实现无状态的两个主体,即request和response,在进行前后端分离框架设计的时候,不可避免的需要对其进行封装,一个作用是框,包括req和resp的结构,body的结构,状态码的含义规范等,一个作用是架,包括顺序的执行流程,发生异常错误时的处理,过程信息日志的记录等。

框的部分,最终成果物会是一系列规范,目前实践情况最多的就是rest接口规范的各种妥协后的变种,这一块各家有各家的习惯,主要几个点,动词的使用,URI结构,JSON的使用,分页等状态参数的使用,我手里有一套我习惯的,后续整理下再分享。

架的部分,最终成果物会是一堆代码,这一块就更是五花八门了,不可多说,就只说一下,我个人基于Spring mvc,对request/response做的一些简单的封装,用来满足前后端分离,自定义扩展,结构规范,日志记录等几个简单的点。

先上个简单的图示
20200114154855.png

先大致说明下,我选了一些奇奇怪怪的颜色大致区分了下,

0x02 Request 封装

最左边是关于request的拦截并封装,主要需要实现的有两个:

使用Filter拦截

通过filter拦截原始Request,然后进行相关的操作,保证真正进入到主体程序里面的request是符合要求的

增加额外字段

拦截的同时,根据需要设置额外的request attributes,常见的:

不要增加业务相关的,不要增加业务相关的,不要增加业务相关的,要不然你会发现,越加越多

业务相关的字段或者判断,请使用interceptor,交给spring来管理,多种业务的可以分开创建,常见的业务如,身份验证,权限验证等等,后续如果有时间写权限的话,再细说。

     HttpServletRequest httpRequest = (HttpServletRequest) (request);
     String requestId = IdHelper.getId24bit();
     MDC.put(ApiConstants.REQUEST_ID, requestId);
     request.setAttribute(ApiConstants.REQUEST_ID, requestId);
     request.setAttribute(ApiConstants.REQUEST_START_TIME, System.nanoTime());
     String requestUri = urlPathHelper.getOriginatingRequestUri(httpRequest);
     request.setAttribute(ApiConstants.REQUEST_URL, requestUri);
     String method = httpRequest.getMethod();
     request.setAttribute(ApiConstants.REQUEST_METHOD, method);

提供BODY的多次读取

这个主要是因为本身Servlet Request设计的时候,body部分通过流形式读取,只能够读取一次。

一般情况下,这一次的读取是发生在controller部分,读取到某个特定的类,然后进行判断,转化为配置好的对象使用,这里原始的设计又能引申到一个约定和配置的关系问题。暂时就说这样设计的结果,会导致所有需要使用请求bod体y的逻辑,都要发生在controller读取之后,而controller往往又是业务开始的地方,这样很容易就会造成业务耦合。

最常见的就是日志需求,系统希望能够记录每个请求的具体内容并打印到日志中,最直接的做法,是在每个controller里面的mapping方法,增加一行输出,明显的不好。

这里通过Filter实现的思路是这样的,主要利用servlet的request wrapper,改写读取body流的两个方法,引入一个临时的字节数组,第一次读取后,将body内容存在数组中,后续的读取,都是通过这个数组。

     /** Body Buffer */
     private final byte[] requestBodyBuffer;
   
     public RequestWrapper(HttpServletRequest request) {
       super(request);
       this.requestBodyBuffer = RequestUtil.getByteBody(request);
     }
   
     /**
      * getReader
      *
      * <p>Override getReader, point to getInputStream
      *
      * @author Created by ivan at 上午11:28 2020/1/13.
      * @return java.io.BufferedReader
      */
     @Override
     public BufferedReader getReader() {
       ServletInputStream inputStream = getInputStream();
       return Objects.isNull(inputStream)
           ? null
           : new BufferedReader(new InputStreamReader(inputStream));
     }
   
     /**
      * getInputStream
      *
      * <p>Override getInputStream, get request body from buffer
      *
      * @author Created by ivan at 上午11:28 2020/1/13.
      * @return javax.servlet.ServletInputStream
      */
     @Override
     public ServletInputStream getInputStream() {
       if (ObjectUtils.isEmpty(requestBodyBuffer)) {
         return null;
       }
       final ByteArrayInputStream bais = new ByteArrayInputStream(requestBodyBuffer);
       return new ServletInputStream() {
   
         @Override
         public boolean isFinished() {
           return false;
         }
   
         @Override
         public boolean isReady() {
           return false;
         }
   
         @Override
         public void setReadListener(ReadListener readListener) {}
   
         @Override
         public int read() {
           return bais.read();
         }
       };
     }

0x03 待续

这样对Request做了简单的封装,保证一些基本的属性,每个request都具备,主要用于服务后续的系统使用,例如权限拦截器,日志系统等。

接下来,request会通过拦截器等,最终进入到mapping的controller,然后进行相关的逻辑处理,最后Response回去。

这里主要是返回,牵扯到正常的返回,异常的返回,返回内容的结构,可读性等问题,同时response,代表了一次请求周期的完成,这时候还应有一些基本的日志的输出,或者满足一些service matrix,监控等需求的输出实现。

此部分,待续...

Responses