【问题标题】:Can Spray.io routes be split into multiple "Controllers"?Spray.io 路由可以拆分为多个“控制器”吗?
【发布时间】:2013-02-01 19:22:03
【问题描述】:

我还没有找到将 Spray.io 路由拆分为多个文件的可靠示例或结构。我发现我的路由的当前结构将变得非常繁琐,最好将它们抽象为一个非常简单的 REST API 应用程序的不同“控制器”。

文档似乎没有太大帮助:http://spray.io/documentation/spray-routing/key-concepts/directives/#directives

这是我目前所拥有的:

class AccountServiceActor extends Actor with AccountService {

  def actorRefFactory = context

  def receive = handleTimeouts orElse runRoute(demoRoute)

  def handleTimeouts: Receive = {
    case Timeout(x: HttpRequest) =>
      sender ! HttpResponse(StatusCodes.InternalServerError, "Request timed out.")
  }
}


// this trait defines our service behavior independently from the service actor
trait AccountService extends HttpService {

  val demoRoute = {
    get {
      path("") {
        respondWithMediaType(`text/html`) { // XML is marshalled to `text/xml` by default, so we simply override here
          complete(index)
        }
      } ~
      path("ping") {
        complete("PONG!")
      } ~
      path("timeout") { ctx =>
        // we simply let the request drop to provoke a timeout
      } ~
      path("crash") { ctx =>
        throw new RuntimeException("crash boom bang")
      } ~
      path("fail") {
        failWith(new RuntimeException("aaaahhh"))
      } ~
      path("riaktestsetup") {
        Test.setupTestData
        complete("SETUP!")
      } ~
      path("riaktestfetch" / Rest) { id =>
        complete(Test.read(id))
      }
    }
  }
}

感谢您的帮助!

【问题讨论】:

    标签: scala spray


    【解决方案1】:

    我个人将它用于大型 API:

    class ApiActor extends Actor with Api {
      override val actorRefFactory: ActorRefFactory = context
    
      def receive = runRoute(route)
    }
    
    /**
     * API endpoints
     *
     * Individual APIs are created in traits that are mixed here
     */
    trait Api extends ApiService
      with AccountApi with SessionApi
      with ContactsApi with GroupsApi
      with GroupMessagesApi with OneToOneMessagesApi
      with PresenceApi
      with EventsApi
      with IosApi
      with TelephonyApi
      with TestsApi {
      val route = {
        presenceApiRouting ~
        oneToOneMessagesApiRouting ~
        groupMessagesApiRouting ~
        eventsApiRouting ~
        accountApiRouting ~
        groupsApiRouting ~
        sessionApiRouting ~
        contactsApiRouting ~
        iosApiRouting ~
        telephonyApiRouting ~
        testsApiRouting
      }
    }
    

    我建议将最常见的路由放在首位,并在子路由中尽快使用pathPrefix,这样可以减少 Spray 为每个传入请求运行的测试数量。

    您会在下面找到一条我认为经过优化的路线:

      val groupsApiRouting = {
        pathPrefix("v3" / "groups") {
          pathEnd {
            get {
              traceName("GROUPS - Get joined groups list") { listJoinedGroups }
            } ~
            post {
              traceName("GROUPS - Create group") { createGroup }
            }
          } ~
          pathPrefix(LongNumber) { groupId =>
            pathEnd {
              get {
                traceName("GROUPS - Get by ID") { getGroupInformation(groupId) }
              } ~
              put {
                traceName("GROUPS - Edit by ID") { editGroup(groupId) }
              } ~
              delete {
                traceName("GROUPS - Delete by ID") { deleteGroup(groupId) }
              }
            } ~
            post {
              path("invitations" / LongNumber) { invitedUserId =>
                traceName("GROUPS - Invite user to group") { inviteUserToGroup(groupId, invitedUserId) }
              } ~
              path("invitations") {
                traceName("GROUPS - Invite multiple users") { inviteUsersToGroup(groupId) }
              }
            } ~
            pathPrefix("members") {
              pathEnd {
                get {
                  traceName("GROUPS - Get group members list") { listGroupMembers(groupId) }
                }
              } ~
              path("me") {
                post {
                  traceName("GROUPS - Join group") { joinGroup(groupId) }
                } ~
                delete {
                  traceName("GROUPS - Leave group") { leaveGroup(groupId) }
                }
              } ~
              delete {
                path(LongNumber) { removedUserId =>
                  traceName("GROUPS - Remove group member") { removeGroupMember(groupId, removedUserId) }
                }
              }
            } ~
            path("coverPhoto") {
              get {
                traceName("GROUPS - Request a new cover photo upload") { getGroupCoverPhotoUploadUrl(groupId) }
              } ~
              put {
                traceName("GROUPS - Confirm a cover photo upload") { confirmCoverPhotoUpload(groupId) }
              }
            } ~
            get {
              path("attachments" / "new") {
                traceName("GROUPS - Request attachment upload") { getGroupAttachmentUploadUrl(groupId) }
              }
            }
          }
        }
      }
    

    【讨论】:

    • inviteUserToGroup 返回什么类型? RequestContext => Unit?
    • @EdgeCaseBerg inviteUserToGroup 的类型为 (Long, Long) ⇒ Route :)
    • 嗨,Adrien,也许您会知道这种“连接”类型是否仍然正确?我在使用 spray 1.3.3 时遇到了 stackoverflow.com/questions/35614708/… 的问题。
    • @AdrienAubel - 对于您的每个人,我假设,interface ...Apis,例如:IosApi,您是否有实现,即 IosApiImpl 包含实际执行?您如何将它们混合在一起 - 以使实现与接口分开?
    • 也 - 每个Api,例如AccountApi扩展 HttpService?
    【解决方案2】:

    您可以使用 ~ 组合器组合来自不同“控制器”的路由。

    class AccountServiceActor extends Actor with HttpService {
    
      def actorRefFactory = context
    
      def receive = handleTimeouts orElse runRoute(
      new AccountService1.accountService1 ~  new AccountService2.accountService2)
    
      def handleTimeouts: Receive = {
        case Timeout(x: HttpRequest) =>
          sender ! HttpResponse(StatusCodes.InternalServerError, "Request timed out.")
      }
    }
    
    
    
    class AccountService1 extends HttpService {
    
      val accountService1 = {
        get {
          path("") {
            respondWithMediaType(`text/html`) { // XML is marshalled to `text/xml` by default, so we simply override here
              complete(index)
            }
          }
        }
    }
    
    
    class AccountService2 extends HttpService {
    
      val accountService2 = {
        get {
          path("someotherpath") {
            respondWithMediaType(`text/html`) { // XML is marshalled to `text/xml` by default, so we simply override here
              complete(index)
            }
          }
        }
    }
    

    【讨论】:

    • 看起来可以解决问题。我想知道我是否可以编写某种可以自动组合它们的隐式,而不是手动编写 service1 ~ service2 ~ service3。谢谢!
    • 嗯取消选择它,因为它看起来会产生某种继承问题。 type arguments [com.threetierlogic.AccountServ ice.AccountServiceActor] do not conform to method apply's type parameter bounds [T <: akka.actor.Actor]
    • 好的,case class Base(actorRefFactory: ActorRefFactory) extends HttpService { 取得了一些进展现在问题是 HTTP 请求失败,原因如下:Cannot dispatch HttpResponse as response (part) for GET request to '/ ' since current response state is 'Completed' but should be 'Uncompleted'
    • 由于某种原因,我通过扩展 HttpService 创建的类无法编译,他们说:需要抽象,因为类型 => akka.actor.ActorRefFactory 的 trait HttpService 中的方法 actorRefFactory 未定义类MyRouteRoute 扩展了 HttpService{ ^
    【解决方案3】:

    我从上面的代码 sn-p 中尝试了这种方式,基本格式和作品。

    import akka.actor.ActorSystem
    import akka.actor.Props
    import spray.can.Http
    import akka.io.IO
    import akka.actor.ActorRefFactory
    import spray.routing.HttpService
    import akka.actor.Actor
    
    
    /**
     * API endpoints
     *
     * Individual APIs are created in traits that are mixed here
     */
    
    trait Api extends ApiService
    with UserAccountsService
    {
      val route ={
        apiServiceRouting ~
        accountsServiceRouting
      }
    
    }
    
    trait ApiService extends HttpService{
      val apiServiceRouting={
        get{
          path("ping") {
           get {
            complete {
              <h1>pong</h1>
            }
           }
          }
        }
      }
    }
    
    trait UserAccountsService extends HttpService{
      val accountsServiceRouting={
         path("getAdmin") {
          get {
            complete {
              <h1>AdminUserName</h1>
            }
          }
        }
      }
    }
    class ApiActor extends Actor with Api {
      override val actorRefFactory: ActorRefFactory = context
    
      def receive = runRoute(this.route)
    }
    
    
    object MainTest extends App {
    
      // we need an ActorSystem to host our application in
      implicit val system = ActorSystem("UserInformaitonHTTPServer")
    
      // the handler actor replies to incoming HttpRequests
      val handler = system.actorOf(Props[ApiActor], name = "handler")
    
      // starting the server
      IO(Http) ! Http.Bind(handler, interface = "localhost", port = 8080)
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-12-12
      • 2015-07-04
      • 1970-01-01
      • 1970-01-01
      • 2019-12-12
      • 2021-09-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多