코드 뜯어보기 - 재미

아래는 재미삼아 코드 뜯어보면서 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를 사용할 예정이므로 FileStoregetOffset 함수를 찾아보았습니다.

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를 읽어서 가져온다는 것을 확인할 수 있었습니다.