squbs-4.实现HTTP(S)服务

原文地址:Implementing HTTP(S) Services

概貌

HTTP是最普遍的集成协议。它基于web服务,包括客户端和服务端。Akka HTTP提供强大的服务端和客户端API。squbs意图保持这些API不改变。相反,squbs提供了基础设施,允许生产就绪使用这些API,通过为HTTP监听提供标准的配置,服务可以接受和处理请求,并且管道允许在请求到达应用之前或是响应离开应用进入导线之前进行打日志、监控、认证/授权。

squbs支持Akka HTTP,包括高级和低级的服务端API,来定义服务。这两个API享有全面的诸如监听器、管道、日志、监控的产品支持。另外,squbs同时支持Scala和Java风格的服务定义。这些服务处理器声明在class中,通过META-INF/squbs-meta.conf文件中的squbs-services条目的元数据注册在squbs中。每个风格的服务以相同的方式被注册,只需通过提供class名称和配置。

所有的squbs服务定义可以访问字段context, 这是akka.actor.ActorContext, 用于访问actor系统,调度器和一些Akka设施。

依赖(Dependencies)

为启动服务和注册服务定义,以下的依赖是需要的:

"org.squbs" %% "squbs-unicomplex" % squbsVersion

定义服务

服务可以通过Scala或者Java定义,通过高级或低级API。服务定义类必须无参构造,并且有序的注册来处理进来的Http请求。

高级Scala API

高级服务端API通过Akka HTTP's Route工件和指令呈现。使用Route处理请求,只需要提供一个继承于 org.squbs.unicomplex.RouteDefinition的特性并提供一个 route 函数如下:

import akka.http.scaladsl.server.Route
import org.squbs.unicomplex.RouteDefinition

class PingPongSvc extends RouteDefinition {

  def route: Route = path("ping") {
    get {
      complete("pong")
    }
  }
  
  // Overriding the rejectionHandler is optional
  override def rejectionHandler: Option[RejectionHandler] =
    Some(RejectionHandler.newBuilder().handle {
      case ServiceRejection => complete("rejected")
    }.result())

  // Overriding the exceptionHandler is optional
  override def exceptionHandler: Option[ExceptionHandler] =
    Some(ExceptionHandler {
      case _: ServiceException => complete("exception")
    })
}

除了定义route,你还可以通过重写相应的rejectionHandlerexceptionHandler方法来提供 RejectionHandlerExceptionHandler 。这些可以在上面的例子中看到。

请参考 Akka HTTP high-level API, Routing DSL, Directives, Rejection, 和Exception Handling 文档来充分利用这些API

低级Scala API

使用Scala低级API,只需要继承org.squbs.unicomplex.FlowDefinition和重写 flow 方法。使用Scala DSL时 flow需要 Flow[HttpRequest, HttpResponse, NotUsed] 类型, Akka HTTP提供的模板如下:

scala
import akka.http.scaladsl.model.Uri.Path
import akka.http.scaladsl.model._
import akka.stream.scaladsl.Flow
import org.squbs.unicomplex.FlowDefinition

class SampleFlowSvc extends FlowDefinition {

  def flow = Flow[HttpRequest].map {
    case HttpRequest(_, Uri(_, _, Path("ping"), _, _), _, _, _) =>
      HttpResponse(StatusCodes.OK, entity = "pong")
    case _ =>
      HttpResponse(StatusCodes.NotFound, entity = "Path not found!")
}

在 Akka HTTP低级服务端API中呈现了提供Flow的访问。在构造更复杂Flow时,请参考 Akka HTTP low-level APIHTTP Model 文档了解更多信息。

高级Java API

高级服务端API通过 Akka HTTP's Route 的工件和指令呈现。使用一个Route来处理请求,只需要提供一个继承于org.squbs.unicomplex.RouteDefinition 特征的类,并提供如下route方法:

import akka.http.javadsl.server.ExceptionHandler;
import akka.http.javadsl.server.RejectionHandler;
import akka.http.javadsl.server.Route;
import org.squbs.unicomplex.AbstractRouteDefinition;

import java.util.Optional;

public class JavaRouteSvc extends AbstractRouteDefinition {

    @Override
    public Route route() {
        return route(
                path("ping", () ->
                        complete("pong")
                ),
                path("hello", () ->
                        complete("hi")
                ));
    }

    // Overriding the rejection handler is optional
    @Override
    public Optional<RejectionHandler> rejectionHandler() {
        return Optional.of(RejectionHandler.newBuilder()
                .handle(ServiceRejection.class, sr ->
                        complete("rejected"))
                .build());
    }

    // Overriding the exception handler is optional
    @Override
    public Optional<ExceptionHandler> exceptionHandler() {
        return Optional.of(ExceptionHandler.newBuilder()
                .match(ServiceException.class, se ->
                        complete("exception"))
                .build());
    }
}

除了定义route,你还可以通过重写相应的 rejectionHandlerexceptionHandler方法提供RejectionHandlerExceptionHandler。这些可以在以上的例子中看到。

请参考Akka HTTP high-level API, Routing DSL, Directives, Rejection, 和Exception Handling 文档来充分的利用这些API

低级Java API

使用低级JAVA API,只需要继承 org.squbs.unicomplex.AbstractFlowDefinition和重写 flow方法。使用JAVA DSL时flow应当为Flow[HttpRequest, HttpResponse, NotUsed] 类型,模板由 Akka Http提供。注意下面的导入:

java
import akka.NotUsed;
import akka.http.javadsl.model.*;
import akka.stream.javadsl.Flow;
import org.squbs.unicomplex.AbstractFlowDefinition;

public class JavaFlowSvc extends AbstractFlowDefinition {

    @Override
    public Flow<HttpRequest, HttpResponse, NotUsed> flow() {
        return Flow.of(HttpRequest.class)
                .map(req -> {
                    String path = req.getUri().path();
                    if (path.equals(webContext() + "/ping")) {
                        return HttpResponse.create().withStatus(StatusCodes.OK).withEntity("pong");
                    } else {
                        return HttpResponse.create().withStatus(StatusCodes.NOT_FOUND).withEntity("Path not found!");
                    }
                });
    }
}

注意:, 访问上下文的webContext()context() 方法由AbstractFlowDefinition 类提供。

Akka Http低级服务端API提供了对Flow表现的访问。构建更复杂的 Flow,请参考 Akka HTTP low-level API, Akka Streams,和Http model 文档获得更多信息。

Service注册

服务的元数据在META-INF/squbs-meta.conf文件中声明,如下例所示:

cube-name = org.sample.sampleflowsvc
cube-version = "0.0.2"
squbs-services = [
  {
    class-name = org.sample.SampleFlowSvc
    web-context = sample # You can also specify bottles/v1, for instance.
    
    # The listeners entry is optional, and defaults to 'default-listener'.
    listeners = [ default-listener, my-listener ]
    
    # Optional, defaults to a default pipeline.
    pipeline = some-pipeline
    
    # Optional, disables the default pipeline if set to false.
    defaultPipelineOn = true                
    
    # Optional, only applies to actors.
    init-required = false
  }
]

这个class-name参数确认服务定义,它既可以使用高级或低级API,也可以通过JAVA或者Scala实现。

web-context是一个唯一的字符串,它唯一的表示分发到这个服务的请求的web上下文。请参考下文中关于web上下文的讨论 The Web Context

可选的,监听器参数声明了绑定这项服务的监听器列表。监听绑定在下面的Listener Binding 模块讨论。

pipeline是一组处理请求前后的预处理器和后处理器。pipeline的名称通过pipeline参数定义。与指定的pipeline一起,配置中默认定义的一组pipeline会插入到请求和响应中。想要关闭当前服务默认的pipeline。你可以在META-INF/squbs-meta.conf中设置defaultPipelineOn = false 。请参考Streaming Request/Response Pipeline 获得更多信息。

监听绑定

有别于直接编程 Akka HTTP,squbs通过它们的监听提供所有套接字绑定和连接管理。只需通过上面讨论的一个或多个API提供请求/响应处理器,就能这些实现注册到squbs。这使得跨服务的绑定配置标准化,并且可以跨服务的统一配置管理。

监听在application.confreference.conf配置文件中声明,通常配置在项目路径src/main/resources下。监听器声明了接口、端口、https安全参数和名称依赖,在 Configuration中有解释。

一个服务处理器将自己附加到一个或多个监听当中。listeners属性是一列监听器或需要绑定处理器的别名。如果监听器没有定义,它将默认使用 default-listener

通配符 "*"是一个特殊的情况(注意:它必须带引号,否则不会被正确的解释),它会将这个处理器附加到所有的监听器上。然而,如果它还没有被处理器上的具体附件激活,他本身不会激活任何监听。如果这个处理器应该激活默认的监听并附加到由其他程序激活的任何监听器时,那么这个具体的附件应被单独指定如下:

listeners = [ default-listener, "*" ]

Web上下文

任何服务的入口都是通过以字符/分段的路径来绑定唯一web上下文。举例来说,如果注册过的话,urlhttp://mysite.com/my-context/index 会匹配"my-context"上下文。如果"my-context"没有注册过,他同样会匹配到根上下文。web上下文不一定是斜杠分隔的路径的第一个。基于上下文注册,它可以匹配多个分段。一个具体的例子可以是一个带服务版本的URL。地址http://mysite.com/my-context/v2/index可以任意将 my-contextmy-context/v2 作为web上下文,基于上下文是被注册的。如果 my-contextmy-context/v2都被注册,那么最长的匹配-在这里的情况是 my-context/v2 将会被路由请求使用。这将对存在不同版本的web接口或者不同cubes/模块的API非常有用。

注册web上下文 必须不/ 字符开头。对于多段上下文情况,字符/可以作为段分隔符存在。它允许 "" 作为根节点上下文。如果多个服务匹配这个请求,那么最长的请求优先。

每当web上下文在元数据中进行注册,route,尤其是在低等级API中定义的 flow需要知道web上下文在服务什么。

  • JAVA服务处理器类可以直接访问 webContext() 方法。

  • Scala服务处理器类将混合org.squbs.unicomplex.WebContext特性。这将在你的类中加入如下字段。

    val webContext: String
    

webContext字段被初始化为在构建对象时在元数据中设置的注册web上下文的值,如下所示:

scala
class SampleFlowSvc extends FlowDefinition with WebContext {

  def flow = Flow[HttpRequest].map {
    case HttpRequest(_, Uri(_, _, Path(s"$webContext/ping"), _, _), _, _, _) =>
      HttpResponse(StatusCodes.OK, entity = "pong")
    case _ =>
      HttpResponse(StatusCodes.NotFound, entity = "Path not found!")
  }
}

高级路由(Route)API的规则和行为

  1. Concurrent state access: 提供的route可以被使用在多连接、多线程、并发上。至关重要的是,如果通过route访问封装的RouteDefinition (Scala)或 AbstractRouteDefinition (Java) 类中的任何状态,它可以是读写并发的。在封装类中读取和写入可变状态是不安全的。在这个情况下,使用Akka的 ActorAgent是非常推荐的。
  2. Access to actor context: RouteDefinition/AbstractRouteDefinition默认通过Scala中的context或Java中的context()访问ActorContext。它将默认用于创建新的actor或访问其他actor。
  3. Access to web context: 针对Scala中的 RouteDefinition,如果 WebContext特性已混合,它将可以访问到 webContext字段。Java 中的AbstractRouteDefinition 在所有情况下都提供webContext()方法。在RouteDefinition/AbstractRouteDefinition处理请求时,这个字段/方法用来定义web上下文或从根开始的路径。

低级流(Flow)API的规则和行为

当你实现 FlowDefinition (Scala) 或 AbstractFlowDefinition (Java)时请记住以下几条规则:

  1. Exactly one response: 应用程序有责任为每个请求生成一个响应。
  2. Response ordering: 响应的顺序与相关联的请求的顺序匹配(在HTTP流水线开启,多重进入的请求重叠时会关联)
  3. Concurrent state access: flow可以实例化多次,导致Flow本身的多个实例。如果这些实例访问封装在 FlowDefinitionAbstractFlowDefinition中的状态,重要的支出这些访问是读写并发的。访问在封装类中读写可变的状态是不安全的。Akka ActorAgent在这种情况下非常推荐使用。
  4. Access to actor context: FlowDefinition/AbstractFlowDefinition默认通过context 字段 (Scala) 或者 context() 方法 (Java)访问 ActorContext。这个用于创建新的actor或访问其他actor。
  5. Access to web context: 对于Scala的FlowDefinition,如果 WebContext特性已经混合,它将能访问到字段 webContext。在JAVA中,AbstractFlowDefinition提供了 webContext() 方法在所有的情况中。在FlowDefinition/AbstractFlowDefinition 处理请求时,这个字段/方法用来定义web上下文或从根开始的路径
  6. Request path: HttpRequest对象未经修改的传递到这个流(flow)。webContext 在请求中的Path中。这是一个通过webContext的知识来处理请求的用户任务(如上所示)。换句话说,低级API直接处理HttpRequest并且需要手动将web上下文考虑用于任何路径匹配。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容