-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
finagle/finagle-http: propagate contexts over HTTP
Problem Contexts are not sent over HTTP, which means deadlines are not propagated. Solution Deadlines are sent in Finagle-Ctx request headers. RB_ID=778123 TBR=true
- Loading branch information
Showing
10 changed files
with
238 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 2 additions & 1 deletion
3
finagle-http/src/main/scala/com/twitter/finagle/http/codec/HttpClientDispatcher.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
finagle-http/src/main/scala/com/twitter/finagle/http/codec/HttpContext.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package com.twitter.finagle.http.codec | ||
|
||
import com.twitter.finagle._ | ||
import com.twitter.finagle.context.Contexts | ||
import com.twitter.finagle.http.Message | ||
import com.twitter.logging.Logger | ||
import com.twitter.util.{NonFatal, Time} | ||
|
||
private[http] object HttpContext { | ||
|
||
private[this] val Prefix = "Finagle-Ctx-" | ||
private[this] val DeadlineHeaderKey = Prefix+Deadline.id | ||
|
||
private val log = Logger(getClass.getName) | ||
|
||
private[this] def marshalDeadline(deadline: Deadline): String = | ||
deadline.timestamp.inNanoseconds + " " + deadline.deadline.inNanoseconds | ||
|
||
private[this] def unmarshalDeadline(header: String): Option[Deadline] = | ||
try { | ||
val values = header.split(' ') | ||
val timestamp = values(0).toLong | ||
val deadline = values(1).toLong | ||
Some(Deadline(Time.fromNanoseconds(timestamp), Time.fromNanoseconds(deadline))) | ||
} catch { | ||
case NonFatal(exc) => | ||
log.debug(s"Could not unmarshall Deadline from header value: ${header}") | ||
None | ||
} | ||
|
||
/** | ||
* Read Finagle-Ctx header pairs from the given message for Contexts: | ||
* - Deadline | ||
* and run `fn`. | ||
*/ | ||
def read[R](msg: Message)(fn: => R): R = | ||
msg.headerMap.get(DeadlineHeaderKey) match { | ||
case Some(str) => | ||
unmarshalDeadline(str) match { | ||
case Some(deadline) => Contexts.broadcast.let(Deadline, deadline)(fn) | ||
case None => fn | ||
} | ||
case None => | ||
fn | ||
} | ||
|
||
/** | ||
* Write Finagle-Ctx header pairs into the given message for Contexts: | ||
* - Deadline | ||
*/ | ||
def write(msg: Message): Unit = | ||
Contexts.broadcast.get(Deadline) match { | ||
case Some(deadline) => | ||
msg.headerMap.set(DeadlineHeaderKey, marshalDeadline(deadline)) | ||
case None => | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
finagle-http/src/main/scala/com/twitter/finagle/http/filter/ContextFilter.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package com.twitter.finagle.http.filter | ||
|
||
import com.twitter.finagle._ | ||
import com.twitter.finagle.context.Contexts | ||
import com.twitter.finagle.http.Request | ||
import com.twitter.finagle.http.codec.HttpContext | ||
import com.twitter.util.Future | ||
|
||
/** | ||
* Sets the following Context values from the request headers: | ||
* - request deadline | ||
*/ | ||
private[finagle] class ServerContextFilter[Req <: Request, Rep] | ||
extends SimpleFilter[Req, Rep] { | ||
|
||
def apply(req: Req, service: Service[Req, Rep]): Future[Rep] = | ||
HttpContext.read(req)(service(req)) | ||
} | ||
|
||
/** | ||
* Sets the following header values for the request Context: | ||
* - request deadline | ||
*/ | ||
private[finagle] class ClientContextFilter[Req <: Request, Rep] | ||
extends SimpleFilter[Req, Rep] { | ||
|
||
def apply(req: Req, service: Service[Req, Rep]): Future[Rep] = { | ||
HttpContext.write(req) | ||
service(req) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
finagle-http/src/test/scala/com/twitter/finagle/http/codec/HttpContextTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package com.twitter.finagle.http.codec | ||
|
||
import com.twitter.conversions.time._ | ||
import com.twitter.finagle._ | ||
import com.twitter.finagle.context.Contexts | ||
import com.twitter.finagle.Deadline | ||
import com.twitter.finagle.http.{Message, Method, Request, Version} | ||
import org.junit.runner.RunWith | ||
import org.scalatest.FunSuite | ||
import org.scalatest.junit.JUnitRunner | ||
|
||
@RunWith(classOf[JUnitRunner]) | ||
class HttpContextTest extends FunSuite { | ||
|
||
def newMsg(): Message = Request(Version.Http11, Method.Get, "/") | ||
|
||
test("written request context matches read request context") { | ||
val m = newMsg() | ||
val writtenDeadline = Deadline.ofTimeout(5.seconds) | ||
Contexts.broadcast.let(Deadline, writtenDeadline) { | ||
HttpContext.write(m) | ||
|
||
// Clear the deadline value in the context | ||
Contexts.broadcast.letClear(Deadline) { | ||
|
||
// ensure the deadline was cleared | ||
assert(Contexts.broadcast.get(Deadline) == None) | ||
|
||
HttpContext.read(m) { | ||
val readDeadline = Contexts.broadcast.get(Deadline).get | ||
assert(writtenDeadline == readDeadline) | ||
} | ||
} | ||
} | ||
} | ||
|
||
test("invalid context header value causes context to not be set") { | ||
val m = newMsg() | ||
m.headers.set("Finagle-Ctx-com.twitter.finagle.foo", ",,,"); | ||
HttpContext.read(m) { | ||
assert(Contexts.broadcast.marshal.isEmpty) | ||
} | ||
} | ||
|
||
test("when there are no context headers, reading returns an empty iterator") { | ||
val m = newMsg() | ||
HttpContext.read(m) { | ||
assert(Contexts.broadcast.marshal.isEmpty) | ||
} | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
finagle-http/src/test/scala/com/twitter/finagle/http/filter/ContextFilterTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package com.twitter.finagle.http.filter | ||
|
||
import com.twitter.conversions.time._ | ||
import com.twitter.finagle.{Deadline, Service} | ||
import com.twitter.finagle.context.Contexts | ||
import com.twitter.finagle.http.{Status, Response, Request} | ||
import com.twitter.finagle.http.codec.HttpContext | ||
import com.twitter.util.{Await, Future} | ||
import org.junit.runner.RunWith | ||
import org.scalatest.FunSuite | ||
import org.scalatest.junit.JUnitRunner | ||
|
||
@RunWith(classOf[JUnitRunner]) | ||
class ContextFilterTest extends FunSuite { | ||
|
||
test("parses Finagle-Ctx headers") { | ||
val writtenDeadline = Deadline.ofTimeout(5.seconds) | ||
val service = | ||
new ClientContextFilter[Request, Response] andThen | ||
new ServerContextFilter[Request, Response] andThen | ||
Service.mk[Request, Response] { req => | ||
assert(Contexts.broadcast.get(Deadline).get == writtenDeadline) | ||
Future.value(Response()) | ||
} | ||
|
||
Contexts.broadcast.let(Deadline, writtenDeadline) { | ||
val req = Request() | ||
HttpContext.write(req) | ||
|
||
// Clear the deadline value in the context | ||
Contexts.broadcast.letClear(Deadline) { | ||
// ensure the deadline was cleared | ||
assert(Contexts.broadcast.get(Deadline) == None) | ||
|
||
val rsp = Await.result(service(req)) | ||
assert(rsp.status == Status.Ok) | ||
} | ||
} | ||
} | ||
|
||
test("does not set incorrectly encoded context headers") { | ||
val service = | ||
new ClientContextFilter[Request, Response] andThen | ||
new ServerContextFilter[Request, Response] andThen | ||
Service.mk[Request, Response] { _ => | ||
assert(Contexts.broadcast.marshal.isEmpty) | ||
Future.value(Response()) | ||
} | ||
|
||
val req = Request() | ||
req.headers().add("Finagle-Ctx-com.twitter.finagle.Deadline", "foo") | ||
|
||
val rsp = Await.result(service(req)) | ||
assert(rsp.status == Status.Ok) | ||
} | ||
} |