HTTP Interface

This commit is contained in:
Daniel Kluge
2023-10-23 16:18:20 +02:00
parent 0fd7b81526
commit 778202e61a
3 changed files with 178 additions and 31 deletions
+27 -2
View File
@@ -70,11 +70,36 @@ export default class AASHelper {
return current; return current;
} }
public static findElement(environment: types.Class, checkFunction: (element: types.Class) => boolean): types.Class | null { public static traverseByShortIds(start: types.Class, shortIds: string[]): types.Class | null {
for (const element of environment.descend()) { let current: types.Class | null = start;
for (const shortId of shortIds) {
if (current === null) break;
current = AASHelper.findChildByIdShort(current, shortId);
}
return current;
}
public static getElementByIdPath(env: types.Environment, submodelOrIdShort: string | types.Submodel, idShorts: string[]): types.Class | null {
const sm = typeof submodelOrIdShort === "string" ? AASHelper.findSMByIdShort(env, submodelOrIdShort) : submodelOrIdShort;
if (sm === null) return null;
return AASHelper.traverseByShortIds(sm, idShorts);
}
public static findElement(start: types.Class, checkFunction: (element: types.Class) => boolean): types.Class | null {
for (const element of start.descend()) {
if (checkFunction(element)) return element; if (checkFunction(element)) return element;
} }
return null; return null;
} }
public static findChildByIdShort(start: types.Class, idShort: string): types.Class | null {
for (const child of start.descendOnce()) {
if ((child as any).idShort === idShort) return child;
}
return null;
}
} }
+148 -6
View File
@@ -2,7 +2,8 @@ import type { Express } from "express";
import express from "express"; import express from "express";
import { jsonization, types } from "@aas-core-works/aas-core3.0-typescript"; import { jsonization, types } from "@aas-core-works/aas-core3.0-typescript";
import AASInterfaceServer from "../server"; import AASInterfaceServer from "../server";
import type { OnRequestCallback, Request } from "types/requests"; import type { OnRequestCallback } from "types/requests";
import AASHelper from "../aasHelper";
export type Config = { export type Config = {
bindAddress: string, bindAddress: string,
@@ -23,9 +24,7 @@ export default class HTTPInterfaceServer extends AASInterfaceServer<Config> {
public prepare(): void { public prepare(): void {
this.app.use(express.json()); this.app.use(express.json());
this.app.get("/aas", (_, res) => { this.createRoutes();
res.json(jsonization.toJsonable(this.aas));
});
} }
public run(): void { public run(): void {
@@ -45,11 +44,154 @@ export default class HTTPInterfaceServer extends AASInterfaceServer<Config> {
}); });
} }
protected findElementByRequest(request: Request) { private createRoutes(): void {
const NOT_IMPLEMENTED = (_req: express.Request, res: express.Response) => res.status(501).end();
const getSM = (id: string) => {
if (!id) return null;
try {
// This is what the specification says
const decoded = Buffer.from(id, "base64").toString("utf-8");
let sm = AASHelper.findSMById(this.aas, decoded);
if (sm === null) {
// Normally, at least in BaSyx just the shortId is used, so just test if we find something like this
sm = AASHelper.findSMByIdShort(this.aas, id);
} }
private createRoutes()/*: express.Router*/ { return sm;
} catch {
return null;
}
}
// /aas
this.app.get("/aas", (_, res) => res.json(jsonization.toJsonable(this.aas)).end());
this.app.put("/aas", NOT_IMPLEMENTED);
this.app.delete("/aas", NOT_IMPLEMENTED);
// /aas/$reference
this.app.get("/aas/$reference", NOT_IMPLEMENTED);
// /aas/asset-information
this.app.get("/aas/asset-information", NOT_IMPLEMENTED);
this.app.put("/aas/asset-information", NOT_IMPLEMENTED);
this.app.get("/aas/asset-information/thumbnail", NOT_IMPLEMENTED);
this.app.put("/aas/asset-information/thumbnail", NOT_IMPLEMENTED);
this.app.delete("/aas/asset-information/thumbnail", NOT_IMPLEMENTED);
// /aas/submodel-refs
this.app.get("/aas/submodel-refs", NOT_IMPLEMENTED);
this.app.post("/aas/submodel-refs", NOT_IMPLEMENTED);
this.app.delete("/aas/submodel-refs/:submodelIdentifier", NOT_IMPLEMENTED);
// /aas/submodels
this.app.get("/aas/submodels/:smId", (req, res) => {
const sm = getSM(req.params.smId);
if (sm === null) return res.status(404).end();
return res.json(jsonization.toJsonable(sm)).end()
});
this.app.put("/aas/submodels/:smId", NOT_IMPLEMENTED);
this.app.patch("/aas/submodels/:smId", NOT_IMPLEMENTED);
this.app.delete("/aas/submodels/:smId", NOT_IMPLEMENTED);
// /aas/submodels/:smId/submodel-elements
this.app.use("/aas/submodels/:smId/submodel-elements", (req, res) => {
if (req.method !== "GET") return res.status(501).end();
const sm = getSM(req.params.smId);
if (sm === null || sm.submodelElements === null) return res.status(404).end();
return res.json(sm.submodelElements.map(element => jsonization.toJsonable(element))).end();
});
// Main function
this.app.use("/aas/submodels/:smId/submodel-elements/*", (req, res) => {
const idShortPathString = (req.params as any)[0];
console.log(idShortPathString);
if (idShortPathString.endsWith("/")) res.status(400).end();
const sm = getSM(req.params.smId);
if (sm === null || sm.submodelElements === null) return res.status(404).end();
if (idShortPathString === "") {
if (req.method === "GET") {
const json = sm.submodelElements.map(element => jsonization.toJsonable(element));
return res.json(json).end();
}
}
// Currently we don't support these operations
if (idShortPathString.endsWith("$value") ||
idShortPathString.endsWith("$reference") ||
idShortPathString.endsWith("$metadata") ||
idShortPathString.endsWith("$path")) return res.status(501).end();
// If operation ids are changed, you can change the format here
const endingMatchSearch = idShortPathString.match(/(\/attachment$|\/invoke$|\/invoke-async$|\/operation-status\/[a-zA-Z0-9\-]+$|\/operation-result\/[a-zA-Z0-9\-]+$)/)
const endingMatch = endingMatchSearch?.[0];
const idShortPath = endingMatch ? idShortPathString.replace(new RegExp(`${endingMatch}$`), "").split("/") : idShortPathString.split("/");
switch (endingMatch) {
case undefined: {
if (req.method !== "GET") return res.status(501).end();
const prop = AASHelper.getElementByIdPath(this.aas, sm, idShortPath);
// TODO // TODO
//! Check if mapping and live data is available!
if (prop === null) return res.status(404).end();
return res.json(jsonization.toJsonable(prop)).end();
}
case "/attachment":
return res.status(501).end();
case "/invoke": {
if (req.method !== "POST") return res.status(405).end();
// TODO
// Mapping, dann Befehl
// Parameter?
return res.send(`Operation result for ${idShortPath}`).end();
}
case "/invoke-async": {
if (req.method !== "POST") return res.status(405).end();
// TODO
// Invoke operation and get handle
const handle = "TODO";
const operationPaths = [`${idShortPath.join("/")}/operation-status/${handle}`, `${idShortPath.join("/")}/operation-result/${handle}`];
return res.status(202).header("Location", operationPaths).end();
}
default: {
// operation-status, operation-result or anything else
const [_, operation, handle] = endingMatch.split("/");
switch (operation) {
case "operation-status": {
if (req.method !== "GET") return res.status(405).end();
// TODO
return res.json({
"status": "TODO",
handle
}).end();
}
case "operation-result": {
if (req.method !== "GET") return res.status(405).end();
// TODO
return res.json({
"status": "TODO",
handle
}).end();
}
default: {
return res.status(404).end();
}
}
}
}
})
} }
} }
-20
View File
@@ -19,24 +19,4 @@ export default abstract class AASInterfaceServer<ConfigInterface> {
public abstract stop(): void; public abstract stop(): void;
public abstract notify(event: any): void; public abstract notify(event: any): void;
protected abstract findElementByRequest(request: Request): any | null;
protected findElementBySemanticId(id: string): types.Class | null {
for (const element of this.aas.descend()) {
if ((element as any).semanticId === id) {
return element;
}
}
return null;
}
protected findElementByShortId(id: string): types.Class | null {
for (const element of this.aas.descend()) {
if ((element as any).idShort === id) {
return element;
}
}
return null;
}
} }