Skip to content

Commit 5e5f9c7

Browse files
authored
Support ID-based HTTP paths for exposed Things (#1464)
* feat(binding-http/http-server): generate id based http paths fix #1458 * chore: remove files from another branch
1 parent 5f9a49e commit 5e5f9c7

3 files changed

Lines changed: 89 additions & 21 deletions

File tree

packages/binding-http/src/http-server.ts

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export default class HttpServer implements ProtocolServer {
6464
private readonly address?: string;
6565
private readonly baseUri?: string;
6666
private readonly urlRewrite?: Record<string, string>;
67+
private readonly devFriendlyUri: boolean;
6768
private readonly supportedSecuritySchemes: string[] = ["nosec"];
6869
private readonly validOAuthClients: RegExp = /.*/g;
6970
private readonly server: http.Server | https.Server;
@@ -83,6 +84,7 @@ export default class HttpServer implements ProtocolServer {
8384
this.baseUri = config.baseUri;
8485
this.urlRewrite = config.urlRewrite;
8586
this.middleware = config.middleware;
87+
this.devFriendlyUri = config.devFriendlyUri ?? true;
8688

8789
const router = Router({
8890
ignoreTrailingSlash: true,
@@ -267,21 +269,32 @@ export default class HttpServer implements ProtocolServer {
267269
}
268270

269271
public async expose(thing: ExposedThing, tdTemplate: WoT.ExposedThingInit = {}): Promise<void> {
270-
let urlPath = slugify(thing.title, { lower: true });
271-
272-
// avoid URL clashes
273-
if (this.things.has(urlPath)) {
274-
let uniqueUrlPath;
275-
let nameClashCnt = 2;
276-
do {
277-
uniqueUrlPath = urlPath + "_" + nameClashCnt++;
278-
} while (this.things.has(uniqueUrlPath));
279-
urlPath = uniqueUrlPath;
280-
}
281-
282272
if (this.getPort() !== -1) {
283-
debug(`HttpServer on port ${this.getPort()} exposes '${thing.title}' as unique '/${urlPath}'`);
284-
this.things.set(urlPath, thing);
273+
const paths: string[] = [];
274+
// If not id is given we create the path using the title even if devFriendlyUri is false.
275+
// in Thing Description 1.1 id is optional
276+
if (this.devFriendlyUri || thing.id == null) {
277+
let urlPath = slugify(thing.title, { lower: true });
278+
279+
// avoid URL clashes
280+
if (this.things.has(urlPath)) {
281+
let uniqueUrlPath;
282+
let nameClashCnt = 2;
283+
do {
284+
uniqueUrlPath = urlPath + "_" + nameClashCnt++;
285+
} while (this.things.has(uniqueUrlPath));
286+
urlPath = uniqueUrlPath;
287+
}
288+
this.things.set(urlPath, thing);
289+
paths.push(urlPath);
290+
debug("HttpServer on port %d exposes %s as unique '/%s'", this.getPort(), thing.name, urlPath);
291+
}
292+
293+
if (thing.id != null) {
294+
this.things.set(thing.id, thing);
295+
paths.push(thing.id);
296+
debug("HttpServer on port %d exposes %s as unique '/%s'", this.getPort(), thing.name, thing.id);
297+
}
285298

286299
if (this.scheme === "http" && Object.keys(thing.securityDefinitions).length !== 0) {
287300
warn(`HTTP Server will attempt to use your security schemes even if you are not using HTTPS.`);
@@ -290,16 +303,20 @@ export default class HttpServer implements ProtocolServer {
290303
this.fillSecurityScheme(thing);
291304

292305
if (this.baseUri !== undefined) {
293-
const base: string = this.baseUri.concat("/", encodeURIComponent(urlPath));
294-
info("HttpServer TD hrefs using baseUri " + this.baseUri);
295-
this.addEndpoint(thing, tdTemplate, base);
306+
for (const path of paths) {
307+
info("HttpServer TD hrefs using baseUri %s and path %s", this.baseUri, path);
308+
const base: string = this.baseUri.concat("/", encodeURIComponent(path));
309+
this.addEndpoint(thing, tdTemplate, base);
310+
}
296311
} else {
297312
// fill in binding data
298313
for (const address of Helpers.getAddresses()) {
299-
const base: string =
300-
this.scheme + "://" + address + ":" + this.getPort() + "/" + encodeURIComponent(urlPath);
301-
302-
this.addEndpoint(thing, tdTemplate, base);
314+
for (const path of paths) {
315+
const base: string =
316+
this.scheme + "://" + address + ":" + this.getPort() + "/" + encodeURIComponent(path);
317+
info("HttpServer TD hrefs using address %s and path %s", address, path);
318+
this.addEndpoint(thing, tdTemplate, base);
319+
}
303320
}
304321
}
305322
}
@@ -311,6 +328,7 @@ export default class HttpServer implements ProtocolServer {
311328
for (const [name, thing] of this.things.entries()) {
312329
if (thing.id === thingId) {
313330
this.things.delete(name);
331+
this.things.delete(thingId);
314332
info(`HttpServer successfully destroyed '${thing.title}'`);
315333

316334
return true;

packages/binding-http/src/http.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export interface HttpConfig {
4545
serverKey?: string;
4646
serverCert?: string;
4747
security?: SecurityScheme[];
48+
devFriendlyUri?: boolean;
4849
middleware?: MiddlewareRequestHandler;
4950
}
5051

packages/binding-http/test/http-server-test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,4 +1020,53 @@ class HttpServerTest {
10201020

10211021
return httpServer.stop();
10221022
}
1023+
1024+
@test async "should expose Thing with id and title and be reachable from both"() {
1025+
const httpServer = new HttpServer({ port: 0 });
1026+
1027+
await httpServer.start(new Servient());
1028+
1029+
const testThing = new ExposedThing(new Servient(), {
1030+
title: "TestThing",
1031+
id: "urn:dev:wot:test-thing-1234",
1032+
properties: {
1033+
test: {
1034+
type: "string",
1035+
forms: [],
1036+
},
1037+
},
1038+
actions: {
1039+
test: {
1040+
output: { type: "string" },
1041+
forms: [],
1042+
},
1043+
},
1044+
});
1045+
1046+
await httpServer.expose(testThing);
1047+
1048+
const uriByTitle = `http://localhost:${httpServer.getPort()}/testthing`;
1049+
const uriById = `http://localhost:${httpServer.getPort()}/urn:dev:wot:test-thing-1234`;
1050+
1051+
let resp;
1052+
resp = await (await fetch(uriByTitle)).json();
1053+
expect(resp.title).to.be.eq("TestThing");
1054+
expect(resp.properties.test.forms.some((form: { href: string }) => form.href.includes("testthing"))).to.be.true;
1055+
expect(resp.actions.test.forms.some((form: { href: string }) => form.href.includes("testthing"))).to.be.true;
1056+
1057+
resp = await (await fetch(uriById)).json();
1058+
expect(resp.id).to.be.eq("urn:dev:wot:test-thing-1234");
1059+
expect(
1060+
resp.properties.test.forms.some((form: { href: string }) =>
1061+
form.href.includes(encodeURIComponent("urn:dev:wot:test-thing-1234"))
1062+
)
1063+
).to.be.true;
1064+
expect(
1065+
resp.actions.test.forms.some((form: { href: string }) =>
1066+
form.href.includes(encodeURIComponent("urn:dev:wot:test-thing-1234"))
1067+
)
1068+
).to.be.true;
1069+
1070+
return httpServer.stop();
1071+
}
10231072
}

0 commit comments

Comments
 (0)