diff --git a/packages/binding-http/src/http-server.ts b/packages/binding-http/src/http-server.ts index c228f3fbc..a55f9e0ff 100644 --- a/packages/binding-http/src/http-server.ts +++ b/packages/binding-http/src/http-server.ts @@ -26,10 +26,18 @@ import * as url from "url"; import { AddressInfo } from "net"; import * as TD from "@node-wot/td-tools"; -import Servient, { ProtocolServer, ContentSerdes, Helpers, ExposedThing, ProtocolHelpers } from "@node-wot/core"; +import Servient, { + ProtocolServer, + ContentSerdes, + Helpers, + ExposedThing, + ProtocolHelpers, + Content, +} from "@node-wot/core"; import { HttpConfig, HttpForm, OAuth2ServerConfig } from "./http"; import createValidator, { Validator } from "./oauth-token-validation"; import { OAuth2SecurityScheme } from "@node-wot/td-tools"; +import { InteractionOutput } from "@node-wot/core/dist/interaction-output"; export default class HttpServer implements ProtocolServer { public readonly scheme: "http" | "https"; @@ -683,11 +691,24 @@ export default class HttpServer implements ProtocolServer { if (req.method === "GET") { thing .readAllProperties() - .then((value: any) => { - const content = ContentSerdes.get().valueToContent(value, undefined); // contentType handling? property); - res.setHeader("Content-Type", content.type); + .then(async (propMap: WoT.PropertyReadMap) => { + // Note we create one object to return, TODO piping response + let recordReponse: Record = {}; + for (let key of propMap.keys()) { + let value: WoT.InteractionOutput = propMap.get(key); + value.form = { f: "all" }; // to avoid missing form error + let content = ContentSerdes.get().valueToContent( + value.data && !value.dataUsed ? value.data : await value.value(), + undefined, + "application/json" + ); + // TODO ProtocolHelpers.readStreamFully() does not work for counter example for SVG countAsImage + const data = await ProtocolHelpers.readStreamFully(content.body); + recordReponse[key] = data.toString(); // contentType handling? + } + res.setHeader("Content-Type", "application/json"); // contentType handling? res.writeHead(200); - res.end(content.body); + res.end(JSON.stringify(recordReponse)); }) .catch((err) => { console.error( @@ -727,19 +748,22 @@ export default class HttpServer implements ProtocolServer { thing .observeProperty( segments[3], - (data) => { - let content; + async (value: InteractionOutput) => { try { const contentType = ProtocolHelpers.getPropertyContentType( thing.getThingDescription(), segments[3], "http" ); - content = ContentSerdes.get().valueToContent( - data, + const content = ContentSerdes.get().valueToContent( + value.data && !value.dataUsed + ? value.data + : await value.value(), property.data, contentType ); + // send event data + content.body.pipe(res); } catch (err) { console.warn( "[binding-http]", @@ -751,8 +775,6 @@ export default class HttpServer implements ProtocolServer { res.end("Invalid Event Data"); return; } - // send event data - res.end(content.body); }, options ) @@ -769,15 +791,14 @@ export default class HttpServer implements ProtocolServer { } else { thing .readProperty(segments[3], options) - // property.read(options) - .then((value: any) => { + .then(async (value: InteractionOutput) => { const contentType = ProtocolHelpers.getPropertyContentType( thing.getThingDescription(), segments[3], "http" ); const content = ContentSerdes.get().valueToContent( - value, + value.data && !value.dataUsed ? value.data : await value.value(), property, contentType ); @@ -827,7 +848,6 @@ export default class HttpServer implements ProtocolServer { } thing .writeProperty(segments[3], value, options) - // property.write(value, options) .then(() => { res.writeHead(204); res.end("Changed"); @@ -897,16 +917,17 @@ export default class HttpServer implements ProtocolServer { thing .invokeAction(segments[3], input, options) - // action.invoke(input, options) - .then((output: any) => { - if (output) { + .then(async (output: InteractionOutput) => { + if (output && action.output) { const contentType = ProtocolHelpers.getActionContentType( thing.getThingDescription(), segments[3], "http" ); const content = ContentSerdes.get().valueToContent( - output, + output.data && !output.dataUsed + ? output.data + : await output.value(), action.output, contentType ); @@ -956,20 +977,20 @@ export default class HttpServer implements ProtocolServer { thing .subscribeEvent( segments[3], - // let subscription = event.subscribe( - (data) => { - let content; + async (value) => { try { const contentType = ProtocolHelpers.getEventContentType( thing.getThingDescription(), segments[3], "http" ); - content = ContentSerdes.get().valueToContent( - data, + const content = ContentSerdes.get().valueToContent( + value.data && !value.dataUsed ? value.data : await value.value(), event.data, contentType ); + // send event data + content.body.pipe(res); } catch (err) { console.warn( "[binding-http]", @@ -981,8 +1002,6 @@ export default class HttpServer implements ProtocolServer { res.end("Invalid Event Data"); return; } - // send event data - res.end(content.body); }, options ) diff --git a/packages/binding-http/test/http-server-test.ts b/packages/binding-http/test/http-server-test.ts index 65f1e68f2..207f00e0e 100644 --- a/packages/binding-http/test/http-server-test.ts +++ b/packages/binding-http/test/http-server-test.ts @@ -70,8 +70,7 @@ class HttpServerTest { await httpServer.stop(); } - // TODO: unskip this test - @test.skip async "should change resource from 'off' to 'on' and try to invoke"() { + @test async "should change resource from 'off' to 'on' and try to invoke"() { const httpServer = new HttpServer({ port: 0 }); await httpServer.start(null); @@ -96,7 +95,7 @@ class HttpServerTest { }); await testThing.writeProperty("test", "off"); testThing.properties.test.forms = []; - testThing.setActionHandler("try", (input) => { + testThing.setActionHandler("try", (input: WoT.InteractionOutput) => { return new Promise((resolve, reject) => { resolve("TEST"); }); @@ -106,21 +105,27 @@ class HttpServerTest { await httpServer.expose(testThing); const uri = `http://localhost:${httpServer.getPort()}/test/`; - let body; + let resp; console.log("Testing", uri); - body = await (await fetch(uri + "properties/test")).text(); - expect(body).to.equal('"off"'); + resp = await (await fetch(uri + "properties/test")).text(); + expect(resp).to.equal("off"); + + resp = await (await fetch(uri + "all/properties")).text(); + expect(resp).to.equal('{"test":"off"}'); + + resp = await (await fetch(uri + "properties/test", { method: "PUT", body: "on" })).text(); + expect(resp).to.equal(""); - body = await (await fetch(uri + "properties/test", { method: "PUT", body: "on" })).text(); - expect(body).to.equal(""); + resp = await (await fetch(uri + "properties/test")).text(); + expect(resp).to.equal("on"); - body = await (await fetch(uri + "properties/test")).text(); - expect(body).to.equal('"on"'); + resp = await (await fetch(uri + "actions/try", { method: "POST", body: "toggle" })).text(); + expect(resp).to.equal("TEST"); - body = await (await fetch(uri + "actions/try", { method: "POST", body: "toggle" })).text(); - expect(body).to.equal('"TEST"'); + resp = await (await fetch(uri + "actions/try", { method: "POST", body: undefined })).text(); + expect(resp).to.equal("TEST"); return httpServer.stop(); } diff --git a/packages/core/src/exposed-thing.ts b/packages/core/src/exposed-thing.ts index d9785e22b..bcaa313e1 100644 --- a/packages/core/src/exposed-thing.ts +++ b/packages/core/src/exposed-thing.ts @@ -25,6 +25,7 @@ import { InteractionOutput } from "./interaction-output"; import { Readable } from "stream"; import ProtocolHelpers from "./protocol-helpers"; import { ReadableStream as PolyfillStream } from "web-streams-polyfill/ponyfill/es2018"; +import { Content } from "./core"; export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { security: Array; @@ -294,9 +295,8 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { ps.readHandler(options) .then((customValue) => { const body = ExposedThing.interactionInputToReadable(customValue); - resolve( - new InteractionOutput({ body, type: "application/json" }, this.properties[propertyName]) - ); + let c: Content = { body: body, type: "application/json" }; + resolve(new InteractionOutput(c, undefined, this.properties[propertyName])); }) .catch((err) => { reject(err); @@ -307,7 +307,13 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { `ExposedThing '${this.title}' gets internal value '${ps.value}' for Property '${propertyName}'` ); const body = ExposedThing.interactionInputToReadable(ps.value); - resolve(new InteractionOutput({ body, type: "application/json" }, this.properties[propertyName])); + resolve( + new InteractionOutput( + { body, type: "application/json" }, + undefined, + this.properties[propertyName] + ) + ); } } else { reject(new Error(`ExposedThing '${this.title}', no property found for '${propertyName}'`)); @@ -476,15 +482,23 @@ export default class ExposedThing extends TD.Thing implements WoT.ExposedThing { "[core/exposed-thing]", `ExposedThing '${this.title}' calls registered handler for Action '${actionName}'` ); - let body = ExposedThing.interactionInputToReadable(parameter); + let bodyInput; + if (parameter) { + bodyInput = ExposedThing.interactionInputToReadable(parameter); + } + let cInput: Content = { body: bodyInput, type: "application/json" }; const result = await as.handler( - new InteractionOutput({ body, type: "application/json" }, this.actions[actionName].input), + new InteractionOutput(cInput, undefined, this.actions[actionName].input), options ); - body = ExposedThing.interactionInputToReadable(result); - return new InteractionOutput({ body, type: "application/json" }, this.actions[actionName].output); + let bodyOutput; + if (result) { + bodyOutput = ExposedThing.interactionInputToReadable(result); + } + let cOutput: Content = { body: bodyOutput, type: "application/json" }; + return new InteractionOutput(cOutput, undefined, this.actions[actionName].output); } else { throw new Error(`ExposedThing '${this.title}' has no handler for Action '${actionName}'`); } diff --git a/packages/core/src/interaction-output.ts b/packages/core/src/interaction-output.ts index 43234f713..adf6d5619 100644 --- a/packages/core/src/interaction-output.ts +++ b/packages/core/src/interaction-output.ts @@ -42,7 +42,9 @@ export class InteractionOutput implements WoT.InteractionOutput { this.form = form; this.schema = schema; - this.data = ProtocolHelpers.toWoTStream(content.body); + if (content && content.body) { + this.data = ProtocolHelpers.toWoTStream(content.body); + } } async arrayBuffer(): Promise { @@ -68,7 +70,7 @@ export class InteractionOutput implements WoT.InteractionOutput { const validate = ajv.compile(this.schema); - // is content type vaild? + // is content type valid? if (!this.form || !ContentSerdes.get().isSupported(this.content.type)) { const message = !this.form ? "Missing form" : `Content type ${this.content.type} not supported`; throw new NotSupportedError(message);