squbsScala In Action我爱编程

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

2017-01-20  本文已影响183人  吕亦行

原文地址: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上下文在服务什么。

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上下文考虑用于任何路径匹配。
上一篇下一篇

猜你喜欢

热点阅读