Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit d82bbb3
Showing
10 changed files
with
350 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
*.iml | ||
*.jar | ||
/documentation | ||
/dist | ||
/lib | ||
/modules | ||
/tmp |
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,84 @@ | ||
# Play-framework 1.2.x series support for HTTP GZIP compression | ||
|
||
Author: [Jay Taylor][jaytaylor] | ||
Date of first release: April 10, 2012 | ||
|
||
This module is hosted [on github](https://github.com/jaytaylor/play-gzip/ "Play-gzip on github") | ||
|
||
## Description | ||
|
||
This is a plugin to enable GZIP compression of HTTP responses for [Play-framework][] | ||
applications of the 1.2.x variety. | ||
|
||
|
||
## Requirements | ||
|
||
A play-framework application running a [1.2.x series release][Play-framework-releases], | ||
e.g. [play-1.2.4][]. | ||
|
||
|
||
## Adding the plugin to your project | ||
|
||
First, add the gzip dependency and repository to `conf/dependencies.yml`: | ||
|
||
require: | ||
- play | ||
- play -> gzip 0.1 | ||
|
||
repositories: | ||
- play-gzip: | ||
type: http | ||
artifact: "https://github.com/jaytaylor/jaytaylor-mvn-repo/raw/master/releases/play/[module]/[revision]/[module]-[revision].[ext]" | ||
contains: | ||
- play -> * | ||
|
||
and run `play deps --sync --verbose` to retrieve the module. | ||
|
||
Then you're ready to integrate GZIP support into your application! This is as | ||
simple as adding an import statement and extending the controller class with the | ||
`Compress` trait. For DRY purposes, this should usually be a base controller | ||
trait which gets inherited by all other controllers. | ||
|
||
import play.modules.gzip.Compress | ||
|
||
class MyBaseControllerTrait extends Compress { ... } | ||
|
||
|
||
## Configuration | ||
|
||
Directives available for `conf/application.conf`: | ||
|
||
# Set to true to disable gzip compression (defaults to false) | ||
gzip.disabled=false | ||
|
||
# Set to true to disable gzip module logging (defaults to false) | ||
gzip.logging.disabled=false | ||
|
||
If these directives are not present in your configuration, they will both | ||
default to "false" (meaning that these features will be enabled.) | ||
|
||
|
||
## Future iterations | ||
|
||
- Automatically detect the average compression ratio and optimize | ||
accordingly | ||
- Automatically detect the average response size in real-time as | ||
we go and then automatically optimize the buffer allocation | ||
sizes | ||
|
||
|
||
## References | ||
|
||
The development of this Play module has been fun, and there were a few documents | ||
which helped greatly: | ||
|
||
- [engintekin's compression gist][engintekin] | ||
- [lights51's compression optimization commit][lights51] | ||
|
||
[jaytaylor]: http://jaytaylor.com/ "Jay Taylor's website" | ||
[Play-framework]: http://playframework.org/ "Play-framework" | ||
[Play-framework-releases]: http://download.playframework.org/releases/ "Play-framework releases" | ||
[play-1.2.4]: http://download.playframework.org/releases/play-1.2.4.zip "Play v1.2.4" | ||
[engintekin]: https://gist.github.com/1317626 "engintekin's compression gist" | ||
[lights51]: https://github.com/lights51/play/commit/a029b74a143464a1dec008b0de6d7ba7a75b8f20 "lights51's compression optimization commit" | ||
|
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,92 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project name="play-gzip" default="build" basedir="."> | ||
|
||
<path id="project.classpath"> | ||
<fileset dir="${play.path}/framework"> | ||
<include name="*.jar" /> | ||
</fileset> | ||
<fileset dir="${play.path}/framework/lib"> | ||
<include name="*.jar" /> | ||
</fileset> | ||
<fileset dir="${play.path}/modules/scala-0.9.1/lib"> | ||
<include name="*.jar" /> | ||
</fileset> | ||
<fileset dir="lib"> | ||
<include name="*.jar" /> | ||
</fileset> | ||
<pathelement path="tmp/classes" /> | ||
</path> | ||
|
||
|
||
<target name="clean"> | ||
<delete dir="tmp" /> | ||
<delete file="lib/play-gzip-0.1.jar" /> | ||
<delete dir="lib" /> | ||
<mkdir dir="lib" /> | ||
</target> | ||
|
||
<target name="build" depends="clean"> | ||
<taskdef resource="scala/tools/ant/antlib.xml"> | ||
<classpath refid="project.classpath" /> | ||
</taskdef> | ||
|
||
<mkdir dir="tmp/classes" /> | ||
|
||
<scalac srcdir="src" destdir="tmp/classes" force="changed"> | ||
<classpath refid="project.classpath" /> | ||
</scalac> | ||
|
||
<javac srcdir="src" destdir="tmp/classes" debug="true" includeantruntime="false"> | ||
<classpath refid="project.classpath" /> | ||
</javac> | ||
|
||
<copy todir="tmp/classes"> | ||
<fileset dir="src"> | ||
<include name="**/*.properties" /> | ||
<include name="**/*.xml" /> | ||
<include name="**/play.plugins" /> | ||
</fileset> | ||
</copy> | ||
|
||
<jar destfile="lib/play-gzip-0.1.jar" basedir="tmp/classes"> | ||
<manifest> | ||
<section name="Play-module"> | ||
<attribute name="Specification-Title" value="play-gzip"/> | ||
</section> | ||
</manifest> | ||
</jar> | ||
</target> | ||
|
||
<target name="test" depends="build"> | ||
<echo message="Using ${play.path}/play" /> | ||
<delete dir="${basedir}/samples-and-tests/just-test-cases/tmp" /> | ||
|
||
<antcall target="play-test"> | ||
<param name="testAppPath" value="${basedir}/samples-and-tests/just-test-cases" /> | ||
</antcall> | ||
|
||
<echo message="****************" /> | ||
<echo message="All test passed!" /> | ||
<echo message="****************" /> | ||
</target> | ||
|
||
<target name="play-test"> | ||
<echo message="${play.path}/play auto-test ${testAppPath} (wait)" /> | ||
<exec executable="${play.path}/play" failonerror="true"> | ||
<arg value="clean" /> | ||
<arg value="${testAppPath}" /> | ||
</exec> | ||
<exec executable="${play.path}/play" failonerror="true"> | ||
<arg value="auto-test" /> | ||
<arg value="${testAppPath}" /> | ||
</exec> | ||
<available file="${testAppPath}/test-result/result.passed" property="${testAppPath}testPassed" /> | ||
<fail message="Last test has failed ! (Check results in file://${testAppPath}/test-result)"> | ||
<condition> | ||
<not> | ||
<isset property="${testAppPath}testPassed" /> | ||
</not> | ||
</condition> | ||
</fail> | ||
</target> | ||
</project> |
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,5 @@ | ||
self: play -> gzip 0.1 | ||
|
||
require: | ||
- play [1.2.4,) | ||
- play -> scala 0.9.1 |
Empty file.
Empty file.
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,39 @@ | ||
package controllers | ||
|
||
import play.Logger | ||
import play.mvc.{Before, Controller, Finally} | ||
import play.modules.gzip.Compress | ||
import org.joda.time.DateTime | ||
|
||
/** | ||
* @author Jay Taylor [@jtaylor] | ||
* | ||
* @date 2011-05-23 | ||
*/ | ||
|
||
trait DefaultController extends Compress { | ||
|
||
self: Controller => | ||
|
||
@Before | ||
def setDefaults { | ||
Logger info "Incoming request :: Action=" + request.action + ", params=" + request.params.allSimple | ||
renderArgs += "started" -> new DateTime | ||
} | ||
|
||
@Finally | ||
def finish { | ||
val started = renderArgs.get[DateTime]("started", classOf[DateTime]) | ||
val ended = new DateTime | ||
|
||
Option(started) match { | ||
case Some(started: DateTime) => | ||
val duration = ended.getMillis - started.getMillis | ||
Logger info "Request took " + duration + "ms) :: Action=" + request.action + ", params=" + request.params.allSimple | ||
|
||
case _ => | ||
Logger warn "[WEIRD] For some reason renderArgs.get(\"started\") was null! :: Action=" + request.action + ", params=" + request.params.allSimple | ||
} | ||
} | ||
} | ||
|
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 @@ | ||
0:play.modules.gzip.GzipCompressionPlugin |
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,95 @@ | ||
package play.modules.gzip | ||
|
||
import GzipCompressionPlugin.{gzipDisabled, loggingDisabled} | ||
import play.Logger | ||
import play.mvc.{Controller, Finally} | ||
import scala.collection.JavaConverters._ | ||
import java.io.{ByteArrayInputStream, ByteArrayOutputStream} | ||
import java.util.zip.GZIPOutputStream | ||
|
||
/** | ||
* @author Jay Taylor [@jtaylor] | ||
* | ||
* @date 2012-04-02 | ||
*/ | ||
trait Compress { | ||
|
||
|
||
self: Controller => | ||
|
||
/** | ||
* Apply GZIP compression to the response output. | ||
*/ | ||
@Finally | ||
def compress() { | ||
// Check whether or not module is enabled. | ||
gzipDisabled match { | ||
case true => Logger info "GZIP compression module disabled by configuration" | ||
|
||
case false => | ||
// Look for the "Accept-Encoding" header. | ||
request.headers.asScala find { _._1.equalsIgnoreCase("accept-encoding") } match { | ||
case Some((_, header)) => | ||
// Look for "gzip" as a supplied accepted encoding. | ||
header.values.asScala mkString "," split ',' find { _.trim.equalsIgnoreCase("gzip") } match { | ||
case Some(_) => | ||
val text = response.out.toString | ||
val gzipped = gzip(text) | ||
val bytesOut = gzipped.size | ||
|
||
if (!loggingDisabled) { | ||
val bytesIn = (text getBytes "UTF-8").length | ||
|
||
// Print some stats to the log. | ||
val percentage = if (text.length == 0) { | ||
0 | ||
} else { | ||
((1 - (1.0 * gzipped.size / text.length)) * 100).round | ||
} | ||
Logger info "GZIP compression deflated " + bytesOut + "/" + bytesIn + " (" + percentage + "%)" | ||
} | ||
|
||
response.setHeader("Content-Encoding", "gzip") | ||
response.setHeader("Content-Length", bytesOut.toString) | ||
response.out = gzipped | ||
|
||
case None => // Client did not support gzip responses. | ||
Logger info "GZIP compression not possible - not supported by client ('gzip' not found in 'accept-encoding' header [value=" + header.toString + "])" | ||
} | ||
|
||
case None => // Client did not support gzip responses. | ||
Logger info "GZIP compression not possible - not supported by client (\"accept-encoding\" was not specified)" | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Compress the input string using the GZIP algorithm. | ||
*/ | ||
def gzip(input: String): ByteArrayOutputStream = { | ||
val inputStream = new ByteArrayInputStream(input.getBytes) | ||
|
||
// Pre-allocate some bytes based on the optimistic assumption that | ||
// some amount of compression will occur | ||
val hopingForCompressibility = (input.length * 0.50).toInt | ||
|
||
val byteArrayOut = new ByteArrayOutputStream(hopingForCompressibility) | ||
|
||
val gzipper = new GZIPOutputStream(byteArrayOut) | ||
|
||
val buf = new Array[Byte](4096) | ||
|
||
var numBytesRead = inputStream read buf | ||
while (numBytesRead > 0) { | ||
gzipper.write(buf, 0, numBytesRead) | ||
numBytesRead = inputStream read buf | ||
} | ||
|
||
// Clean up. | ||
inputStream.close | ||
gzipper.close | ||
|
||
byteArrayOut | ||
} | ||
} | ||
|
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,27 @@ | ||
package play.modules.gzip | ||
|
||
import play.{configuration, Logger, PlayPlugin} | ||
|
||
/** | ||
* GZIP Compression Plugin | ||
* | ||
* @author Jay Taylor [@jtaylor] | ||
* @date 2012-04-06 | ||
*/ | ||
|
||
object GzipCompressionPlugin { | ||
val gzipDisabled = configuration("gzip.disabled", "false").toBoolean | ||
val loggingDisabled = configuration("gzip.logging.disabled", "false").toBoolean | ||
} | ||
|
||
class GzipCompressionPlugin extends PlayPlugin { | ||
|
||
import GzipCompressionPlugin._ | ||
|
||
/** | ||
* Play Framework Hook: onApplicationStart | ||
*/ | ||
override def onApplicationStart: Unit = | ||
Logger info "GZIP deflate module started (gzipDisabled=" + gzipDisabled + ", loggingDisabled=" + loggingDisabled + ")" | ||
} | ||
|