Created
March 6, 2014 16:01
-
-
Save sirthias/9392921 to your computer and use it in GitHub Desktop.
+ routing: add `conditional` support to FileAndResourceDirectives
This file contains hidden or 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
diff --git a/spray-routing/src/main/resources/reference.conf b/spray-routing/src/main/resources/reference.conf | |
index 67c6177..08aaf84 100644 | |
--- a/spray-routing/src/main/resources/reference.conf | |
+++ b/spray-routing/src/main/resources/reference.conf | |
@@ -21,6 +21,9 @@ spray.routing { | |
# the size of an individual chunk when streaming file content | |
file-chunking-chunk-size = 128k | |
+ # Enables/disables ETag and `If-Modified-Since` support for FileAndResourceDirectives | |
+ file-get-conditional = on | |
+ | |
# Enables/disables the rendering of the "rendered by" footer in directory listings | |
render-vanity-footer = yes | |
diff --git a/spray-routing/src/main/scala/spray/routing/RoutingSettings.scala b/spray-routing/src/main/scala/spray/routing/RoutingSettings.scala | |
index 6612dbf..c0bd6ed 100644 | |
--- a/spray-routing/src/main/scala/spray/routing/RoutingSettings.scala | |
+++ b/spray-routing/src/main/scala/spray/routing/RoutingSettings.scala | |
@@ -24,6 +24,7 @@ case class RoutingSettings( | |
verboseErrorMessages: Boolean, | |
fileChunkingThresholdSize: Long, | |
fileChunkingChunkSize: Int, | |
+ fileGetConditional: Boolean, | |
users: Config, | |
renderVanityFooter: Boolean) { | |
@@ -36,6 +37,7 @@ object RoutingSettings extends SettingsCompanion[RoutingSettings]("spray.routing | |
c getBoolean "verbose-error-messages", | |
c getBytes "file-chunking-threshold-size", | |
c getIntBytes "file-chunking-chunk-size", | |
+ c getBoolean "file-get-conditional", | |
c getConfig "users", | |
c getBoolean "render-vanity-footer") | |
diff --git a/spray-routing/src/main/scala/spray/routing/directives/FileAndResourceDirectives.scala b/spray-routing/src/main/scala/spray/routing/directives/FileAndResourceDirectives.scala | |
index 34f0fe4..29bc785 100644 | |
--- a/spray-routing/src/main/scala/spray/routing/directives/FileAndResourceDirectives.scala | |
+++ b/spray-routing/src/main/scala/spray/routing/directives/FileAndResourceDirectives.scala | |
@@ -28,6 +28,7 @@ import HttpHeaders._ | |
/* format: OFF */ | |
trait FileAndResourceDirectives { | |
+ import CacheConditionDirectives._ | |
import ChunkingDirectives._ | |
import ExecutionDirectives._ | |
import MethodDirectives._ | |
@@ -64,8 +65,8 @@ trait FileAndResourceDirectives { | |
get { | |
detach() { | |
if (file.isFile && file.canRead) { | |
- respondWithLastModifiedHeader(file.lastModified) { | |
- autoChunk(settings.fileChunkingThresholdSize, settings.fileChunkingChunkSize) { | |
+ autoChunked.apply { | |
+ conditionalFor(file.length, file.lastModified).apply { | |
complete(HttpEntity(contentType, HttpData(file))) | |
} | |
} | |
@@ -73,10 +74,20 @@ trait FileAndResourceDirectives { | |
} | |
} | |
+ private def autoChunked(implicit settings: RoutingSettings, refFactory: ActorRefFactory): Directive0 = | |
+ autoChunk(settings.fileChunkingThresholdSize, settings.fileChunkingChunkSize) | |
+ | |
+ private def conditionalFor(length: Long, lastModified: Long)(implicit settings: RoutingSettings): Directive0 = | |
+ if (settings.fileGetConditional) { | |
+ val tag = java.lang.Long.toHexString(lastModified ^ java.lang.Long.reverse(length)) | |
+ val lastModifiedDateTime = DateTime(math.min(lastModified, System.currentTimeMillis)) | |
+ conditional(EntityTag(tag), lastModifiedDateTime) | |
+ } else BasicDirectives.noop | |
+ | |
/** | |
* Adds a Last-Modified header to all HttpResponses from its inner Route. | |
*/ | |
- def respondWithLastModifiedHeader(timestamp: Long): Directive0 = | |
+ def respondWithLastModifiedHeader(timestamp: Long): Directive0 = // TODO: remove since it is not needed anymore here | |
respondWithHeader(`Last-Modified`(DateTime(math.min(timestamp, System.currentTimeMillis)))) | |
/** | |
@@ -104,19 +115,22 @@ trait FileAndResourceDirectives { | |
theClassLoader.getResource(resourceName) match { | |
case null ⇒ reject | |
case url ⇒ | |
- val lastModified = { | |
+ val (length, lastModified) = { | |
val conn = url.openConnection() | |
conn.setUseCaches(false) // otherwise the JDK will keep the JAR file open when we close! | |
+ val len = conn.getContentLengthLong | |
val lm = conn.getLastModified | |
conn.getInputStream.close() | |
- lm | |
+ len -> lm | |
} | |
implicit val bufferMarshaller = BasicMarshallers.byteArrayMarshaller(contentType) | |
- respondWithLastModifiedHeader(lastModified) { | |
- complete { | |
- // readAllBytes closes the InputStream when done, which ends up closing the JAR file | |
- // if caching has been disabled on the connection | |
- FileUtils.readAllBytes(url.openStream()) | |
+ autoChunked.apply { // TODO: add implicit RoutingSettings to method and use here | |
+ conditionalFor(length, lastModified).apply { | |
+ complete { | |
+ // readAllBytes closes the InputStream when done, which ends up closing the JAR file | |
+ // if caching has been disabled on the connection | |
+ FileUtils.readAllBytes(url.openStream()) | |
+ } | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment