Skip to content

Commit

Permalink
[JN-665] Adding asset fingerprint fallback to participant site (#615)
Browse files Browse the repository at this point in the history
  • Loading branch information
devonbush authored Nov 2, 2023
1 parent 9df57ba commit 2f91923
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 5 deletions.
37 changes: 32 additions & 5 deletions api-participant/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,42 @@ dependencies {
}

task copyWebApp(type: Copy) {
dependsOn(rootProject.bundleParticipantUI)
from "$rootDir/ui-participant/build"
into "$rootDir/api-participant/build/resources/main/static"
}

// for now, only jib depends on copyWebApp, so that a npm rebuild/install will not be triggered for
// development redeploys. this means to deploy locally with the static assets in place you'll
// need to run the copyWebApp task yourself
copyWebApp.dependsOn(rootProject.bundleParticipantUI)
jibDockerBuild.dependsOn('copyWebApp')
// creates copies of the fingerprinted js files without the asset fingerprint.
task createUnfingerprintedJs(type: Copy) {
dependsOn('copyWebApp')
dependsOn('processResources')
from "$rootDir/api-participant/build/resources/main/static/static/js"
into "$rootDir/api-participant/build/resources/main/static/static/js"
rename("main.([0-9a-f]{8}).js", "main.js")
rename('(.*).([0-9a-f]{8}).chunk.js', '$1.chunk.js')
}

// creates copies of the fingerprinted CSS files without the asset fingerprint
task createUnfingerprintedCss(type: Copy) {
dependsOn('copyWebApp')
dependsOn('processResources')
from "$rootDir/api-participant/build/resources/main/static/static/css"
into "$rootDir/api-participant/build/resources/main/static/static/css"
rename("main.*.css", "main.css")
}

// See comment in PublicApiController.java for why we want unfingerprinted versions of assets. We still keep the fingerprinted
// versions for reference to help in determining which version of a file the server *actually* has
task createUnfingerprintedAssets() {
dependsOn('createUnfingerprintedJs')
dependsOn('createUnfingerprintedCss')
}

// for now, only jib depends on copyWebApp (via createUnfingerprintedAssets),
// so that a npm rebuild/install will not be triggered for development redeploys.
// this means to deploy locally with the static assets in place you'll
// need to run the copyWebApp task yourself from the repo root with ./gradlew api-participant:copyWebApp
jibDockerBuild.dependsOn('createUnfingerprintedAssets')

test {
useJUnitPlatform ()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import bio.terra.pearl.core.model.site.SiteImage;
import bio.terra.pearl.core.service.portal.PortalService;
import bio.terra.pearl.core.service.site.SiteImageService;
import java.nio.file.*;
import java.util.Map;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
Expand All @@ -25,6 +26,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Controller
public class PublicApiController implements PublicApi {
Expand Down Expand Up @@ -149,6 +151,37 @@ public String getIndex(HttpServletRequest request) {
return "forward:/";
}

/**
* DANGER: The three methods below essentially tell the server to ignore the fingerprint of the
* asset requested, and always just return the asset it has. This enables us to do rolling
* deployments and not have the case where a request for an asset from an old pod gets served by a
* new pod which has a different asset fingerprint, and thus returns 404. This obviously opens the
* door for obscure bugs relating to a user having different versions of different frontend assets
* on the same page. This is reasonably safe for us now, though, since main.js is typically the
* only asset that changes between versions -- the majority of our css is inlined, and our JS
* chunks are for things like the privacy policy that are rarely used/updated. And the
* fingerprints are still included in index.html, so the fingerprints still do their job of
* preventing unwanted browser caching.
*
* <p>We're willing to temporarily accept the risk of possibly odd behavior in exchange for the
* site not appearing as down during deploys. Eventually, we should upgrade our deployment/hosting
* infrastructure to solve this problem in a more robust way
*/
@GetMapping(value = "/static/js/main.{hash}.js")
public String getFingerprintedJs() {
return "forward:/static/js/main.js";
}

@GetMapping(value = "/static/css/main.{hash}.css")
public String getFingerprintedCss() {
return "forward:/static/css/main.css";
}

@GetMapping(value = "/static/js/{chunkId}.{hash}.chunk.js")
public String getFingerprintedJsChunks(@PathVariable("chunkId") String chunkId) {
return "forward:/static/js/%s.chunk.js".formatted(chunkId);
}

private Optional<PortalEnvironmentDescriptor> getPortalDescriptorForRequest(
HttpServletRequest request) {
String hostname = request.getServerName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,27 @@ void testForwarding() throws Exception {
this.mockMvc.perform(get("/hearthive")).andExpect(forwardedUrl("/"));
}

@Test
void testHandlesMismatchedJSFingerprint() throws Exception {
this.mockMvc
.perform(get("/static/js/main.12345678.js"))
.andExpect(forwardedUrl("/static/js/main.js"));
}

@Test
void testHandlesMismatchedCSSFingerprint() throws Exception {
this.mockMvc
.perform(get("/static/css/main.12345678.css"))
.andExpect(forwardedUrl("/static/css/main.css"));
}

@Test
void testHandlesMismatchedJsChunkFingerprint() throws Exception {
this.mockMvc
.perform(get("/static/js/111.12345678.chunk.js"))
.andExpect(forwardedUrl("/static/js/111.chunk.js"));
}

@Test
void testResourceGets() throws Exception {
// confirm image paths are not forwarded to index
Expand Down

0 comments on commit 2f91923

Please sign in to comment.