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
,你还可以通过重写相应的rejectionHandler
和exceptionHandler
方法来提供 RejectionHandler
和 ExceptionHandler
。这些可以在上面的例子中看到。
请参考 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 API和HTTP 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
,你还可以通过重写相应的 rejectionHandler
和exceptionHandler
方法提供RejectionHandler
和ExceptionHandler
。这些可以在以上的例子中看到。
请参考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.conf
或reference.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-context
和my-context/v2
作为web上下文,基于上下文是被注册的。如果 my-context
和my-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的规则和行为
-
Concurrent state access: 提供的
route
可以被使用在多连接、多线程、并发上。至关重要的是,如果通过route
访问封装的RouteDefinition
(Scala)或AbstractRouteDefinition
(Java) 类中的任何状态,它可以是读写并发的。在封装类中读取和写入可变状态是不安全的。在这个情况下,使用Akka的Actor
或Agent
是非常推荐的。 -
Access to actor context:
RouteDefinition
/AbstractRouteDefinition
默认通过Scala中的context
或Java中的context()
访问ActorContext
。它将默认用于创建新的actor或访问其他actor。 -
Access to web context: 针对Scala中的
RouteDefinition
,如果WebContext
特性已混合,它将可以访问到webContext
字段。Java 中的AbstractRouteDefinition
在所有情况下都提供webContext()
方法。在RouteDefinition
/AbstractRouteDefinition
处理请求时,这个字段/方法用来定义web上下文或从根开始的路径。
低级流(Flow)API的规则和行为
当你实现 FlowDefinition
(Scala) 或 AbstractFlowDefinition
(Java)时请记住以下几条规则:
- Exactly one response: 应用程序有责任为每个请求生成一个响应。
- Response ordering: 响应的顺序与相关联的请求的顺序匹配(在HTTP流水线开启,多重进入的请求重叠时会关联)
-
Concurrent state access: flow可以实例化多次,导致
Flow
本身的多个实例。如果这些实例访问封装在FlowDefinition
或AbstractFlowDefinition
中的状态,重要的支出这些访问是读写并发的。访问在封装类中读写可变的状态是不安全的。AkkaActor
和Agent
在这种情况下非常推荐使用。 -
Access to actor context:
FlowDefinition
/AbstractFlowDefinition
默认通过context
字段 (Scala) 或者context()
方法 (Java)访问ActorContext
。这个用于创建新的actor或访问其他actor。 -
Access to web context: 对于Scala的
FlowDefinition
,如果WebContext
特性已经混合,它将能访问到字段webContext
。在JAVA中,AbstractFlowDefinition
提供了webContext()
方法在所有的情况中。在FlowDefinition
/AbstractFlowDefinition
处理请求时,这个字段/方法用来定义web上下文或从根开始的路径 -
Request path:
HttpRequest
对象未经修改的传递到这个流(flow)。webContext
在请求中的Path
中。这是一个通过webContext
的知识来处理请求的用户任务(如上所示)。换句话说,低级API直接处理HttpRequest
并且需要手动将web上下文考虑用于任何路径匹配。