2014-12-18 4 views
2

Я занят переносом одного из наших приложений Spring/Groovy на Spray/Scala. Я довольно новичок в Spray, так что простите меня, если это вопрос для начинающих.Перехватчики/фильтры в спрее

Целью является эмулировать наш регистрационный перехватчик, который регистрирует различные данные для каждого запроса/ответа. В этой части кода существует довольно много логики, поэтому это не простая строка журнала. Кроме того, я хотел бы обернуть ВСЕ запросы, которые входят в эту логику.

Существующие Groovy/Spring Interceptor:

boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler { 
    //do some logging logic 
} 


void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { 
    //do some more logging logic 
} 

Мой Scala актер выглядит так

class BootServiceActor extends Actor with ViewingController with LazyLogging with ViewingWire { 

def actorRefFactory = context 

implicit val ctx = context.dispatcher 

def receive = runRoute(route)(exceptionHandler, RejectionHandler.Default, context, 
    RoutingSettings.default, LoggingContext.fromActorRefFactory) 
} 

ответ

5

Одна из больших пунктов продажи Spray/Scala является то, что вы можете избежать "магии", невидимый такие вещи, как перехватчики. Вы достигаете одних и тех же вещей, используя типы и/или директивы, но все, что вы делаете, становится видимым в коде и рефакторинге благодаря системе типов. Например. вы можете использовать scalaz Writer для накопления сообщений журнала, связанных с конкретным запросом, а затем определить «метамаршаллер», который предоставил Marshaller[Writer[MyLogStructure, A]] для любого A, который имел существующий маршаллер, и написал все журналы для этого конкретного запроса в правильной структуре , Я рад углубиться в этот подход, если хотите.

«Предварительно обработанная» часть, вероятно, лучше всего обрабатывается простым Directive; так как спрей реагирует и асинхронно, на самом деле нет концепции сопряженной обработки «до и после». Скорее, вы делаете что-то для запроса и передаете его на следующий шаг обработки, который в конечном итоге отправит ответ. Если вам нужно передать какой-то «контекст» через пре-дескриптор в пост-дескриптор, снова это, вероятно, лучше всего сделать с «типом контекста» (я стараюсь избегать говорить страшное слово m), например State.

+0

Спасибо за ответ - я пытаюсь сделать подход к директиве. Существует DubuggingDirective, который поставляется с Spray, который выводит запрос/ответ, и я пытаюсь скопировать/вставить этот код. У вас есть информация о передаче «типа контекста»/состояния вокруг? Как правило, я мог бы использовать объект threadlocal/request, но я не уверен, что это возможно с помощью Spray? –

+0

Threadlocal не работает, ответ может быть отправлен другим потоком. Вы можете сделать так, как это делает директива для отладки, «mapRequestContext» и внутри нее «withRouteResponseMapped», поэтому данные фактически находятся в закрытии, которое вызывается, когда ответ в итоге произошел, но в этом случае у вас не будет доступа к маршрутам , Если вы хотите получить доступ к нему внутри, вы можете передать его явно (т. Е. Предоставить его из своей директивы); вы можете объявить его неявным и передать его в функции как неявный параметр, чтобы упростить работу. – lmm

+0

Но то, на что я ссылался, является функциональным способом потоковой передачи изменчивого состояния посредством вычисления, [монады государства пламени] (http://eed3si9n.com/learning-scalaz/State.html). Вы можете составлять вместе вычисления, которые работают с состоянием, используя 'for' /' yield' и другие инструменты scalaz (например,'traverse') прекрасно работают на них, а затем вы принимаете директиву' State [MyInfo, Route] 'или некоторые из них. Но это может быть излишним для простого случая. – lmm

1

Решение I осуществляется на основе ввода LMM в

я в конечном итоге делает пользовательские директивы, то вдоль этих линий:

trait LoggingDirectives { 

    val logger = LoggerFactory.getLogger(classOf[LoggingDirective]) 

    import spray.routing.directives.BasicDirectives._ 

    def logResponseInfo(user : String): Directive0 = 
    mapRouteResponse { response ⇒ 
     //... 
     logger.info("RESPONSE >> "+user+":"+response) 
     response 
    } 

    def logRequestInfo: Directive0 = 
    mapRequest { request ⇒ 
     //... 
     logger.info("REQUEST << " + request); 
     request 
    } 
} 

Я тогда обернутый вокруг моего маршрута:

val route = logRequestInfo { 
    accountAuthenicationValidator { profileId:String => 
    logResponseInfo(profileId) { 
     controllerRoute(profileId) 
    } 
    } 
} 

где accountAuthenicationValidator - другая настраиваемая директива, а controllerRoute - основной маршрут, который обрабатывает ВСЕ paths и является (String => Route)

+1

Выглядит хорошо. Обратите внимание: если вы хотите применить кучу директив в одном и том же месте, вы можете использовать '&' для объединения директив в один, а не с '{}' на каждом уровне. – lmm

Смежные вопросы