Prototyping end to end workflow and UpstreamFilter and Tests
This commit is contained in:
parent
d414806664
commit
25496ef520
|
@ -0,0 +1,29 @@
|
|||
package com.lookout.borderpatrol
|
||||
|
||||
import com.lookout.borderpatrol.BorderPatrolApp.{Response, RoutedRequest}
|
||||
import com.twitter.finagle.{Filter, Service}
|
||||
import com.twitter.finagle.http.{Http, Request => FinagleRequest, Response => FinagleResponse}
|
||||
|
||||
/**
|
||||
* Created by wkimeria on 12/11/14.
|
||||
*/
|
||||
|
||||
trait AuthFilter extends Filter[RoutedRequest, FinagleResponse, RoutedRequest, FinagleResponse]
|
||||
|
||||
class AuthFilterSimple extends AuthFilter {
|
||||
def apply(request: RoutedRequest, service: Service[RoutedRequest, FinagleResponse]) = {
|
||||
println("----------------------------- AuthFilterSimple ------------------------------>")
|
||||
val r = service(request)
|
||||
println("<----------------------------- AuthFilterSimple ------------------------------")
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
class AuthFilterCond(uService: Service[RoutedRequest, FinagleResponse]) extends AuthFilter {
|
||||
def apply(request: RoutedRequest, service: Service[RoutedRequest, FinagleResponse]) = {
|
||||
println("----------------------------- AuthFilterCond ------------------------------>")
|
||||
val r = uService(request)
|
||||
println("<----------------------------- AuthFilterCond ------------------------------")
|
||||
r
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.lookout.borderpatrol
|
||||
|
||||
import com.lookout.borderpatrol.BorderPatrolApp.{Response}
|
||||
import com.twitter.finagle.Service
|
||||
import com.twitter.util.Future
|
||||
import org.jboss.netty.handler.codec.http._
|
||||
import com.twitter.finagle.http.{Http, Request => FinagleRequest, Response => FinagleResponse}
|
||||
|
||||
/**
|
||||
* Created by wkimeria on 12/11/14.
|
||||
*/
|
||||
class AuthService extends Service[HttpRequest, FinagleResponse] {
|
||||
def apply(request: HttpRequest) = {
|
||||
println("----------------------------- AuthService------------------------------>")
|
||||
val result: FinagleResponse = new Response(new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK))
|
||||
val r = Future.value(result)
|
||||
println("<----------------------------- AuthService------------------------------")
|
||||
r
|
||||
}
|
||||
}
|
|
@ -1,59 +1,54 @@
|
|||
package com.lookout.borderpatrol
|
||||
|
||||
import com.lookout.borderpatrol.routing._
|
||||
import com.lookout.borderpatrol.auth._
|
||||
import com.lookout.borderpatrol.sessions._
|
||||
import com.twitter.finagle.{Filter, _}
|
||||
import com.twitter.finagle.builder.ClientBuilder
|
||||
import java.net.{InetAddress, InetSocketAddress}
|
||||
|
||||
import com.twitter.finagle.builder.{Server, ServerBuilder}
|
||||
import com.twitter.server.TwitterServer
|
||||
import com.twitter.util._
|
||||
import org.jboss.netty.handler.codec.http._
|
||||
import com.twitter.finagle.http.{Http, Request => FinagleRequest, Response => FinagleResponse}
|
||||
|
||||
object BorderPatrolApp extends TwitterServer {
|
||||
|
||||
val sessionStore = new SessionStore
|
||||
|
||||
val sessionIDFilter = new SessionIDFilter(sessionStore)
|
||||
val routingFilter = new RoutingFilter
|
||||
val sessionStoreFilter = new SessionStoreFilter(sessionStore)
|
||||
val authFilter = new AuthFilter
|
||||
val authService = new AuthService
|
||||
|
||||
val orchestratorFilter: Filter[HttpRequest, HttpResponse, HttpRequest, HttpResponse] =
|
||||
sessionIDFilter andThen routingFilter andThen sessionStoreFilter andThen authFilter
|
||||
|
||||
val service = new Service[HttpRequest, HttpResponse] {
|
||||
def apply(request: HttpRequest) = {
|
||||
log.info("Service: Received a request at " + Time.now + ". Calling upstream =" + request.getUri())
|
||||
|
||||
val newUri = request.getUri.split("/").toSeq match {
|
||||
case Seq() => "/"
|
||||
case x => x.tail.tail.mkString("/", "/", "")
|
||||
}
|
||||
|
||||
log.info("new uri: " + newUri)
|
||||
request.setUri(newUri)
|
||||
//call upstream
|
||||
val client: Service[HttpRequest, HttpResponse] = ClientBuilder()
|
||||
.codec(com.twitter.finagle.http.Http())
|
||||
.hosts(request.getHeader("HOST")) // If >1 host, client does simple load-balancing
|
||||
.hostConnectionLimit(1)
|
||||
.build()
|
||||
|
||||
val clientWithTokenFilter = new TokenFilter(sessionStore) andThen client
|
||||
|
||||
clientWithTokenFilter(request)
|
||||
//TODO: Flesh this out
|
||||
class Session {
|
||||
def token(name: String): Option[String] = {
|
||||
val tokens = Map("flexd" -> "DEADBEEF", "mtp" -> "LIVEKALE")
|
||||
tokens.get(name)
|
||||
}
|
||||
}
|
||||
|
||||
val orchestratorService: Service[HttpRequest, HttpResponse] =
|
||||
orchestratorFilter andThen service
|
||||
//A Request with Routing and Session Information
|
||||
class RoutedRequest(val httpRequest: HttpRequest, val session: Session)
|
||||
extends FinagleRequest {
|
||||
override val remoteSocketAddress: InetSocketAddress = new InetSocketAddress(InetAddress.getLoopbackAddress, 0) //TODO: This is wrong
|
||||
}
|
||||
|
||||
//Unsuccessful Response
|
||||
case class NeedsAuthResponse (httpResponse: HttpResponse) extends FinagleResponse //with BorderPatrolResponse
|
||||
|
||||
//Successful response
|
||||
case class Response (httpResponse: HttpResponse) extends FinagleResponse //with BorderPatrolResponse
|
||||
|
||||
|
||||
val sessionFilter = new SessionFilter
|
||||
val upstreamService = new UpstreamService
|
||||
val upstreamFilter = new UpstreamFilter(None, None)
|
||||
val routingFilter = new RoutingFilter
|
||||
val loginFilter = new LoginFilter
|
||||
val loginService = new LoginService
|
||||
var authService = new AuthService
|
||||
val authFilter = new AuthFilterCond(authService)
|
||||
val upstreamCombination = upstreamFilter andThen upstreamService
|
||||
val loginCombination = loginFilter andThen authFilter
|
||||
val upStreamFilterWithLeft = new UpstreamFilter(Some(loginCombination andThen upstreamFilter andThen upstreamService), Some(upstreamService))
|
||||
|
||||
val orchestratorService = routingFilter andThen sessionFilter andThen upStreamFilterWithLeft andThen upstreamService
|
||||
|
||||
def main() {
|
||||
val server = Http.serve(":8080", orchestratorService)
|
||||
onExit {
|
||||
server.close()
|
||||
}
|
||||
Await.ready(httpServer)
|
||||
val server: Server = ServerBuilder()
|
||||
.codec(Http())
|
||||
.bindTo(new InetSocketAddress(8080))
|
||||
.name("BorderPatrol")
|
||||
.build(orchestratorService)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.lookout.borderpatrol
|
||||
|
||||
import com.lookout.borderpatrol.BorderPatrolApp.{Response, RoutedRequest}
|
||||
import com.twitter.finagle.{Service, SimpleFilter}
|
||||
import com.twitter.finagle.http.{Http, Request => FinagleRequest, Response => FinagleResponse}
|
||||
/**
|
||||
* Created by wkimeria on 12/11/14.
|
||||
*/
|
||||
class LoginFilter
|
||||
extends SimpleFilter[RoutedRequest, FinagleResponse] {
|
||||
def apply(request: RoutedRequest, service: Service[RoutedRequest, FinagleResponse]) = {
|
||||
println("----------------------------- LoginFilter------------------------------>")
|
||||
val r = service(request)
|
||||
println("<----------------------------- LoginFilter------------------------------")
|
||||
r
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.lookout.borderpatrol
|
||||
|
||||
import com.lookout.borderpatrol.BorderPatrolApp.{Response, RoutedRequest}
|
||||
import com.twitter.finagle.Service
|
||||
import com.twitter.util.Future
|
||||
import org.jboss.netty.handler.codec.http._
|
||||
import com.twitter.finagle.http.{Http, Request => FinagleRequest, Response => FinagleResponse}
|
||||
|
||||
|
||||
/**
|
||||
* Created by wkimeria on 12/11/14.
|
||||
*/
|
||||
class LoginService extends Service[RoutedRequest, Response] {
|
||||
def apply(request: RoutedRequest) = {
|
||||
println("----------------------------- LoginService------------------------------>")
|
||||
val r = Future.value(new Response(new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK)))
|
||||
println("<----------------------------- LoginService------------------------------")
|
||||
r
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package com.lookout.borderpatrol
|
||||
|
||||
import com.lookout.borderpatrol.BorderPatrolApp._
|
||||
import com.twitter.finagle.{Filter, Service}
|
||||
import com.twitter.util.{Future}
|
||||
import org.jboss.netty.handler.codec.http._
|
||||
import com.twitter.finagle.http.{Http, Request => FinagleRequest, Response => FinagleResponse}
|
||||
|
||||
class RoutingFilter
|
||||
extends Filter[HttpRequest, FinagleResponse, RoutedRequest, FinagleResponse] {
|
||||
def apply(request: HttpRequest, service: Service[RoutedRequest, FinagleResponse]) = {
|
||||
println(request.getUri + "-------- RoutingFilter ------------------------------>")
|
||||
val r =
|
||||
if(request.getUri() == "/notfound" || request.getUri() == "/favicon.ico"){
|
||||
val result: FinagleResponse = new Response(new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND))
|
||||
Future.value(result)
|
||||
}else{
|
||||
val rRequest = new RoutedRequest(request, new Session())
|
||||
service(rRequest)
|
||||
}
|
||||
println("<----------------------------- RoutingFilter ------------------------------")
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.lookout.borderpatrol
|
||||
|
||||
import com.lookout.borderpatrol.BorderPatrolApp.{Response, RoutedRequest}
|
||||
import com.twitter.finagle.{Service, SimpleFilter}
|
||||
import com.twitter.finagle.http.{Http, Request => FinagleRequest, Response => FinagleResponse}
|
||||
|
||||
/**
|
||||
* Created by wkimeria on 12/10/14.
|
||||
*/
|
||||
class SessionFilter
|
||||
extends SimpleFilter[RoutedRequest, FinagleResponse] {
|
||||
def apply(request: RoutedRequest, service: Service[RoutedRequest, FinagleResponse]) = {
|
||||
println("----------------------------- SessionFilter ------------------------------>")
|
||||
val r = service(request)
|
||||
println("<----------------------------- SessionFilter ------------------------------")
|
||||
r
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package com.lookout.borderpatrol
|
||||
|
||||
/**
|
||||
* The UpstreamFilter is responsible for calling the UpstreamsService and returning non 401 responses
|
||||
* back up the chain. For 401 responses, it calls an optional Service in line to get an Authorization
|
||||
* token for the upstream service that returned the 401. If that succeeeds, it makes a call back to
|
||||
* the service once more with the Authorization Token
|
||||
*/
|
||||
|
||||
import com.lookout.borderpatrol.BorderPatrolApp._
|
||||
import com.twitter.finagle.{Filter, SimpleFilter, Service}
|
||||
import com.twitter.util.{Await}
|
||||
import com.twitter.finagle.http.{Http, Request => FinagleRequest, Response => FinagleResponse}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param next Service to call if we get a 401 from the Upstream Service
|
||||
* @param uService Service to call instead of calling the service passed in the apply. We do this to accommodate the
|
||||
* need to have another service called after the Upstream Service.
|
||||
*/
|
||||
class UpstreamFilter(next: Option[Service[RoutedRequest, FinagleResponse]], uService: Option[Service[RoutedRequest, FinagleResponse]])
|
||||
extends SimpleFilter[RoutedRequest, FinagleResponse] {
|
||||
def apply(request: RoutedRequest, service: Service[RoutedRequest, FinagleResponse]) = {
|
||||
println("----------------------------- UpstreamFilter ------------------------------>")
|
||||
val responseFuture = uService match {
|
||||
case None => service(request)
|
||||
case Some(t) => {
|
||||
val authorizedRequest = addRequiredHeaders(addAuthHeader(request, None))
|
||||
t(authorizedRequest)
|
||||
}
|
||||
}
|
||||
val response = Await.result(responseFuture)
|
||||
val r = response match {
|
||||
case NeedsAuthResponse(_) => {
|
||||
next match {
|
||||
case None => responseFuture
|
||||
case Some(s) => {
|
||||
val respFuture = s(request)
|
||||
val response = Await.result(respFuture)
|
||||
val authorizedRequest = addRequiredHeaders(addAuthHeader(request, Option(response)))
|
||||
val responseFuture = uService match {
|
||||
case None => service(request)
|
||||
case Some(t) => {
|
||||
t(authorizedRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
responseFuture
|
||||
}
|
||||
case Response(_) => {
|
||||
responseFuture
|
||||
}
|
||||
}
|
||||
println("<----------------------------- UpstreamFilter ------------------------------")
|
||||
r
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Session tokens with tokens returned in response
|
||||
* @param rq RoutedRequest
|
||||
* @param response Response
|
||||
* @return New RoutedRequest
|
||||
*/
|
||||
def updateSession(rq: RoutedRequest, response: FinagleResponse):RoutedRequest = {
|
||||
//TODO: Get tokens from Response
|
||||
val request = new RoutedRequest(rq.httpRequest, rq.session)
|
||||
request
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Authorization Header to the RoutedRequest.
|
||||
* @param rq RoutedRequest
|
||||
* @param responseOpt Optional Response
|
||||
* @return New RoutedRequest
|
||||
*/
|
||||
def addAuthHeader(rq: RoutedRequest, responseOpt: Option[FinagleResponse]): RoutedRequest = {
|
||||
val request = responseOpt match {
|
||||
case None => {
|
||||
val request = rq.session.token("flexd") match {
|
||||
case None => new RoutedRequest(rq.httpRequest, rq.session)
|
||||
case Some(t) => {
|
||||
val r = new RoutedRequest(rq.httpRequest, rq.session)
|
||||
r.headers().remove("Auth-Token")
|
||||
r.headers().add("Auth-Token", t)
|
||||
r
|
||||
}
|
||||
}
|
||||
request
|
||||
}
|
||||
case Some(resp) =>{
|
||||
val request = updateSession(rq, resp)
|
||||
request.headers().remove("Auth-Token")
|
||||
val token = rq.session.token("flexd") match {
|
||||
case None => Nil
|
||||
case Some(t) => t
|
||||
}
|
||||
request.headers().add("Auth-Token", token)
|
||||
request
|
||||
}
|
||||
}
|
||||
request
|
||||
}
|
||||
|
||||
/**
|
||||
* Add required headers in order to call upstream service
|
||||
* @param rq RoutedRequest
|
||||
* @return New RoutedRequest
|
||||
*/
|
||||
def addRequiredHeaders(rq:RoutedRequest): RoutedRequest = {
|
||||
//TODO: Figure out a better way of setting headers
|
||||
val request = new RoutedRequest(rq.httpRequest, rq.session)
|
||||
//val request = RequestBuilder(rq).withHeaders(("Via","Borderpatrol"))
|
||||
|
||||
//request.headers().clear()
|
||||
//TODO: Figure out what headers we need to pass and whether we need to clear all headers beforehand
|
||||
request.headers().remove("Via")
|
||||
request.headers().add("Via", "Borderpatrol")
|
||||
request
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package com.lookout.borderpatrol
|
||||
|
||||
import com.lookout.borderpatrol.BorderPatrolApp.{Response, NeedsAuthResponse}
|
||||
import com.twitter.finagle.Service
|
||||
import com.twitter.util.Future
|
||||
import org.jboss.netty.handler.codec.http._
|
||||
import com.twitter.finagle.http.{Http, Request => FinagleRequest, Response => FinagleResponse}
|
||||
|
||||
/**
|
||||
* Created by wkimeria on 12/10/14.
|
||||
*/
|
||||
class UpstreamService extends Service[HttpRequest, FinagleResponse] {
|
||||
def apply(request: HttpRequest) = {
|
||||
println("----------------------------- UpstreamService ------------------------------>")
|
||||
var it = request.headers().entries().iterator()
|
||||
while(it.hasNext){
|
||||
println(it.next().toString)
|
||||
}
|
||||
println("HEADERS --------> " + request.headers().toString)
|
||||
val r =
|
||||
if(request.getUri() == "/good"){
|
||||
val result: Response = new Response(new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK))
|
||||
Future.value(result)
|
||||
}else{
|
||||
val result: FinagleResponse = new NeedsAuthResponse(new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.UNAUTHORIZED))
|
||||
Future.value(result)
|
||||
}
|
||||
println("<----------------------------- UpstreamService ------------------------------")
|
||||
r
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package com.lookout.borderpatrol.auth
|
||||
|
||||
import org.jboss.netty.buffer._
|
||||
import org.jboss.netty.handler.codec.http.{HttpRequest, HttpResponse, HttpResponseStatus}
|
||||
import com.twitter.finagle.{SimpleFilter, Service}
|
||||
import org.jboss.netty.buffer.ChannelBuffers.copiedBuffer
|
||||
import org.jboss.netty.util.CharsetUtil.UTF_8
|
||||
|
||||
/**
|
||||
* Transforms an upstream responses 401 UNAUTHORIZED into a 301 Redirect to Checkpoint
|
||||
*
|
||||
* Created by akuhnhausen on 6/11/14.
|
||||
*/
|
||||
class AuthFilter extends SimpleFilter[HttpRequest, HttpResponse] {
|
||||
def apply(request: HttpRequest, service: Service[HttpRequest, HttpResponse]) = {
|
||||
|
||||
// When the upstream have us a 401, transform it to a 302 and return the auth URL.
|
||||
service(request).onSuccess(resp => {
|
||||
if(resp.getStatus == HttpResponseStatus.UNAUTHORIZED) {
|
||||
resp.setStatus(HttpResponseStatus.MOVED_PERMANENTLY)
|
||||
// TODO: this URL should come from the RoutingService
|
||||
resp.setContent(copiedBuffer("", UTF_8))
|
||||
resp.setHeader("Content-Length", "0")
|
||||
resp.setHeader("Location", "/a")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package com.lookout.borderpatrol.auth
|
||||
|
||||
import com.twitter.finagle._
|
||||
import com.twitter.util.Future
|
||||
import org.jboss.netty.handler.codec.http.{HttpResponseStatus, DefaultHttpResponse, HttpRequest, HttpResponse}
|
||||
|
||||
/**
|
||||
* Invokes the Authentication service to turn a master session token into a token for a specific service.
|
||||
*
|
||||
* Created by akuhnhausen on 6/11/14.
|
||||
*/
|
||||
class AuthService extends Service[HttpRequest, HttpResponse] {
|
||||
def apply(request: HttpRequest) = {
|
||||
val response = new DefaultHttpResponse(request.getProtocolVersion, HttpResponseStatus.OK)
|
||||
Future.value(response)
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package com.lookout.borderpatrol.auth
|
||||
|
||||
import com.lookout.borderpatrol.sessions.SessionStore
|
||||
import com.twitter.finagle.{Service, SimpleFilter}
|
||||
import org.jboss.netty.handler.codec.http.{HttpResponseStatus, HttpMethod, HttpRequest, HttpResponse}
|
||||
|
||||
/**
|
||||
* Created by svij on 6/12/14.
|
||||
*/
|
||||
class TokenFilter(sessionStore: SessionStore) extends SimpleFilter[HttpRequest, HttpResponse]{
|
||||
def apply(request: HttpRequest, service: Service[HttpRequest, HttpResponse]) = {
|
||||
System.out.println("Inside token filter. Uri="+ request.getUri())
|
||||
//TODO: append the service token if exists
|
||||
//get the master token
|
||||
val sessionId = request.getHeader("BORDER_PATROL_SESSION_ID")
|
||||
val session = sessionStore.session(sessionId)
|
||||
session.masterToken map {
|
||||
request.addHeader("master_token", _)
|
||||
}
|
||||
var f_response = service(request)
|
||||
if (request.getUri().contains("auth") && request.getMethod() == HttpMethod.POST) {
|
||||
System.out.println("Inside token filter: extracting master token")
|
||||
// reassign the response future as we are running this function onSuccess
|
||||
f_response = f_response onSuccess { resp: HttpResponse =>
|
||||
if (resp.getStatus() == HttpResponseStatus.OK) {
|
||||
//take the master token and store
|
||||
val master_token = resp.getHeader("master_token")
|
||||
System.out.println("Inside token filter: extracted the master token="+master_token + "; session-id="+sessionId)
|
||||
session.masterToken = master_token
|
||||
resp.removeHeader("master_token")
|
||||
resp.setStatus(HttpResponseStatus.MOVED_PERMANENTLY)
|
||||
resp.setHeader("Content-Length", "0")
|
||||
resp.setHeader("Location", "/b/home")
|
||||
}
|
||||
}
|
||||
}
|
||||
f_response
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package com.lookout.borderpatrol
|
||||
|
||||
/**
|
||||
* Created by akuhnhausen on 6/11/14.
|
||||
*/
|
||||
package object auth {
|
||||
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
package com.lookout.borderpatrol.routing
|
||||
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
import com.twitter.finagle.http.Request
|
||||
import com.twitter.finagle.http.Response
|
||||
import com.twitter.finagle.Service
|
||||
import com.twitter.finagle.SimpleFilter
|
||||
import com.twitter.finagle.Service
|
||||
import com.twitter.finagle.http.{Request, Response}
|
||||
import com.twitter.finagle.SimpleFilter
|
||||
import scala.collection.immutable.HashMap
|
||||
import org.jboss.netty.handler.codec.http._
|
||||
import scala.util.matching.Regex
|
||||
|
||||
class RoutingFilter
|
||||
extends SimpleFilter[HttpRequest, HttpResponse] {
|
||||
|
||||
// Ideally the route would map a string to a new object with
|
||||
// details like service name and host information etc. For demo purposes we are going for
|
||||
// a string to string mapping of service names. "b" => "smb", "c" => "flexd" but we are hardcoding it
|
||||
// to SERVICE_NAME for simplicity
|
||||
//
|
||||
val routes = HashMap[String, HashMap[String, String]](
|
||||
"/b"->HashMap[String,String]("service_name"->"smb","host"->"localhost","port"->"9292"),
|
||||
"/a"->HashMap[String,String]("service_name"->"checkpoint","host"->"localhost","port"->"4567"),
|
||||
"default"->HashMap[String,String]("service_name"->"flexd","host"->"localhost","port"->"8080"))
|
||||
|
||||
val pattern = new Regex("^/([^/]+)")
|
||||
|
||||
def apply(request: HttpRequest, service: Service[HttpRequest, HttpResponse]) = {
|
||||
// Ideally we would have a RoutingService looking up possible routes and not
|
||||
// have a static lookup table.
|
||||
val req = injectHeaderAndModifyUri(request)
|
||||
service(req)
|
||||
}
|
||||
|
||||
|
||||
def extractServiceFromUri(uri: String): Serializable = {
|
||||
val result = pattern.findFirstIn(uri).getOrElse("default")
|
||||
val serviceUri = {
|
||||
routes.getOrElse(result , "")
|
||||
}
|
||||
serviceUri
|
||||
}
|
||||
|
||||
def injectHeaderAndModifyUri(request: HttpRequest): HttpRequest = {
|
||||
println("INITIAL HEADERS : "+ request.getHeaders)
|
||||
val service = extractServiceFromUri(request.getUri).asInstanceOf[HashMap[String,String]]
|
||||
request.addHeader("SERVICE_NAME", service.get("service_name").getOrElse("SERVICE_NAME"))
|
||||
request.addHeader("ORIGINAL_HOST", request.getHeader("HOST"))
|
||||
request.setHeader("HOST", service.get("host").getOrElse("localhost")+":"+service.get("port").getOrElse("8080"))
|
||||
request.setUri(request.getUri)
|
||||
println("HEADERS AFTER REROUTE : "+ request.getHeaders)
|
||||
request
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package com.lookout.borderpatrol.routing
|
||||
|
||||
/**
|
||||
* Created by akuhnhausen on 6/11/14.
|
||||
*/
|
||||
class RoutingService {
|
||||
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package com.lookout.borderpatrol
|
||||
|
||||
/**
|
||||
* Created by akuhnhausen on 6/11/14.
|
||||
*/
|
||||
package object routing {
|
||||
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
package com.lookout.borderpatrol.sessions
|
||||
|
||||
import com.twitter.finagle.SimpleFilter
|
||||
import com.twitter.finagle.Service
|
||||
import org.jboss.netty.handler.codec.http._
|
||||
import java.util.UUID.randomUUID
|
||||
import org.jboss.netty.handler.codec.http.DefaultCookie
|
||||
import scala.collection.JavaConversions._
|
||||
import com.twitter.logging.Logging
|
||||
import com.twitter.util.Try
|
||||
|
||||
class SessionIDFilter(sessionStore: SessionStore) extends SimpleFilter[HttpRequest, HttpResponse] {
|
||||
|
||||
val cookieKey = "border_session"
|
||||
val maxAge = 24 * 60 * 60
|
||||
|
||||
def apply(request: HttpRequest, service: Service[HttpRequest, HttpResponse]) = {
|
||||
val id = getIDFromRequest(request) getOrElse generateNewID
|
||||
request.setHeader("BORDER_PATROL_SESSION_ID", id)
|
||||
System.out.println("Session ID: %s".format(id))
|
||||
|
||||
service(request) onSuccess (setCookie(_, id))
|
||||
}
|
||||
|
||||
def generateNewID: String = {
|
||||
val id = randomUUID.toString
|
||||
sessionStore.addSession(id)
|
||||
id
|
||||
}
|
||||
|
||||
def getIDFromRequest(request: HttpRequest): Option[String] = {
|
||||
Option(request.getHeader(HttpHeaders.Names.COOKIE)) flatMap {
|
||||
getIDFromValidCookie(_)
|
||||
}
|
||||
}
|
||||
|
||||
def getIDFromValidCookie(cookieHeader: String): Option[String] = {
|
||||
val cookies = new CookieDecoder().decode(cookieHeader).toSet
|
||||
val cookie: Option[Cookie] = cookies find {
|
||||
validCookie(_)
|
||||
}
|
||||
cookie map {
|
||||
_.getValue
|
||||
}
|
||||
}
|
||||
|
||||
def validCookie(cookie: Cookie): Boolean = {
|
||||
cookie.getName == cookieKey
|
||||
}
|
||||
|
||||
def setCookie(response: HttpResponse, sessionId: String): Unit = {
|
||||
val cookie = new DefaultCookie(cookieKey, sessionId)
|
||||
cookie.setMaxAge(maxAge)
|
||||
cookie.setDomain("lookout.com")
|
||||
cookie.setSecure(true)
|
||||
response.setHeader(HttpHeaders.Names.SET_COOKIE, cookie)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package com.lookout.borderpatrol.sessions
|
||||
|
||||
import scala.collection.mutable.Map
|
||||
|
||||
class SessionStore {
|
||||
/*
|
||||
{
|
||||
<session_id>: {
|
||||
<master_token>: String,
|
||||
<destination>: String,
|
||||
<service1>: String, ...
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
private var store = Map[String, Map[String, String]]()
|
||||
|
||||
def session(id: String) = new Session(id)
|
||||
|
||||
def addSession(id: String) = store += (id -> Map[String,String]())
|
||||
|
||||
class Session(id: String) {
|
||||
|
||||
var sessionStore = store get id
|
||||
|
||||
def masterToken_= (token: String): Unit = sessionStore.get += ("master_token" -> token)
|
||||
|
||||
def masterToken = store get id flatMap { _ get "master_token" }
|
||||
|
||||
def serviceToken_= (t: (String, String)) = sessionStore.get += (t._1 -> t._2)
|
||||
|
||||
def serviceToken = store get id
|
||||
|
||||
def destination_= (destination: String) = sessionStore.get += ("destination" -> destination)
|
||||
|
||||
def destination = store get id flatMap { _ get "destination" }
|
||||
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package com.lookout.borderpatrol.sessions
|
||||
|
||||
import com.twitter.finagle.Service
|
||||
import com.twitter.finagle.SimpleFilter
|
||||
import com.twitter.finagle.Service
|
||||
import com.twitter.finagle.SimpleFilter
|
||||
import org.jboss.netty.handler.codec.http._
|
||||
import scala.collection.immutable.{Map, HashMap}
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||
|
||||
class SessionStoreFilter(sessionStore: SessionStore) extends SimpleFilter[HttpRequest, HttpResponse] {
|
||||
def apply(request: HttpRequest, service: Service[HttpRequest, HttpResponse]) = {
|
||||
val serviceName = request.getHeader("SERVICE_NAME")
|
||||
val sessionId = request.getHeader("BORDER_PATROL_SESSION_ID")
|
||||
val session = sessionStore.session(sessionId)
|
||||
session.serviceToken map { _ get serviceName } map {
|
||||
request.setHeader("Auth-Token", _)
|
||||
}
|
||||
|
||||
System.out.println("Auth-Token: %s".format(request.getHeader("Auth-Token")))
|
||||
service(request)
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package com.lookout.borderpatrol
|
||||
|
||||
/**
|
||||
* Created by akuhnhausen on 6/11/14.
|
||||
*/
|
||||
package object sessions {
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package com.lookout.borderpatrol
|
||||
|
||||
import com.lookout.borderpatrol.BorderPatrolApp._
|
||||
import com.twitter.finagle.{Filter, Service}
|
||||
import com.twitter.util.{Future, Await}
|
||||
import org.jboss.netty.handler.codec.http._
|
||||
import org.scalatest.{Matchers, FlatSpec}
|
||||
import com.twitter.finagle.http.{Http, Request => FinagleRequest, Response => FinagleResponse}
|
||||
|
||||
/**
|
||||
* Created by wkimeria on 12/12/14.
|
||||
*/
|
||||
class UpstreamFilterSpec extends FlatSpec with Matchers{
|
||||
def mockUpstreamService(response: String) = new Service[HttpRequest, String] {
|
||||
def apply(request: HttpRequest) = Future.value(response)
|
||||
}
|
||||
|
||||
def mockRequest = new RoutedRequest(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,"/upstream"), new Session())
|
||||
|
||||
def mockUpService200 = new Service [HttpRequest, FinagleResponse]{
|
||||
def apply(request: HttpRequest) = {
|
||||
Future.value(new Response(new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK)))
|
||||
}
|
||||
}
|
||||
|
||||
def mockUpService403 = new Service [HttpRequest, FinagleResponse]{
|
||||
def apply(request: HttpRequest) = {
|
||||
Future.value(new NeedsAuthResponse(new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN)))
|
||||
}
|
||||
}
|
||||
|
||||
"An UpstreamFilter" should "route appropriately" in {
|
||||
Await.result(new UpstreamFilter(None, Some(mockUpService200)).apply(mockRequest, mockUpService200)) shouldBe a [Response]
|
||||
Await.result(new UpstreamFilter(None, Some(mockUpService403)).apply(mockRequest, mockUpService403)) shouldBe a [NeedsAuthResponse]
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ This module includes dummy integration test services for Border Patrol.
|
|||
#### Darwin
|
||||
|
||||
* bundle install
|
||||
* god load borderpatrol
|
||||
* god -Dbc borderpatrol.god
|
||||
|
||||
[God](http://godrb.com) to run 4 processes, on the following ports
|
||||
|
||||
|
@ -18,4 +18,7 @@ This module includes dummy integration test services for Border Patrol.
|
|||
* `9083` Mock downstream service B
|
||||
* `9084` Mock Account Service
|
||||
|
||||
To stop the integration services, run god stop borderpatrol
|
||||
To stop the integration services, kill god and shotgun
|
||||
|
||||
pkill -9 -f shotgun; pkill -9 -f god
|
||||
|
||||
|
|
|
@ -9,8 +9,7 @@ object BorderPatrol extends Build {
|
|||
organization := "com.lookout",
|
||||
scalaVersion := "2.10.3",
|
||||
libraryDependencies ++= Seq(
|
||||
"com.twitter" %% "twitter-server" % "1.0.2",
|
||||
"org.scalatest" % "scalatest_2.11" % "2.2.0" % "test"
|
||||
"com.twitter" % "twitter-server_2.10" % "1.8.0"
|
||||
),
|
||||
|
||||
scalacOptions ++= Seq("-encoding", "utf8"),
|
||||
|
|
Loading…
Reference in New Issue