Skip to content

Commit

Permalink
feat: parent image cache
Browse files Browse the repository at this point in the history
  • Loading branch information
aldor007 committed Oct 21, 2022
1 parent d2417c0 commit b5a80e4
Show file tree
Hide file tree
Showing 9 changed files with 2,799 additions and 19 deletions.
2,659 changes: 2,647 additions & 12 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@
"@semantic-release/commit-analyzer": "^9.0.2",
"@semantic-release/git": "^10.0.1",
"@semantic-release/release-notes-generator": "10.0.3",
"async": "^3.2.4",
"aws-sdk": "^2.814.0",
"axios": "^0.26.1",
"axios-retry": "3.2.4",
"chai": "^4.2.0",
"chai-like": "^1.1.1",
"chai-things": "^0.2.0",
"mocha": "^5.2.0",
"moment": "^2.29.4",
"request": "^2.88.0",
"axios": "^0.26.1",
"axios-retry": "3.2.4",
"superagent": "^7",
"superagent-binary-parser": "^1.0.1",
"supertest": "^6.2.2"
Expand Down
2 changes: 1 addition & 1 deletion pkg/cache/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (c *MemoryCache) Get(obj *object.FileObject) (*response.Response, error) {
monitoring.Report().Inc("cache_ratio;status:miss")
return nil, errors.New("not found")
}
resCp.Set("x-mort-cache", "hit")
resCp.SetCacheHit()
return resCp, nil
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/cache/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func (c *RedisCache) Get(obj *object.FileObject) (*response.Response, error) {
monitoring.Report().Inc("cache_ratio;status:hit")
err = msgpack.Unmarshal(buf, &res)
if res.Headers != nil {
res.Set("x-mort-cache", "hit")
res.SetCacheHit()
}
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/object/file_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ func (o *FileObject) FillWithRequest(req *http.Request, ctx context.Context) {
}
}

parent := o.Parent
for parent != nil {
parent.Ctx = ctx
parent = parent.Parent
}

}

func (o *FileObject) GetResponseCacheKey() string {
Expand Down
25 changes: 23 additions & 2 deletions pkg/processor/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func (r *RequestProcessor) process(req *http.Request, obj *object.FileObject) *r
res = updateHeaders(obj, r.handleGET(req, obj))
}

if res.IsCacheable() && res.ContentLength != -1 && res.ContentLength < r.serverConfig.Cache.MaxCacheItemSize {
if !res.IsFromCache() && res.IsCacheable() && res.ContentLength != -1 && res.ContentLength < r.serverConfig.Cache.MaxCacheItemSize {
resCpy, err := res.Copy()
objCpy := obj.Copy()
if err == nil {
Expand Down Expand Up @@ -388,8 +388,29 @@ func (r *RequestProcessor) handleNotFound(obj, parentObj *object.FileObject, tra
if parentRes.StatusCode != 200 || !parentRes.IsImage() {
return res
}
parentRes = storage.Get(parentObj)
if cacheRes, errCache := r.responseCache.Get(parentObj); errCache == nil {
parentRes = cacheRes
} else {
parentRes = storage.Get(parentObj)
}

if obj.HasTransform() {
if !parentRes.IsFromCache() {
copyParentRes, err := parentRes.Copy()
if err != nil {
monitoring.Log().Warn("unable to copy qobject", parentObj.LogData(zap.Error(err))...)
} else {
updateHeaders(parentObj, copyParentRes)
go func() {
copyParentRes.Body()
defer copyParentRes.Close()
err := r.responseCache.Set(parentObj, copyParentRes)
if err != nil {
monitoring.Log().Warn("unable to set parent in cache", parentObj.LogData(zap.Error(err))...)
}
}()
}
}
// processImage returns new response so both parentRes must be closed
defer parentRes.Close()
return r.processImage(obj, parentRes, transformsTab)
Expand Down
8 changes: 8 additions & 0 deletions pkg/response/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,14 @@ func (r *Response) IsCacheable() bool {
return r.StatusCode > 199 && r.StatusCode < 299 && r.cachable
}

func (r *Response) IsFromCache() bool {
return r.Headers.Get("x-mort-cache") != ""
}

func (r *Response) SetCacheHit() {
r.Headers.Set("x-mort-cache", "hit")
}

func (r *Response) GetTTL() int {
r.parseCacheHeaders()
return r.ttl
Expand Down
109 changes: 109 additions & 0 deletions tests-int/Cache.Spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
const supertest = require('supertest');
const chai = require('chai');
const expect = chai.expect;
const async = require('async')

const host = process.env.MORT_HOST + ':' + + process.env.MORT_PORT;
const request = supertest(`http://${host}`);

function checkImage(reqPath, width, height, sizeRange, done) {
request.get(reqPath)
.expect(200)
.end(function(err, res) {
if (err) {
return done(err);
}

const body = res.body;
expect(body.length).to.be.within(...sizeRange);

expect(res.headers['x-amz-meta-public-width']).to.eql(width);
expect(res.headers['x-amz-meta-public-height']).to.eql(height);
request.get(reqPath)
.expect(200)
.end(function (err2, res2) {
if (err2) {
return done(err2);
}


expect(res2.headers['x-amz-meta-public-width']).to.eql(width);
expect(res2.headers['x-amz-meta-public-height']).to.eql(height);

const body2 = res2.body;
expect(body2.length).to.eql(body.length);
done(err2)
});
});
}


describe('Image processing - cache', function () {

it('should create thumbnails and store in cache', function (done) {
this.timeout(5000);
async.Seriesk
const reqPath = '/remote/nxpvwo7qqfwz.jpg?operation=resize&width=300&operation=rotate&angle=90&format=webp';
const width = '300';
const height = '240';
const sizeRange = [5100, 8100]
async.parallel([
(cb) => checkImage(reqPath, width, height, sizeRange, cb),
(cb) => checkImage(reqPath, width, height, sizeRange, cb),
(cb) => checkImage(reqPath, width, height, sizeRange, cb),
(cb) => checkImage(reqPath, width, height, sizeRange, cb),
(cb) => checkImage(reqPath, width, height, sizeRange, cb),
(cb) => checkImage(reqPath, width, height, sizeRange, cb),
(cb) => checkImage(reqPath, width, height, sizeRange, cb),
(cb) => checkImage(reqPath, width, height, sizeRange, cb),
(cb) => checkImage(reqPath, width, height, sizeRange, cb),
], (err) => {
if (err) {
return done(err)
}
request.get(reqPath)
.expect(200)
.end(function(err2, res) {
if (err) {
return done(err2);
}

expect(res.headers['x-mort-cache']).to.eql('hit');
done(err)
})
})
});

it('should create thumbnails with watermark and cache', function (done) {
this.timeout(5000);
const reqPath = '/remote/nxpvwo7qqfwz.jpg?operation=resize&width=400&height=200&image=https://i.imgur.com/uomkVIL.png&opacity=0.5&position=top-left&operation=watermark';
const width = '400';
const height = '200';
async.parallel([
(cb) => checkImage(reqPath, width, height, [5645, 18300], cb),
(cb) => checkImage(reqPath, width, height, [5645, 18300], cb),
(cb) => checkImage(reqPath, width, height, [5645, 18300], cb),
(cb) => checkImage(reqPath, width, height, [5645, 18300], cb),
(cb) => checkImage(reqPath, width, height, [5645, 18300], cb),
(cb) => checkImage(reqPath, width, height, [5645, 18300], cb),
(cb) => checkImage(reqPath, width, height, [5645, 18300], cb),
(cb) => checkImage(reqPath, width, height, [5645, 18300], cb),
(cb) => checkImage(reqPath, width, height, [5645, 18300], cb),
], (err) => {
if (err) {
return done(err)
}
request.get(reqPath)
.expect(200)
.end(function(err2, res) {
if (err) {
return done(err2);
}

expect(res.headers['x-mort-cache']).to.eql('hit');
done(err)
})
})
});

});
2 changes: 1 addition & 1 deletion tests-int/mort-redis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
server:
listen: ":8091"
accessLogs: true
accessLogs: false
cache:
type: "redis"
address:
Expand Down

0 comments on commit b5a80e4

Please sign in to comment.