아래는 재미삼아 코드 뜯어보면서 OFFSET을 어떻게 가져오는지를 확인하는 과정을 담았습니다.
offset 저장 경로 확인을 위해서 코드 뜯어보기
offset 정보는 HEAD 요청을 통해 얻을 수 있습니다 (공식문서).
https://tus.io/faq#how-does-tus-work
따라서 Head 요청을 담당하는 파일을 찾아보았습니다.
tus-node-server/lib/handlers/HeadHandler.js
class HeadHandler extends BaseHandler {
...
async send(req, res) {
const file_id = this.getFileIdFromRequest(req);
if (file_id === false) {
throw ERRORS.FILE_NOT_FOUND;
}
// @@ 여기가 Offset 얻는 함수 @@
**const file = await this.store.getOffset(file_id);**
...
}
...
}
보면 this.store.getOffset 에서 offset을 추출해온다는 걸 알 수 있습니다.
해당 클래스에는 store가 없으므로, 부모 클래스 BaseHandler를 살펴보았습니다.
tus-node-server/lib/handlers/BaseHandler.js
class BaseHandler extends EventEmitter {
constructor(store, options) {
super();
if (!store) {
throw new Error('Store must be defined');
}
this.store = store;
this.options = options;
}
...
}
store를 등록해서 일괄적으로 상속받아 사용하는 구조인 것 같습니다. (이를 보면서 추상화가 잘 되어있었고 제 프로젝트 또한 추상화를 잘 시켜두어야겠다는 생각이 들었습니다)
BaseHandler에 아래와 같이 store를 등록하게 됩니다.
class TusServer extends EventEmitter {
...
set datastore(store) {
this._datastore = store;
this.handlers = {
// GET handlers should be written in the implementations
// eg.
// const server = new tus.Server();
// server.get('/', (req, res) => { ... });
GET: new GetHandler(store, this.options),
// These methods are handled under the tus protocol
HEAD: new HeadHandler(store, this.options),
OPTIONS: new OptionsHandler(store, this.options),
PATCH: new PatchHandler(store, this.options),
POST: new PostHandler(store, this.options),
DELETE: new DeleteHandler(store, this.options),
};
}
...
}
즉, datastore에 저장하면 자동으로 Handler에 등록이 되는 구조입니다.
따라서 등록되어있는 dataStore에 따라서 getOffset 함수가 달라지는 것을 알 수 있고, 제 프로젝트에서는 FileStore를 사용할 예정이므로 FileStore의 getOffset 함수를 찾아보았습니다.
tus-node-server/lib/stores/FileStore.js 코드입니다.
// tus-node-server/lib/stores/FileStore.js
class FileStore extends DataStore {
/**
* Return file stats, if they exits
*
* @param {string} file_id name of the file
* @return {object} fs stats
*/
async getOffset(file_id) {
const config = await this.configstore.get(file_id);
return new Promise((resolve, reject) => {
const file_path = `${this.directory}/${file_id}`;
fs.stat(file_path, (error, stats) => {
if (error && error.code === FILE_DOESNT_EXIST && config) {
log(`[FileStore] getOffset: No file found at ${file_path} but db record exists`, config);
return reject(ERRORS.FILE_NO_LONGER_EXISTS);
}
if (error && error.code === FILE_DOESNT_EXIST) {
log(`[FileStore] getOffset: No file found at ${file_path}`);
return reject(ERRORS.FILE_NOT_FOUND);
}
if (error) {
return reject(error);
}
if (stats.isDirectory()) {
log(`[FileStore] getOffset: ${file_path} is a directory`);
return reject(ERRORS.FILE_NOT_FOUND);
}
**// @@ 여기 반환 값 @@
config.size = stats.size;**
**return resolve(config);**
});
});
}
}
여기 보면 offset(size) 값이 stats.size에서 오는 것을 볼 수 있습니다. fs.stat()의 파라미터인데, 파일시스템에서 해당 파일에 대한 Metadata를 직접 읽어서 가져오고 있습니다. (파일의 크기를 가져와서, 해당 크기 이후로 데이터를 쓰는 구조인 것 같습니다.) ****
즉, .info 파일을 읽어서 가져오는 것이 아니라, 저장되어있는 파일의 metadata를 읽어서 가져오는 것 입니다.
위 과정을 통해서 offset은 현재 OS 자체 파일에 저장되어있는 metadata를 읽어서 가져온다는 것을 확인할 수 있었습니다.