mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Integrate MetadataHandler
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type { HttpRequest } from '../../server/HttpRequest';
|
||||
import type { Operation } from '../operations/Operation';
|
||||
import type { BodyParser } from './BodyParser';
|
||||
import type { MetadataExtractor } from './metadata/MetadataExtractor';
|
||||
import type { PreferenceParser } from './PreferenceParser';
|
||||
import { RequestParser } from './RequestParser';
|
||||
import type { TargetExtractor } from './TargetExtractor';
|
||||
@@ -11,16 +12,18 @@ import type { TargetExtractor } from './TargetExtractor';
|
||||
export interface SimpleRequestParserArgs {
|
||||
targetExtractor: TargetExtractor;
|
||||
preferenceParser: PreferenceParser;
|
||||
metadataExtractor: MetadataExtractor;
|
||||
bodyParser: BodyParser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link Operation} from an incoming {@link HttpRequest} by aggregating the results
|
||||
* of a {@link TargetExtractor}, {@link PreferenceParser}, and {@link BodyParser}.
|
||||
* of a {@link TargetExtractor}, {@link PreferenceParser}, {@link MetadataExtractor}, and {@link BodyParser}.
|
||||
*/
|
||||
export class BasicRequestParser extends RequestParser {
|
||||
private readonly targetExtractor!: TargetExtractor;
|
||||
private readonly preferenceParser!: PreferenceParser;
|
||||
private readonly metadataExtractor!: MetadataExtractor;
|
||||
private readonly bodyParser!: BodyParser;
|
||||
|
||||
public constructor(args: SimpleRequestParserArgs) {
|
||||
@@ -38,7 +41,8 @@ export class BasicRequestParser extends RequestParser {
|
||||
}
|
||||
const target = await this.targetExtractor.handleSafe(input);
|
||||
const preferences = await this.preferenceParser.handleSafe(input);
|
||||
const body = await this.bodyParser.handleSafe(input);
|
||||
const metadata = await this.metadataExtractor.handleSafe(input);
|
||||
const body = await this.bodyParser.handleSafe({ request: input, metadata });
|
||||
|
||||
return { method: input.method, target, preferences, body };
|
||||
}
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
import type { HttpRequest } from '../../server/HttpRequest';
|
||||
import { AsyncHandler } from '../../util/AsyncHandler';
|
||||
import type { Representation } from '../representation/Representation';
|
||||
import type { RepresentationMetadata } from '../representation/RepresentationMetadata';
|
||||
|
||||
export interface BodyParserArgs {
|
||||
/**
|
||||
* Request that contains the (potential) body.
|
||||
*/
|
||||
request: HttpRequest;
|
||||
/**
|
||||
* Metadata that has already been parsed from the request.
|
||||
* Can be updated by the BodyParser with extra metadata.
|
||||
*/
|
||||
metadata: RepresentationMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the body of an incoming {@link HttpRequest} and converts it to a {@link Representation}.
|
||||
*/
|
||||
export abstract class BodyParser extends AsyncHandler<HttpRequest, Representation | undefined> {}
|
||||
export abstract class BodyParser extends
|
||||
AsyncHandler<BodyParserArgs, Representation | undefined> {}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import type { HttpRequest } from '../../server/HttpRequest';
|
||||
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
|
||||
import { CONTENT_TYPE, HTTP, RDF } from '../../util/UriConstants';
|
||||
import type { Representation } from '../representation/Representation';
|
||||
import { RepresentationMetadata } from '../representation/RepresentationMetadata';
|
||||
import type { BodyParserArgs } from './BodyParser';
|
||||
import { BodyParser } from './BodyParser';
|
||||
|
||||
/**
|
||||
* Converts incoming {@link HttpRequest} to a Representation without any further parsing.
|
||||
* Naively parses the mediatype from the content-type header.
|
||||
* Some other metadata is also generated, but this should probably be done in an external handler.
|
||||
*/
|
||||
export class RawBodyParser extends BodyParser {
|
||||
public async canHandle(): Promise<void> {
|
||||
@@ -17,56 +13,23 @@ export class RawBodyParser extends BodyParser {
|
||||
|
||||
// Note that the only reason this is a union is in case the body is empty.
|
||||
// If this check gets moved away from the BodyParsers this union could be removed
|
||||
public async handle(input: HttpRequest): Promise<Representation | undefined> {
|
||||
public async handle({ request, metadata }: BodyParserArgs): Promise<Representation | undefined> {
|
||||
// RFC7230, §3.3: The presence of a message body in a request
|
||||
// is signaled by a Content-Length or Transfer-Encoding header field.
|
||||
if (!input.headers['content-length'] && !input.headers['transfer-encoding']) {
|
||||
if (!request.headers['content-length'] && !request.headers['transfer-encoding']) {
|
||||
return;
|
||||
}
|
||||
|
||||
// While RFC7231 allows treating a body without content type as an octet stream,
|
||||
// such an omission likely signals a mistake, so force clients to make this explicit.
|
||||
if (!input.headers['content-type']) {
|
||||
if (!request.headers['content-type']) {
|
||||
throw new UnsupportedHttpError('An HTTP request body was passed without Content-Type header');
|
||||
}
|
||||
|
||||
return {
|
||||
binary: true,
|
||||
data: input,
|
||||
metadata: this.parseMetadata(input),
|
||||
data: request,
|
||||
metadata,
|
||||
};
|
||||
}
|
||||
|
||||
private parseMetadata(input: HttpRequest): RepresentationMetadata {
|
||||
const contentType = /^[^;]*/u.exec(input.headers['content-type']!)![0];
|
||||
|
||||
const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: contentType });
|
||||
|
||||
const { link, slug } = input.headers;
|
||||
|
||||
if (slug) {
|
||||
if (Array.isArray(slug)) {
|
||||
throw new UnsupportedHttpError('At most 1 slug header is allowed.');
|
||||
}
|
||||
metadata.set(HTTP.slug, slug);
|
||||
}
|
||||
|
||||
// There are similarities here to Accept header parsing so that library should become more generic probably
|
||||
if (link) {
|
||||
const linkArray = Array.isArray(link) ? link : [ link ];
|
||||
const parsedLinks = linkArray.map((entry): { url: string; rel: string } => {
|
||||
const [ , url, rest ] = /^<([^>]*)>(.*)$/u.exec(entry) ?? [];
|
||||
const [ , rel ] = /^ *; *rel="(.*)"$/u.exec(rest) ?? [];
|
||||
return { url, rel };
|
||||
});
|
||||
for (const entry of parsedLinks) {
|
||||
if (entry.rel === 'type') {
|
||||
metadata.set(RDF.type, entry.url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,50 @@
|
||||
import { PassThrough } from 'stream';
|
||||
import type { Algebra } from 'sparqlalgebrajs';
|
||||
import { translate } from 'sparqlalgebrajs';
|
||||
import type { HttpRequest } from '../../server/HttpRequest';
|
||||
import { APPLICATION_SPARQL_UPDATE } from '../../util/ContentTypes';
|
||||
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
|
||||
import { UnsupportedMediaTypeHttpError } from '../../util/errors/UnsupportedMediaTypeHttpError';
|
||||
import { CONTENT_TYPE } from '../../util/UriConstants';
|
||||
import { readableToString } from '../../util/Util';
|
||||
import { RepresentationMetadata } from '../representation/RepresentationMetadata';
|
||||
import { pipeStreamsAndErrors, readableToString } from '../../util/Util';
|
||||
import type { BodyParserArgs } from './BodyParser';
|
||||
import { BodyParser } from './BodyParser';
|
||||
import type { SparqlUpdatePatch } from './SparqlUpdatePatch';
|
||||
|
||||
/**
|
||||
* {@link BodyParser} that supports `application/sparql-update` content.
|
||||
* Will convert the incoming update string to algebra in a {@link SparqlUpdatePatch}.
|
||||
* Still needs access to a handler for parsing metadata.
|
||||
*/
|
||||
export class SparqlUpdateBodyParser extends BodyParser {
|
||||
public async canHandle(input: HttpRequest): Promise<void> {
|
||||
if (input.headers['content-type'] !== APPLICATION_SPARQL_UPDATE) {
|
||||
public async canHandle({ request }: BodyParserArgs): Promise<void> {
|
||||
if (request.headers['content-type'] !== APPLICATION_SPARQL_UPDATE) {
|
||||
throw new UnsupportedMediaTypeHttpError('This parser only supports SPARQL UPDATE data.');
|
||||
}
|
||||
}
|
||||
|
||||
public async handle(input: HttpRequest): Promise<SparqlUpdatePatch> {
|
||||
public async handle({ request, metadata }: BodyParserArgs): Promise<SparqlUpdatePatch> {
|
||||
// Note that readableObjectMode is only defined starting from Node 12
|
||||
// It is impossible to check if object mode is enabled in Node 10 (without accessing private variables)
|
||||
const options = { objectMode: request.readableObjectMode };
|
||||
const toAlgebraStream = new PassThrough(options);
|
||||
const dataCopy = new PassThrough(options);
|
||||
pipeStreamsAndErrors(request, toAlgebraStream);
|
||||
pipeStreamsAndErrors(request, dataCopy);
|
||||
let algebra: Algebra.Operation;
|
||||
try {
|
||||
// Note that readableObjectMode is only defined starting from Node 12
|
||||
// It is impossible to check if object mode is enabled in Node 10 (without accessing private variables)
|
||||
const options = { objectMode: input.readableObjectMode };
|
||||
const toAlgebraStream = new PassThrough(options);
|
||||
const dataCopy = new PassThrough(options);
|
||||
input.pipe(toAlgebraStream);
|
||||
input.pipe(dataCopy);
|
||||
const sparql = await readableToString(toAlgebraStream);
|
||||
const algebra = translate(sparql, { quads: true });
|
||||
|
||||
const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: APPLICATION_SPARQL_UPDATE });
|
||||
|
||||
// Prevent body from being requested again
|
||||
return {
|
||||
algebra,
|
||||
binary: true,
|
||||
data: dataCopy,
|
||||
metadata,
|
||||
};
|
||||
algebra = translate(sparql, { quads: true });
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
throw new UnsupportedHttpError(error.message);
|
||||
}
|
||||
throw new UnsupportedHttpError();
|
||||
}
|
||||
|
||||
// Prevent body from being requested again
|
||||
return {
|
||||
algebra,
|
||||
binary: true,
|
||||
data: dataCopy,
|
||||
metadata,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user