Merge pull request #1465 from ONLYOFFICE/feature/cdn-for-dynamic-content
s3: added support presigned cdn url for Media/Image viewer
This commit is contained in:
commit
083cf7e049
@ -30,6 +30,7 @@ public abstract class BaseStorage : IDataStore
|
||||
{
|
||||
public IQuotaController QuotaController { get; set; }
|
||||
public virtual bool IsSupportInternalUri => true;
|
||||
public virtual bool IsSupportCdnUri => false;
|
||||
public virtual bool IsSupportedPreSignedUri => true;
|
||||
public virtual bool IsSupportChunking => false;
|
||||
internal string Modulename { get; set; }
|
||||
@ -154,6 +155,11 @@ public abstract class BaseStorage : IDataStore
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual Task<Uri> GetCdnPreSignedUriAsync(string domain, string path, TimeSpan expire, IEnumerable<string> headers)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public abstract Task<Stream> GetReadStreamAsync(string domain, string path);
|
||||
|
||||
public abstract Task<Stream> GetReadStreamAsync(string domain, string path, long offset);
|
||||
|
@ -63,12 +63,20 @@ public interface IDataStore
|
||||
/// <param name="headers"></param>
|
||||
/// <returns></returns>
|
||||
Task<Uri> GetPreSignedUriAsync(string domain, string path, TimeSpan expire, IEnumerable<string> headers);
|
||||
|
||||
///<summary>
|
||||
/// Supporting generate uri to the file
|
||||
///</summary>
|
||||
///<returns></returns>
|
||||
bool IsSupportInternalUri { get; }
|
||||
|
||||
///<summary>
|
||||
/// Supporting generate uri to the file
|
||||
///</summary>
|
||||
///<returns></returns>
|
||||
bool IsSupportCdnUri { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get absolute Uri for html links
|
||||
/// </summary>
|
||||
@ -79,6 +87,16 @@ public interface IDataStore
|
||||
/// <returns></returns>
|
||||
Task<Uri> GetInternalUriAsync(string domain, string path, TimeSpan expire, IEnumerable<string> headers);
|
||||
|
||||
/// <summary>
|
||||
/// Get absolute Uri via CDN for html links
|
||||
/// </summary>
|
||||
/// <param name="domain"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="expire"></param>
|
||||
/// <param name="headers"></param>
|
||||
/// <returns></returns>
|
||||
Task<Uri> GetCdnPreSignedUriAsync(string domain, string path, TimeSpan expire, IEnumerable<string> headers);
|
||||
|
||||
///<summary>
|
||||
/// A stream of read-only. In the case of the C3 stream NetworkStream general, and with him we have to work
|
||||
/// Very carefully as a Jedi cutter groin lightsaber.
|
||||
|
@ -33,6 +33,7 @@ namespace ASC.Data.Storage.S3;
|
||||
[Scope]
|
||||
public class S3Storage : BaseStorage
|
||||
{
|
||||
public override bool IsSupportCdnUri => true;
|
||||
public override bool IsSupportChunking => true;
|
||||
|
||||
private readonly List<string> _domains = new List<string>();
|
||||
@ -50,8 +51,10 @@ public class S3Storage : BaseStorage
|
||||
private readonly ServerSideEncryptionMethod _sse = ServerSideEncryptionMethod.AES256;
|
||||
private bool _useHttp = true;
|
||||
private bool _lowerCasing = true;
|
||||
private bool _revalidateCloudFront;
|
||||
private string _distributionId = "";
|
||||
private bool _cdnEnabled;
|
||||
private string _cdnKeyPairId;
|
||||
private string _cdnPrivateKeyPath;
|
||||
private string _cdnDistributionDomain;
|
||||
private string _subDir = "";
|
||||
|
||||
private EncryptionMethod _encryptionMethod = EncryptionMethod.None;
|
||||
@ -145,9 +148,80 @@ public class S3Storage : BaseStorage
|
||||
}
|
||||
|
||||
using var client = GetClient();
|
||||
|
||||
return Task.FromResult(MakeUri(client.GetPreSignedURL(pUrlRequest)));
|
||||
}
|
||||
|
||||
public override Task<Uri> GetCdnPreSignedUriAsync(string domain, string path, TimeSpan expire, IEnumerable<string> headers)
|
||||
{
|
||||
if (!_cdnEnabled) return GetInternalUriAsync(domain, path, expire, headers);
|
||||
|
||||
var proto = SecureHelper.IsSecure(_httpContextAccessor?.HttpContext, _options) ? "https" : "http";
|
||||
|
||||
var baseUrl = $"{proto}://{_cdnDistributionDomain}/{MakePath(domain, path)}";
|
||||
|
||||
var uriBuilder = new UriBuilder(baseUrl)
|
||||
{
|
||||
Port = -1
|
||||
};
|
||||
|
||||
var queryParams = HttpUtility.ParseQueryString(uriBuilder.Query);
|
||||
|
||||
if (headers != null && headers.Any())
|
||||
{
|
||||
foreach (var h in headers)
|
||||
{
|
||||
if (h.StartsWith("Content-Disposition"))
|
||||
{
|
||||
queryParams["response-content-disposition"] = h.Substring("Content-Disposition".Length + 1);
|
||||
}
|
||||
else if (h.StartsWith("Cache-Control"))
|
||||
{
|
||||
queryParams["response-cache-control"] = h.Substring("Cache-Control".Length + 1);
|
||||
}
|
||||
else if (h.StartsWith("Content-Encoding"))
|
||||
{
|
||||
queryParams["response-content-encoding"] = h.Substring("Content-Encoding".Length + 1);
|
||||
}
|
||||
else if (h.StartsWith("Content-Language"))
|
||||
{
|
||||
queryParams["response-content-language"] = h.Substring("Content-Language".Length + 1);
|
||||
}
|
||||
else if (h.StartsWith("Content-Type"))
|
||||
{
|
||||
queryParams["response-content-type"] = h.Substring("Content-Type".Length + 1);
|
||||
}
|
||||
else if (h.StartsWith("Expires"))
|
||||
{
|
||||
queryParams["response-expires"] = h.Substring("Expires".Length + 1);
|
||||
}
|
||||
else if (h.StartsWith("Custom-Cache-Key"))
|
||||
{
|
||||
queryParams["custom-cache-key"] = h.Substring("Custom-Cache-Key".Length + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FormatException(string.Format("Invalid header: {0}", h));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uriBuilder.Query = queryParams.ToString();
|
||||
|
||||
var signedUrl = "";
|
||||
|
||||
using (TextReader textReader = File.OpenText(_cdnPrivateKeyPath))
|
||||
{
|
||||
signedUrl = AmazonCloudFrontUrlSigner.GetCannedSignedURL(
|
||||
uriBuilder.ToString(),
|
||||
textReader,
|
||||
_cdnKeyPairId,
|
||||
DateTime.UtcNow.Add(expire));
|
||||
}
|
||||
|
||||
return Task.FromResult(new Uri(signedUrl));
|
||||
}
|
||||
|
||||
public override Task<Stream> GetReadStreamAsync(string domain, string path)
|
||||
{
|
||||
return GetReadStreamAsync(domain, path, 0);
|
||||
@ -217,12 +291,7 @@ public class S3Storage : BaseStorage
|
||||
ContentType = mime,
|
||||
ServerSideEncryptionMethod = _sse,
|
||||
InputStream = buffered,
|
||||
AutoCloseStream = false,
|
||||
Headers =
|
||||
{
|
||||
CacheControl = string.Format("public, maxage={0}", (int)TimeSpan.FromDays(cacheDays).TotalSeconds),
|
||||
ExpiresUtc = DateTime.UtcNow.Add(TimeSpan.FromDays(cacheDays))
|
||||
}
|
||||
AutoCloseStream = false
|
||||
};
|
||||
|
||||
if (!(client is IAmazonS3Encryption))
|
||||
@ -259,7 +328,7 @@ public class S3Storage : BaseStorage
|
||||
|
||||
await uploader.UploadAsync(request);
|
||||
|
||||
await InvalidateCloudFrontAsync(MakePath(domain, path));
|
||||
//await InvalidateCloudFrontAsync(MakePath(domain, path));
|
||||
|
||||
await QuotaUsedAdd(domain, buffered.Length);
|
||||
|
||||
@ -348,7 +417,7 @@ public class S3Storage : BaseStorage
|
||||
using (var s3 = GetClient())
|
||||
{
|
||||
await s3.CompleteMultipartUploadAsync(request);
|
||||
await InvalidateCloudFrontAsync(MakePath(domain, path));
|
||||
// await InvalidateCloudFrontAsync(MakePath(domain, path));
|
||||
}
|
||||
|
||||
if (QuotaController != null)
|
||||
@ -969,12 +1038,17 @@ public class S3Storage : BaseStorage
|
||||
{
|
||||
bool.TryParse(lower, out _lowerCasing);
|
||||
}
|
||||
if (props.TryGetValue("cloudfront", out var front))
|
||||
|
||||
if (props.TryGetValue("cdn_enabled", out var cdnEnabled))
|
||||
{
|
||||
bool.TryParse(front, out _revalidateCloudFront);
|
||||
if (bool.TryParse(cdnEnabled, out _cdnEnabled))
|
||||
{
|
||||
_cdnKeyPairId = props["cdn_keyPairId"];
|
||||
_cdnPrivateKeyPath = props["cdn_privateKeyPath"];
|
||||
_cdnDistributionDomain = props["cdn_distributionDomain"];
|
||||
}
|
||||
}
|
||||
|
||||
props.TryGetValue("distribution", out _distributionId);
|
||||
props.TryGetValue("subdir", out _subDir);
|
||||
|
||||
return this;
|
||||
@ -1026,10 +1100,10 @@ public class S3Storage : BaseStorage
|
||||
|
||||
return new UnencodedUri(baseUri, signedPart);
|
||||
}
|
||||
|
||||
|
||||
private Task InvalidateCloudFrontAsync(params string[] paths)
|
||||
{
|
||||
if (!_revalidateCloudFront || string.IsNullOrEmpty(_distributionId))
|
||||
if (!_cdnEnabled || string.IsNullOrEmpty(_cdnDistributionDomain))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@ -1042,7 +1116,7 @@ public class S3Storage : BaseStorage
|
||||
using var cfClient = GetCloudFrontClient();
|
||||
var invalidationRequest = new CreateInvalidationRequest
|
||||
{
|
||||
DistributionId = _distributionId,
|
||||
DistributionId = _cdnDistributionDomain,
|
||||
InvalidationBatch = new InvalidationBatch
|
||||
{
|
||||
CallerReference = Guid.NewGuid().ToString(),
|
||||
|
@ -48,11 +48,13 @@ internal class FileDao : AbstractDao, IFileDao<int>
|
||||
private readonly IQuotaService _quotaService;
|
||||
private readonly StorageFactory _storageFactory;
|
||||
private readonly TenantQuotaController _tenantQuotaController;
|
||||
private readonly FileUtility _fileUtility;
|
||||
|
||||
public FileDao(
|
||||
ILogger<FileDao> logger,
|
||||
FactoryIndexerFile factoryIndexer,
|
||||
UserManager userManager,
|
||||
FileUtility fileUtility,
|
||||
IDbContextFactory<FilesDbContext> dbContextManager,
|
||||
TenantManager tenantManager,
|
||||
TenantUtil tenantUtil,
|
||||
@ -108,6 +110,7 @@ internal class FileDao : AbstractDao, IFileDao<int>
|
||||
_quotaService = quotaService;
|
||||
_storageFactory = storageFactory;
|
||||
_tenantQuotaController = tenantQuotaController;
|
||||
_fileUtility = fileUtility;
|
||||
}
|
||||
|
||||
public Task InvalidateCacheAsync(int fileId)
|
||||
@ -377,6 +380,19 @@ internal class FileDao : AbstractDao, IFileDao<int>
|
||||
|
||||
public Task<Uri> GetPreSignedUriAsync(File<int> file, TimeSpan expires)
|
||||
{
|
||||
var storage = _globalStore.GetStore();
|
||||
|
||||
if (storage.IsSupportCdnUri && !_fileUtility.CanWebEdit(file.Title)
|
||||
&& (_fileUtility.CanMediaView(file.Title) || _fileUtility.CanImageView(file.Title)))
|
||||
{
|
||||
return _globalStore.GetStore().GetCdnPreSignedUriAsync(string.Empty, GetUniqFilePath(file), expires,
|
||||
new List<string>
|
||||
{
|
||||
$"Content-Disposition:{ContentDispositionUtil.GetHeaderValue(file.Title, withoutBase: true)}",
|
||||
$"Custom-Cache-Key:{file.ModifiedOn.Ticks}"
|
||||
});
|
||||
}
|
||||
|
||||
return _globalStore.GetStore().GetPreSignedUriAsync(string.Empty, GetUniqFilePath(file), expires,
|
||||
new List<string>
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user