From ca5e49a40c6b07f2f0e70d837fae03500ae1639c Mon Sep 17 00:00:00 2001 From: Jan Romann Date: Mon, 16 Jan 2023 11:11:51 +0100 Subject: [PATCH 1/2] fix(binding-http): fix content negotiation --- packages/binding-http/src/http-server.ts | 27 +++++++++++++++++------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/binding-http/src/http-server.ts b/packages/binding-http/src/http-server.ts index fb3442da9..a0925a43b 100644 --- a/packages/binding-http/src/http-server.ts +++ b/packages/binding-http/src/http-server.ts @@ -544,16 +544,27 @@ export default class HttpServer implements ProtocolServer { private async handleTdRequest(thing: ExposedThing, req: http.IncomingMessage, res: http.ServerResponse) { const td = thing.getThingDescription(); - let accept = req.headers.accept; const contentSerdes = ContentSerdes.get(); + // TODO: Parameters need to be considered here as well + const acceptValues = req.headers.accept?.split(",").map((acceptValue) => acceptValue.split(";")[0]) ?? [ + ContentSerdes.TD, + ]; + // TODO: Better handling of wildcard values - if (accept === "*/*") { - accept = null; - } + const filteredAcceptValues = acceptValues + .map((acceptValue) => { + if (acceptValue === "*/*") { + return ContentSerdes.TD; + } + + return acceptValue; + }) + .filter((acceptValue) => contentSerdes.isSupported(acceptValue)); + + if (filteredAcceptValues.length > 0) { + const contentType = filteredAcceptValues[0]; - if (accept == null || contentSerdes.isSupported(accept)) { - const contentType = accept ?? ContentSerdes.TD; const content = contentSerdes.valueToContent(thing.getThingDescription(), undefined, contentType); const payload = await content.toBuffer(); @@ -565,10 +576,10 @@ export default class HttpServer implements ProtocolServer { return; } - debug(`Request contained an accept header with value ${accept} which is not supported.`); + debug(`Request contained an accept header with the values ${acceptValues}, none of which are supported.`); res.writeHead(406); - res.end(`Content-Type ${accept} is not supported by this resource.`); + res.end(`Accept header contained no Content-Types supported by this resource. (Was ${acceptValues})`); } private async handleRequest(req: http.IncomingMessage, res: http.ServerResponse) { From b892807c12550882ef61e6052de9fac5a346d6d0 Mon Sep 17 00:00:00 2001 From: Jan Romann Date: Mon, 16 Jan 2023 11:33:28 +0100 Subject: [PATCH 2/2] test(binding-http): adjust test for content negotiation --- .../binding-http/test/http-server-test.ts | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/packages/binding-http/test/http-server-test.ts b/packages/binding-http/test/http-server-test.ts index 1d9b8f8aa..9ae338c70 100644 --- a/packages/binding-http/test/http-server-test.ts +++ b/packages/binding-http/test/http-server-test.ts @@ -562,15 +562,46 @@ class HttpServerTest { const uri = `http://localhost:${httpServer.getPort()}/test/`; - const defaultContentTypeResponse = await fetch(uri); - expect(defaultContentTypeResponse.headers.get("Content-Type")).to.equal("application/td+json"); - - const negotiatedContentTypeResponse = await fetch(uri, { - headers: { - Accept: "application/json", + const testCases = [ + { + inputHeaders: {}, + expected: "application/td+json", + expectedResponseCode: 200, }, - }); - expect(negotiatedContentTypeResponse.headers.get("Content-Type")).to.equal("application/json"); + { + inputHeaders: { Accept: "application/json" }, + expected: "application/json", + expectedResponseCode: 200, + }, + { + inputHeaders: { Accept: "*/*,application/json" }, + expected: "application/td+json", + expectedResponseCode: 200, + }, + { + inputHeaders: { Accept: "*/*" }, + expected: "application/td+json", + expectedResponseCode: 200, + }, + { + inputHeaders: { Accept: "foo/cbar;baz=fuzz,application/json,foo/cbar" }, + expected: "application/json", + expectedResponseCode: 200, + }, + { + inputHeaders: { Accept: "foo/cbar;baz=fuzz,foo/cbar" }, + expected: null, + expectedResponseCode: 406, + }, + ]; + + for (const testCase of testCases) { + const negotiatedContentTypeResponse = await fetch(uri, { + headers: testCase.inputHeaders, + }); + expect(negotiatedContentTypeResponse.headers.get("Content-Type")).to.equal(testCase.expected); + expect(negotiatedContentTypeResponse.status).to.equal(testCase.expectedResponseCode); + } return httpServer.stop(); }