【问题标题】:How to make browser cache identical image with different aws s3 presigned url?如何使用不同的 aws s3 预签名 url 使浏览器缓存相同的图像?
【发布时间】:2019-04-11 19:36:52
【问题描述】:

我为我的用户生成了与此类似的 url,以从我的 aws s3 存储桶中检索图像文件:

https://resource.my-company.com/tYERrR13341/q1/something.jpg?response-expires=Thu%2C%2008%20Nov%202018%2007%3A26%3A21%20GMT&AWSAccessKeyId=TTKIAJJBATJ89740989&Expires=1541661981&Signature=J49ebmKMdZ%2FZqwupfaD39f9e716831

有时,用户可能会刷新页面,并且指向同一资源的 url 会获得一组新的 ExpiresSignature 值。

浏览器会将这两个url视为不同的两个对象,会再次尝试从s3 bucket下载资源。

这会导致一些性能问题。是否有可能让浏览器意识到,尽管 url 的参数部分有所不同,但用户正在尝试检索相同的资源并因此从其本地缓存中检索它?

【问题讨论】:

标签: amazon-web-services amazon-s3 pre-signed-url


【解决方案1】:

您不需要放置任何redis 缓存逻辑或一些复杂的逻辑来帮助缓存预签名 URL。

要强制浏览器使用缓存,我们只需使多个请求的 URL 相同。在我们的例子中,同一个文件的预签名 URL 只会在其签名上有所不同。所以我们只需要使签名相同。

并且根据到期时间生成签名。

因此,为了使签名相同,从而使 URL 相同,您可以在特定时间窗口内为同一文件请求的所有预签名 URL 生成请求传递相同的到期时间。

假设,如果我们为 file A 的所有预签名调用传递相同的过期时间 10 分钟,那么所有这些 URL 将具有相同的签名(以及相同的 URL),因此将被缓存。

【讨论】:

    【解决方案2】:

    我遇到了同样的问题,并通过缓存图像预签名网址链接解决了这个问题,这将解决问题。

    当您从 AWS 请求一个预签名的 url 时,它会返回一个带有过期和签名的链接,该链接指向您存储桶中的源文件,每次您进行刷新时:服务器会将请求传递给 AWS,然后检索一个浏览器一无所知的新过期和签名,因此它将再次获取相同的图像。

    告诉浏览器不要再获取它的方法是在刷新页面时发回相同的签名!这听起来很简单,但它可能会变得一团糟,所以请耐心等待。

    我提出的解决方案是使用 "Redis" 缓存层,将浏览器加载的图像映射到串行。

    如果 userA 加载该页面,您只需检查它是否曾被同一用户和同一浏览器和 MacAddress/IP 使用“串行”访问过,您应该将其添加到浏览器的本地存储中,

    serial structure = (browser-type&name + userID + MacAddress/IP),这是为了确保用户A同时从不同的设备和浏览器登录时它可以工作,并确保每次 userA 从该特定浏览器和设备加载页面/图像时,您都会创建相同的序列号。

    如果用户A没有序列号保存到本地存储,则将其添加到本地存储并去检查序列是否存在于Redis中(用户可能之前登录并清除了浏览器缓存) ,如果是,则删除包含图像的预签名链接作为参数的附加对象:

    "serial" : 
          
    { 
           
             imageID_1 : "https://s3.bucket.xxx/imageID_1.jpg?.......xyz1",
    
             imageID_2 : "https://s3.bucket.xxx/imageID_2.jpg?.......xyz2",
    
             imageID_3 : "https://s3.bucket.xxx/imageID_3.jpg?.......xyz3"
    
     }
    

    然后为必须在页面上加载的每个 imageID/key 请求新的预签名 url,并将它们附加到 Redis 中的“串行”对象参数。如果没有,那么只需添加两个“串行”+对象参数:

    "serial" : 
          
    { 
           
             imageID_1 : "https://s3.bucket.xxx/imageID_1.jpg?.......abc1",
    
             imageID_2 : "https://s3.bucket.xxx/imageID_2.jpg?.......abc2",
    
             imageID_3 : "https://s3.bucket.xxx/imageID_3.jpg?.......abc3"
    
     }
    

    如果浏览器有一个缓存到本地存储的串行,您还必须检查每个请求是否存在与串行链接的对象参数中的 imageID 为了为其创建 presigned-url(如果它不存在),然后将其作为新参数添加到对象中:

    "serial" : 
          
    { 
           
             imageID_1 : "https://s3.bucket.xxx/imageID_1.jpg?.......abc1",
    
             imageID_2 : "https://s3.bucket.xxx/imageID_2.jpg?.......abc2",
    
             imageID_3 : "https://s3.bucket.xxx/imageID_3.jpg?.......abc3",
    
             imageID_4 : "https://s3.bucket.xxx/imageID_4.jpg?.......abc4",
    
    
     }
    

    如果用户已经将序列号保存到浏览器本地存储中,说明之前有一些加载的图像,所以我们必须检查用户A是否请求之前加载的相同图像,或者他正在请求新的图像。

    最后,您必须检查是否由于任何原因未加载或禁止从浏览器访问图像(用于检查并向服务器报告的字体端代码),在这种情况下,您只需删除串行附加对象并附加新的预签名 URL,然后将它们发送回前端(浏览器)。

    【讨论】:

      【解决方案3】:

      要使用 AWS Java SDK 2 执行此操作,您需要一个自定义的 Signer 实现,该实现也实现了 Presigner。这是我为执行委托给 AwsS3V4Signer 的工作而创建的一个,它执行所需的计算并且是一个公共 API。

      import org.springframework.stereotype.Component;
      import software.amazon.awssdk.auth.signer.AwsS3V4Signer;
      import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute;
      import software.amazon.awssdk.auth.signer.params.Aws4PresignerParams;
      import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
      import software.amazon.awssdk.core.signer.Presigner;
      import software.amazon.awssdk.core.signer.Signer;
      import software.amazon.awssdk.http.SdkHttpFullRequest;
      
      import java.time.Clock;
      import java.time.Instant;
      import java.time.ZoneId;
      import java.time.temporal.ChronoUnit;
      
      @Component
      public class CustomAwsSigner implements Signer, Presigner {
          private final AwsS3V4Signer theSigner = AwsS3V4Signer.create();
      
          @Override
          public SdkHttpFullRequest presign(final SdkHttpFullRequest request, final ExecutionAttributes executionAttributes) {
      
              Instant baselineInstant = Instant.now().truncatedTo(ChronoUnit.HOURS);
      
              final Aws4PresignerParams signingParams = Aws4PresignerParams.builder()
                  .awsCredentials(executionAttributes.getAttribute(AwsSignerExecutionAttribute.AWS_CREDENTIALS))
                  .signingName(executionAttributes.getAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME))
                  .signingRegion(executionAttributes.getAttribute(AwsSignerExecutionAttribute.SIGNING_REGION))
                  .signingClockOverride(Clock.fixed(baselineInstant, ZoneId.of("UTC")))
                  .expirationTime(baselineInstant.plus(1, ChronoUnit.HOURS))
                  .build();
              return theSigner.presign(request, signingParams);
      
          }
      
          @Override
          public SdkHttpFullRequest sign(final SdkHttpFullRequest request, final ExecutionAttributes executionAttributes) {
      
              throw new UnsupportedOperationException("this class is only used for presigning");
      
          }
      }
      
      

      获取自动接线所需的组件。

      @Configuration
      public class S3ClientConfiguration {
      
          @Bean
          public S3Client amazonS3Client(final AwsCredentialsProvider credentialsProvider, AwsRegionProvider regionProvider) {
      
              return S3Client.builder()
                  .credentialsProvider(credentialsProvider)
                  .region(regionProvider.getRegion())
                  .build();
      
          }
      
          @Bean
          public AwsCredentialsProvider credentialsProvider() {
      
              return DefaultCredentialsProvider.create();
          }
      
          @Bean
          public S3Presigner presigner(final AwsCredentialsProvider credentialsProvider, AwsRegionProvider regionProvider) {
      
              return S3Presigner.builder()
                  .credentialsProvider(credentialsProvider)
                  .region(regionProvider.getRegion())
                  .build();
      
          }
      
          @Bean
          public AwsRegionProvider regionProvider() {
      
              return DefaultAwsRegionProviderChain.builder().build();
      
          }
      
      
      }
      

      获取过期网址

      @Autowired
      private S3Presigner s3Presigner;
      
      /**
       * Convert an S3 URI to a normal HTTPS URI that expires.
       *
       * @param s3Uri S3 URI (e.g. s3://mybucketname/ArchieTest/フェニックス.jpg)
       * @return https URI
       */
      @SneakyThrows
      public URI getExpiringUri(final URI s3Uri) {
      
          final GetObjectRequest getObjectRequest =
              GetObjectRequest.builder()
                  .bucket(s3Uri.getHost())
                  .key(s3Uri.getPath().substring(1))
                  .overrideConfiguration(builder -> {
                      builder.signer(awsSigner);
                  })
                  .build();
      
          final GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest.builder()
              .signatureDuration(Duration.ofSeconds(0)) // required, but ignored
              .getObjectRequest(getObjectRequest)
              .build();
      
          return s3Presigner.presignGetObject(getObjectPresignRequest).url().toURI();
      
      }
      

      【讨论】:

        【解决方案4】:

        如果您有机会修改服务器端应用程序,您可以创建授权标头而不是创建预签名的 url。

        见:5219165

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-08-19
          • 2014-04-27
          • 1970-01-01
          • 2021-12-03
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多