mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
72 lines
3.3 KiB
TypeScript
72 lines
3.3 KiB
TypeScript
import type { Representation } from '../http/representation/Representation';
|
|
import type { RepresentationPreferences } from '../http/representation/RepresentationPreferences';
|
|
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
|
|
import { getLoggerFor } from '../logging/LogUtil';
|
|
import { InternalServerError } from '../util/errors/InternalServerError';
|
|
import { RangeNotSatisfiedHttpError } from '../util/errors/RangeNotSatisfiedHttpError';
|
|
import { guardStream } from '../util/GuardedStream';
|
|
import { termToInt } from '../util/QuadUtil';
|
|
import { SliceStream } from '../util/SliceStream';
|
|
import { toLiteral } from '../util/TermUtil';
|
|
import { POSIX, SOLID_HTTP, XSD } from '../util/Vocabularies';
|
|
import type { Conditions } from './conditions/Conditions';
|
|
import { PassthroughStore } from './PassthroughStore';
|
|
import type { ResourceStore } from './ResourceStore';
|
|
|
|
/**
|
|
* Resource store that slices the data stream if there are range preferences.
|
|
* Only works for `bytes` range preferences on binary data streams.
|
|
* Does not support multipart range requests.
|
|
*
|
|
* If the slice happens, unit/start/end values will be written to the metadata to indicate such.
|
|
* The values are dependent on the preferences we got as an input,
|
|
* as we don't know the actual size of the data stream.
|
|
*/
|
|
export class BinarySliceResourceStore<T extends ResourceStore = ResourceStore> extends PassthroughStore<T> {
|
|
protected readonly logger = getLoggerFor(this);
|
|
|
|
public async getRepresentation(
|
|
identifier: ResourceIdentifier,
|
|
preferences: RepresentationPreferences,
|
|
conditions?: Conditions,
|
|
): Promise<Representation> {
|
|
const result = await this.source.getRepresentation(identifier, preferences, conditions);
|
|
|
|
if (!preferences.range || preferences.range.unit !== 'bytes' || preferences.range.parts.length === 0) {
|
|
return result;
|
|
}
|
|
if (result.metadata.has(SOLID_HTTP.unit)) {
|
|
this.logger.debug('Not slicing stream that has already been sliced.');
|
|
return result;
|
|
}
|
|
|
|
if (!result.binary) {
|
|
throw new InternalServerError('Trying to slice a non-binary stream.');
|
|
}
|
|
if (preferences.range.parts.length > 1) {
|
|
throw new RangeNotSatisfiedHttpError('Multipart range requests are not supported.');
|
|
}
|
|
|
|
const [{ start, end }] = preferences.range.parts;
|
|
result.metadata.set(SOLID_HTTP.terms.unit, preferences.range.unit);
|
|
result.metadata.set(SOLID_HTTP.terms.start, toLiteral(start, XSD.terms.integer));
|
|
if (typeof end === 'number') {
|
|
result.metadata.set(SOLID_HTTP.terms.end, toLiteral(end, XSD.terms.integer));
|
|
}
|
|
|
|
try {
|
|
const size = termToInt(result.metadata.get(POSIX.terms.size));
|
|
// The reason we don't determine the object mode based on the object mode of the parent stream
|
|
// is that `guardedStreamFrom` does not create object streams when inputting streams/buffers.
|
|
// Something to potentially update in the future.
|
|
result.data = guardStream(new SliceStream(result.data, { start, end, size, objectMode: false }));
|
|
} catch (error: unknown) {
|
|
// Creating the slice stream can throw an error if some of the parameters are unacceptable.
|
|
// Need to make sure the stream is closed in that case.
|
|
result.data.destroy();
|
|
throw error;
|
|
}
|
|
return result;
|
|
}
|
|
}
|