2013-02-01 15 views
18

Non ho trovato un solido esempio o struttura per suddividere le rotte Spray.io in più file. Sto scoprendo che l'attuale struttura dei miei percorsi diventerà molto ingombrante e sarebbe bello astrarli in "Controller" diversi per un'API REST molto semplice.Gli itinerari Spray.io possono essere suddivisi in più "Controller"?

Docs non sembrano aiutare più di tanto: http://spray.io/documentation/spray-routing/key-concepts/directives/#directives

Ecco quello che ho finora:

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)) 
     } 
    } 
    } 
} 

Grazie per un aiuto su questo!

risposta

14

È possibile combinare percorsi da diversi "Controller" utilizzando ~ combinatore.

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) 
     } 
     } 
    } 
} 
+0

Sembra che questo faccia il trucco. Mi chiedo se riesco a comporre una sorta di implicito che può combinarli automaticamente invece di scrivere manualmente servizio1 ~ servizio2 ~ servizio3. Grazie! – crockpotveggies

+0

Hmmm l'ha deselezionato poiché sembra che generi una sorta di problema dell'eredità. 'type arguments [com.threetierlogic.AccountServ ice.AccountServiceActor] non conforme ai limiti del parametro di tipo method apply [T <: akka.actor.Actor]' – crockpotveggies

+0

Ok fatto alcuni progressi con 'case class Base (actorRefFactory: ActorRefFactory) estende HttpService {'Ora il problema è che le richieste HTTP falliscono a causa di quanto segue:' Impossibile inviare HttpResponse come risposta (parte) per la richiesta GET a '/ ' poiché lo stato di risposta corrente è 'Completato' ma dovrebbe essere 'Non completato' – crockpotveggies

33

Io personalmente uso questo per grandi 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 
    } 
} 

lo consiglio mettere al primo posto i percorsi più comuni, e utilizzare pathPrefix più presto possibile in sotto-percorsi, in modo da ridurre il numero di test eseguiti da Spray per ogni richiesta in entrata.

Troverete qui di seguito un percorso che credo è ottimizzato:

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) } 
      } 
     } 
     } 
    } 
    } 
+0

Cosa type fa 'inviteUserToGroup' r eturn? 'RequestContext => Unit'? – EdgeCaseBerg

+0

@EdgeCaseBerg 'inviteUserToGroup' è di tipo' (Long, Long) ⇒ Route' :) –

+0

Ciao Adrien, forse saprai se quel tipo di "concatenazione" è ancora corretto? Incontro in quel problema http://stackoverflow.com/questions/35614708/unexpected-behaviour-on-spray-can-operator-with-http-two-methods-on-the-same-p usando spray 1.3.3 . –

1

ho provato in questo modo dal frammento di codice di cui sopra, formato di base e lavora.

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) 

} 
Problemi correlati