@sap/odata-v4
Version:
OData V4.0 server library
239 lines (203 loc) • 7.22 kB
JavaScript
'use strict';
const Transform = require('stream').Transform; // transform catches eos and _flush gets called
const ApplicationHttpReader = require('./ApplicationHttpReader');
const Cache = require('./Cache');
const ContentTypeInfo = require('../format/ContentTypeInfo');
const DataReader = require('./DataReader');
const MultipartParser = require('./MultipartReader');
const DeserializationError = require('../errors/DeserializationError');
const DATA_EVENTS = DataReader.EVENTS;
const REQUEST_EVENTS = ApplicationHttpReader.EVENTS;
const PART_EVENTS = require('./PartReader').EVENTS;
const MULTIPART_EVENTS = MultipartParser.EVENTS;
/**
* States
*/
const STATES = {
START: 0,
PROCESS: 1,
FINISHED: 2
};
/**
* Events
*/
const EVENTS = {
UNCONSUMED: 'reader.unconsumed',
START: 'reader.start',
END: 'reader.end',
REQUEST_START: REQUEST_EVENTS.START,
REQUEST_REQUESTLINE: REQUEST_EVENTS.REQUEST_LINE,
REQUEST_HEADERS: REQUEST_EVENTS.HEADERS,
REQUEST_BODY_START: REQUEST_EVENTS.BODY_START,
REQUEST_BODY_DATA: ApplicationHttpReader.BODY_PREFIX + '.' + DATA_EVENTS.DATA,
REQUEST_BODY_END: REQUEST_EVENTS.BODY_END,
REQUEST_END: REQUEST_EVENTS.END,
PART_START: PART_EVENTS.START,
PART_HEADERS: PART_EVENTS.HEADERS,
MULTIPART_START: MULTIPART_EVENTS.START,
MULTIPART_END: MULTIPART_EVENTS.END
};
/**
* Main entry class for reading requests. Allows registering various readers for mime types (e.g. multipart/mixed,
* application/http and application/octet-stream).
* Implement as transform to catch the end of input event {@see _flush}.
* Events are emitted to the emitter object given to constructor.
*
* Usage example:
* const reader = new Reader(partReader, emitter)
* request.pipe(reader)
*
* @extends Transform
*/
class ContentDeserializer extends Transform {
/**
* @param {Object} reader - Reader used when starting reading
* @param {EventEmitter} emitter - Emitter to be used for reading events
*/
constructor(reader, emitter) {
super();
this._cache = new Cache(Buffer.from([]));
// State Info
this._state = STATES.START;
// Stack info
this._mimetypeReader = null;
this._mimetypeReaderStack = [];
this._mimetypeReaderFactories = new Map();
this._addMimeReaderFactories('application/http', ApplicationHttpReader.createInstance);
this._addMimeReaderFactories('application/octet-stream', DataReader.createInstance);
this._addMimeReaderFactories('multipart/mixed', MultipartParser.createInstance);
this._fallBackReaderFactory = DataReader.createInstance;
this.pushReader(reader);
this._emitter = emitter;
}
/**
* Overwritten _transform implementation
*
* @param {Buffer} buffer - Chunk of bytes
* @param {string} enc - Encoding of the chunk
* @param {Function} next - Callback function
* @private
*/
_transform(buffer, enc, next) {
try {
this._cache.append(buffer);
this.readCache(false);
next();
} catch (err) {
next(err);
}
}
/**
* Overwritten _flush method to catch the end of the source stream
*
* @param {Function} next - Callback function
* @private
*/
_flush(next) {
try {
this.readCache(true); // last
next();
} catch (err) {
next(err);
}
}
/**
* Read data from cache until the catch is empty. The data is passed to the current reader until the
* processor is finished or more data is required by the reader.
*
* @param {boolean} last
* @returns {boolean}
* @throws {Error} In case of internal stack error
* @throws {Error} In case last === true and more data is required
*/
readCache(last) {
let needMoreData = false;
// this._cache.setConsumedBytes(0);
while (!needMoreData && this._state !== STATES.FINISHED) {
switch (this._state) {
case STATES.START:
this._emitter.emit(EVENTS.START);
this._state = STATES.PROCESS;
break;
case STATES.PROCESS:
needMoreData = this._mimetypeReader.readCache(this, this._cache, last);
if (needMoreData === false) {
this.popReader();
}
if (!this._mimetypeReader) {
this._state = STATES.FINISHED;
}
break;
default:
throw new DeserializationError('default reached');
}
}
this._cache.shrink();
// Ensure proper cleanup
if (last) {
if (needMoreData) throw new DeserializationError('More data required to process multipart');
if (this._cache.getLength()) {
this._emitter.emit(EVENTS.UNCONSUMED, this._cache.getBytes(this._cache.getLength()));
}
this._emitter.emit(EVENTS.END);
}
return needMoreData;
}
/**
* Register a reader factory to a mime type
*
* @param {string} mimetype - Mimetype (e.g. multipart/mixed).
* @param {Function} factory - Factory for the reader which can parse this mime type payload
* @private
*/
_addMimeReaderFactories(mimetype, factory) {
this._mimetypeReaderFactories[mimetype] = factory;
}
/**
* Push a reader to the readers stack
*
* @param {*} mimetypeReader
*/
pushReader(mimetypeReader) {
if (this._mimetypeReader) {
this._mimetypeReaderStack.push(this._mimetypeReader);
}
this._mimetypeReader = mimetypeReader;
}
/**
* Pop a reader from the readers stack
*/
popReader() {
this._mimetypeReader = this._mimetypeReaderStack.pop();
// this._mimetypeReader may be null
}
/**
* Determine next reader depending on a content type header
*
* @param {*} headers Http headers, only "content-type" header is used
* @returns {*}
*/
getNextReader(headers) {
// default content-type as defined in [RFC7231] 3.1.1.5. Content-Type
const contentTypeInfo = headers['content-type']
|| new ContentTypeInfo().setMimeType('application/octet-stream');
const nextReader = this.createReader(contentTypeInfo, headers);
if (!nextReader) {
throw new DeserializationError('missing mimetype Reader for ' + contentTypeInfo.getMimeType());
}
return nextReader;
}
/**
* Create and reader
*
* @param {ContentTypeInfo} contentTypeInfo - Type information used to create this reader
* @param {*} headers
* @returns {*}
*/
createReader(contentTypeInfo, headers) {
let factory = this._mimetypeReaderFactories[contentTypeInfo.getMimeType()] || this._fallBackReaderFactory;
return factory(contentTypeInfo, headers);
}
}
ContentDeserializer.EVENTS = EVENTS;
module.exports = ContentDeserializer;