Skip to content

Commit

Permalink
Merge pull request #513 from danielpeintner/fix-http-server
Browse files Browse the repository at this point in the history
fix: http server reporting circular structure
  • Loading branch information
danielpeintner authored Sep 22, 2021
2 parents e0a3976 + 1944d7c commit 1cd63ab
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 48 deletions.
71 changes: 45 additions & 26 deletions packages/binding-http/src/http-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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? <any>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<string, any> = {};
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(
Expand Down Expand Up @@ -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]",
Expand All @@ -751,8 +775,6 @@ export default class HttpServer implements ProtocolServer {
res.end("Invalid Event Data");
return;
}
// send event data
res.end(content.body);
},
options
)
Expand All @@ -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(),
<any>property,
contentType
);
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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
);
Expand Down Expand Up @@ -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]",
Expand All @@ -981,8 +1002,6 @@ export default class HttpServer implements ProtocolServer {
res.end("Invalid Event Data");
return;
}
// send event data
res.end(content.body);
},
options
)
Expand Down
29 changes: 17 additions & 12 deletions packages/binding-http/test/http-server-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<string>((resolve, reject) => {
resolve("TEST");
});
Expand All @@ -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();
}
Expand Down
30 changes: 22 additions & 8 deletions packages/core/src/exposed-thing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>;
Expand Down Expand Up @@ -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);
Expand All @@ -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}'`));
Expand Down Expand Up @@ -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}'`);
}
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/interaction-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ArrayBuffer> {
Expand All @@ -68,7 +70,7 @@ export class InteractionOutput implements WoT.InteractionOutput {

const validate = ajv.compile<T>(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);
Expand Down

0 comments on commit 1cd63ab

Please sign in to comment.