Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Last-Modified HTTP header of assets contained in jar is wrong when using sbt >= 1.4.0 #10572

Open
bchazalet opened this issue Dec 3, 2020 · 6 comments · May be fixed by #12895
Open

Last-Modified HTTP header of assets contained in jar is wrong when using sbt >= 1.4.0 #10572

bchazalet opened this issue Dec 3, 2020 · 6 comments · May be fixed by #12895

Comments

@bchazalet
Copy link

bchazalet commented Dec 3, 2020

Play Version

2.7.7

API

Scala

Operating System

macosx but will affect all I believe

JDK

Seen on OpenJDK 15 and Oracle Java 8

Library Dependencies

Sbt 1.4.4

Expected Behavior

  1. Package a play application using universal:packageZipTarball and sbt >= 1.4.0
  2. Deploy the application
  3. Send a GET request for one of the static asset
  4. Inspect the last-modified response header and assert that it aligns with when you packaged the application in step 1

Actual Behavior

  1. Package a play application using universal:packageZipTarball
  2. Deploy the application
  3. Send a GET request for one of the static asset, say
  4. Inspect the last-modified response header: depending on your platform the last modified header will either be set to 1 Jan 1970 or 7 February 2106 (which known to be a common time storage issue).

We've tracked down the issue to this sbt change: sbt/sbt#5344. Basically, sbt now resets file timestamps in jars to 1970-01-01. And that date is in turn used by the play framework to set the last-modified header. You can double check by unpacking the jar yourself, jar fx your-assets-jar.jar and inspecting the dates on the filesystem.

One fix to this is to set an environment variable to the current time when building and packaging the play application, e.g.:

export SOURCE_DATE_EPOCH=`date +%s`

I don't know if this should be fixed by the framework itself, but I think it should at least be documented as it will (and did for us) cause cache issues.

@bchazalet bchazalet changed the title last-modified of assets contained in jar is wrong when using sbt >= 1.4.0 last-modified HTTP header of assets contained in jar is wrong when using sbt >= 1.4.0 Dec 3, 2020
@bchazalet bchazalet changed the title last-modified HTTP header of assets contained in jar is wrong when using sbt >= 1.4.0 Last-Modified HTTP header of assets contained in jar is wrong when using sbt >= 1.4.0 Dec 3, 2020
eed3si9n added a commit to eed3si9n/sbt that referenced this issue Jan 3, 2021
Fixes sbt#6235

In sbt 1.4.0 (sbt#5344) we started wiping out the timestamps in JAR to make the builds more repeatable. This had an unintended consequence of breaking Play's last-modified response header (playframework/playframework#10572).

This adds an opt-out from the default:

```scala
Compile / packageBin / packageOptions += Package.keepTimestamps
```

### before

```
$ ll example
total 32
-rw-r--r--  1 eed3si9n  wheel   901 Jan  1  1970 Greeting.class
-rw-r--r--  1 eed3si9n  wheel  3079 Jan  1  1970 Hello$.class
-rw-r--r--  1 eed3si9n  wheel   738 Jan  1  1970 Hello$delayedInit$body.class
-rw-r--r--  1 eed3si9n  wheel   875 Jan  1  1970 Hello.class
```

### after

```
$ ll target/scala-2.13/hello_2.13-0.1.0-SNAPSHOT/example
total 32
-rwxr-xr-x  1 eed3si9n  wheel   901 Jan  3 12:20 Greeting.class*
-rwxr-xr-x  1 eed3si9n  wheel  3079 Jan  3 12:20 Hello$.class*
-rwxr-xr-x  1 eed3si9n  wheel   738 Jan  3 12:20 Hello$delayedInit$body.class*
-rwxr-xr-x  1 eed3si9n  wheel   875 Jan  3 12:20 Hello.class*
```
@huntc
Copy link
Contributor

huntc commented Jan 3, 2021

To add some context, I think it was me that provided this functionality in Play many moons ago. Assets can of course be provided as Webjars. The date of the webjar is therefore important. My memory on this is vague though.

eed3si9n added a commit to eed3si9n/sbt that referenced this issue Jan 25, 2021
…mmitDate

Fixes sbt#6235

In sbt 1.4.0 (sbt#5344) we started wiping out the timestamps in JAR to make the builds more repeatable. This had an unintended consequence of breaking Play's last-modified response header (playframework/playframework#10572).

This adds an opt-out from the default:

```scala
ThisBuild / packageOptions += Package.keepTimestamps
```

Before
------

```
$ ll example
total 32
-rw-r--r--  1 eed3si9n  wheel   901 Jan  1  1970 Greeting.class
-rw-r--r--  1 eed3si9n  wheel  3079 Jan  1  1970 Hello$.class
-rw-r--r--  1 eed3si9n  wheel   738 Jan  1  1970 Hello$delayedInit$body.class
-rw-r--r--  1 eed3si9n  wheel   875 Jan  1  1970 Hello.class
```

After
-----

```
$ ll target/scala-2.13/hello_2.13-0.1.0-SNAPSHOT/example
total 32
-rwxr-xr-x  1 eed3si9n  wheel   901 Jan  3 12:20 Greeting.class*
-rwxr-xr-x  1 eed3si9n  wheel  3079 Jan  3 12:20 Hello$.class*
-rwxr-xr-x  1 eed3si9n  wheel   738 Jan  3 12:20 Hello$delayedInit$body.class*
-rwxr-xr-x  1 eed3si9n  wheel   875 Jan  3 12:20 Hello.class*
```
eed3si9n added a commit to eed3si9n/sbt that referenced this issue Jan 25, 2021
Fixes sbt#6235

In sbt 1.4.0 (sbt#5344) we started wiping out the timestamps in JAR
to make the builds more repeatable.
This had an unintended consequence of breaking Play's last-modified response header (playframework/playframework#10572).

This adds a global setting called `packageTimestamp`, which is
initialized as follows:

```scala
packageTimestamp :== Package.defaultTimestamp,
```

Here the `Package.defaultTimestamp` would pick either the value from the
`SOURCE_DATE_EPOCH` environment variable or 2010-01-01.

To opt out of this default, the user can use:

```scala
ThisBuild / packageTimestamp := Package.keepTimestamps

// or

ThisBuild / packageTimestamp := Package.gitCommitDateTimestamp
```

Before (sbt 1.4.6)
------------------

```
$ ll example
total 32
-rw-r--r--  1 eed3si9n  wheel   901 Jan  1  1970 Greeting.class
-rw-r--r--  1 eed3si9n  wheel  3079 Jan  1  1970 Hello$.class
-rw-r--r--  1 eed3si9n  wheel   738 Jan  1  1970 Hello$delayedInit$body.class
-rw-r--r--  1 eed3si9n  wheel   875 Jan  1  1970 Hello.class
```

After (using Package.gitCommitDateTimestamp)
--------------------------------------------

```
$ unzip -v target/scala-2.13/root_2.13-0.1.0-SNAPSHOT.jar
Archive:  target/scala-2.13/root_2.13-0.1.0-SNAPSHOT.jar
 Length   Method    Size  Cmpr    Date    Time   CRC-32   Name
--------  ------  ------- ---- ---------- ----- --------  ----
     288  Defl:N      136  53% 01-25-2021 03:09 888682a9  META-INF/MANIFEST.MF
       0  Stored        0   0% 01-25-2021 03:09 00000000  example/
     901  Defl:N      601  33% 01-25-2021 03:09 3543f377  example/Greeting.class
    3079  Defl:N     1279  59% 01-25-2021 03:09 848b4386  example/Hello$.class
     738  Defl:N      464  37% 01-25-2021 03:09 571f4288  example/Hello$delayedInit$body.class
     875  Defl:N      594  32% 01-25-2021 03:09 ad295259  example/Hello.class
--------          -------  ---                            -------
    5881             3074  48%                            6 files
```
@mkurz
Copy link
Member

mkurz commented Feb 22, 2021

sbt 1.5 makes this configurable:
https://github.com/sbt/sbt/releases/tag/v1.5.0-M2 (see heading "ThisBuild / packageTimestamp setting)
I think we should at least document this.

@strelec
Copy link

strelec commented Mar 21, 2021

Ok,

if this is the case, then let's stop setting the Last-Modified header on assets - obviously it is not correct and will mess up caching.

This header is AFAIK not mandatory, and we are sending ETag as an alterative.

andrew-nowak added a commit to guardian/grid that referenced this issue Jan 14, 2022
Since SBT 1.4.0, SBT has edited the last modified dates in packaged
JARs, which include web assets (css, js, etc.); Grid's current version
sets the date to 1 Jan 2010. Play framework uses these dates to
calculate the ETag and Last-Modified headers. Since the headers are now
based upon a static date, caching (especially via Cloudfront) is broken,
returning outdated assets.

This commit changes the default to set the timestamp to match the latest
git commit date, so cloudfront should now notice and pick up new assets
upon deployment.

See also <playframework/playframework#10572>
See also <sbt/sbt#6237>
andrew-nowak added a commit to guardian/grid that referenced this issue Jan 14, 2022
Since SBT 1.4.0, SBT has edited the last modified dates in packaged
JARs, which include web assets (css, js, etc.); Grid's current version
sets the date to 1 Jan 2010. Play framework uses these dates to
calculate the ETag and Last-Modified headers. Since the headers are now
based upon a static date, caching (especially via Cloudfront) is broken,
returning outdated assets.

This commit restores the previous behaviour, so cloudfront should now
notice and pick up new assets upon deployment.

See also <playframework/playframework#10572>
See also <sbt/sbt#6237>
andrew-nowak added a commit to guardian/grid that referenced this issue Jan 14, 2022
Since SBT 1.4.0, SBT has edited the last modified dates in packaged
JARs, which include web assets (css, js, etc.); Grid's current version
sets the date to 1 Jan 2010. Play framework uses these dates to
calculate the ETag and Last-Modified headers. Since the headers are now
based upon a static date, caching (especially via Cloudfront) is broken,
returning outdated assets.

This commit restores the previous behaviour, so cloudfront should now
notice and pick up new assets upon deployment.

See also <playframework/playframework#10572>
See also <sbt/sbt#6237>
andrew-nowak added a commit to guardian/grid that referenced this issue Jan 14, 2022
Since SBT 1.4.0, SBT has edited the last modified dates in packaged
JARs, which include web assets (css, js, etc.); Grid's current version
sets the date to 1 Jan 2010. Play framework uses these dates to
calculate the ETag and Last-Modified headers. Since the headers are now
based upon a static date, caching (especially via Cloudfront) is broken,
returning outdated assets.

This commit restores the previous behaviour, so cloudfront should now
notice and pick up new assets upon deployment.

See also <playframework/playframework#10572>
See also <sbt/sbt#6237>
andrew-nowak added a commit to guardian/workflow-frontend that referenced this issue Jan 17, 2022
Since SBT 1.4.0, SBT has edited the last modified dates in packaged
JARs, which include web assets (css, js, etc.); Grid's current version
sets the date to 1 Jan 2010. Play framework uses these dates to
calculate the ETag and Last-Modified headers. Since the headers are now
based upon a static date, caching (especially via Cloudfront) is broken,
returning outdated assets.

This commit restores the previous behaviour, so cloudfront should now
notice and pick up new assets upon deployment.

See also <playframework/playframework#10572>
See also <sbt/sbt#6237>
andrew-nowak added a commit to guardian/workflow-frontend that referenced this issue Jan 17, 2022
Since SBT 1.4.0, SBT has edited the last modified dates in packaged
JARs, which include web assets (css, js, etc.); Grid's current version
sets the date to 1 Jan 2010. Play framework uses these dates to
calculate the ETag and Last-Modified headers. Since the headers are now
based upon a static date, caching (especially via Cloudfront) is broken,
returning outdated assets.

This commit restores the previous behaviour, so cloudfront should now
notice and pick up new assets upon deployment.

See also <playframework/playframework#10572>
See also <sbt/sbt#6237>
andrew-nowak added a commit to guardian/facia-tool that referenced this issue Jan 17, 2022
Since SBT 1.4.0, SBT has edited the last modified dates in packaged
JARs, which include web assets (css, js, etc.); Grid's current version
sets the date to 1 Jan 2010. Play framework uses these dates to
calculate the ETag and Last-Modified headers. Since the headers are now
based upon a static date, caching (especially via Cloudfront) is broken,
returning outdated assets.

This commit restores the previous behaviour, so cloudfront should now
notice and pick up new assets upon deployment.

See also <playframework/playframework#10572>
See also <sbt/sbt#6237>
andrew-nowak added a commit to guardian/facia-tool that referenced this issue Jan 17, 2022
Since SBT 1.4.0, SBT has edited the last modified dates in packaged
JARs, which include web assets (css, js, etc.); our current version sets
the date to 1 Jan 2010. Play framework uses these dates to calculate the
ETag and Last-Modified headers. Since the headers are now based upon a
static date, caching (especially via Cloudfront) is broken, returning
outdated assets.

This commit restores the previous behaviour, so cloudfront should now
notice and pick up new assets upon deployment.

See also <playframework/playframework#10572>
See also <sbt/sbt#6237>
andrew-nowak added a commit to guardian/workflow-frontend that referenced this issue Jan 17, 2022
Since SBT 1.4.0, SBT has edited the last modified dates in packaged
JARs, which include web assets (css, js, etc.); our current version sets
the date to 1 Jan 2010. Play framework uses these dates to calculate the
ETag and Last-Modified headers. Since the headers are now based upon a
static date, caching (especially via Cloudfront) is broken, returning
outdated assets.

This commit restores the previous behaviour, so cloudfront should now
notice and pick up new assets upon deployment.

See also <playframework/playframework#10572>
See also <sbt/sbt#6237>
andrew-nowak added a commit to guardian/s3-upload that referenced this issue Jan 17, 2022
Since SBT 1.4.0, SBT has edited the last modified dates in packaged
JARs, which include web assets (css, js, etc.); our current version sets
the date to 1 Jan 2010. Play framework uses these dates to calculate the
ETag and Last-Modified headers. Since the headers are now based upon a
static date, caching (especially via Cloudfront) is broken, returning
outdated assets.

This commit restores the previous behaviour, so cloudfront should now
notice and pick up new assets upon deployment.

See also <playframework/playframework#10572>
See also <sbt/sbt#6237>
SunPj added a commit to SunPj/play-scala-react-seed that referenced this issue Feb 12, 2024
Last-Modified HTTP header of assets contained in jar is wrong

See playframework/playframework#10572
@RommelTJ
Copy link

RommelTJ commented Oct 4, 2024

I'm looking into this. @mkurz, would this be brand new documentation? Or adding to an existing page? Any suggestion on where this documentation should go?

@RommelTJ
Copy link

RommelTJ commented Oct 4, 2024

I've been looking at updating the documentation for this. I was thinking of changing it here: https://www.playframework.com/documentation/3.0.x/Deploying#The-Native-Packager

However, the suggested workaround from sbt is not working for me. Even after modifying my build.sbt as follows:

val currentTime = System.currentTimeMillis()
ThisBuild / packageTimestamp := Some(currentTime)
Universal / packageBin / packageTimestamp := (ThisBuild / packageTimestamp).value
Compile / packageBin / packageTimestamp := (ThisBuild / packageTimestamp).value
Assets / packageBin / packageTimestamp := (ThisBuild / packageTimestamp).value

I can confirm sbt will use the specified time, at least with sbt 1.8.2 on macOS Seqoia 15.0.1, but the assets still have the default time:

jar tvf lib/dummyproject.dummyproject-1.0-SNAPSHOT-assets.jar | grep -i 'favicon'
   687 Fri Jan 01 00:00:00 PST 2010 public/images/favicon.png

And I get these headers:

HTTP/1.1 200 OK
ETag: "fff49652dc44c91aedff86cf926c5558669a0570"
Accept-Ranges: bytes
Cache-Control: public, max-age=3600
Last-Modified: Fri, 01 Jan 2010 08:00:00 GMT
Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Permitted-Cross-Domain-Policies: master-only
Date: Fri, 04 Oct 2024 20:17:15 GMT
Content-Type: image/png
Content-Length: 687

Therefore, I don't want to submit a PR with updated documentation for a workaround that doesn't work. Has anyone gotten this to work?

I also tried setting an environment variable like @bchazalet explained, but I still see the same behavior.

Edit: Nevermind, this works: ThisBuild / packageOptions += Package.FixedTimestamp(Package.keepTimestamps)

RommelTJ pushed a commit to RommelTJ/playframework that referenced this issue Oct 4, 2024
@RommelTJ RommelTJ linked a pull request Oct 4, 2024 that will close this issue
7 tasks
@RommelTJ
Copy link

RommelTJ commented Oct 4, 2024

Adding documentation with #12895

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants