Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for default accept header in http client #1283

Merged
merged 3 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion packages/binding-http/src/http-client-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,10 @@ export default class HttpClient implements ProtocolClient {
}

public async readResource(form: HttpForm): Promise<Content> {
const request = await this.generateFetchRequest(form, "GET");
// See https://www.w3.org/TR/wot-thing-description11/#contentType-usage
// Case: 1B
const headers = form.contentType != null ? [["accept", form.contentType]] : [["accept", ContentSerdes.DEFAULT]];
const request = await this.generateFetchRequest(form, "GET", { headers });
debug(`HttpClient (readResource) sending ${request.method} to ${request.url}`);

const result = await this.fetch(request);
Expand Down Expand Up @@ -176,6 +179,15 @@ export default class HttpClient implements ProtocolClient {

public async invokeResource(form: HttpForm, content?: Content): Promise<Content> {
const headers = content != null ? [["content-type", content.type]] : [];
// See https://www.w3.org/TR/wot-thing-description11/#contentType-usage
// Cases: 1C and 2A
if (form.response?.contentType != null) {
headers.push(["accept", form.response?.contentType]);
} else if (form.contentType != null) {
headers.push(["accept", form.contentType]);
} else {
headers.push(["accept", ContentSerdes.DEFAULT]);
}

const request = await this.generateFetchRequest(form, "POST", {
headers,
Expand Down Expand Up @@ -347,12 +359,20 @@ export default class HttpClient implements ProtocolClient {

const headers = form["htv:headers"] as Array<HttpHeader>;
for (const option of headers) {
// override defaults
requestInit.headers = requestInit.headers.filter(
(header) => header[0].toLowerCase() !== option["htv:fieldName"].toLowerCase()
);
requestInit.headers.push([option["htv:fieldName"], option["htv:fieldValue"]]);
}
} else if (typeof form["htv:headers"] === "object") {
debug(`HttpClient got Form SINGLE-ENTRY 'headers' ${form["htv:headers"]}`);

const option = form["htv:headers"] as HttpHeader;
// override defaults
requestInit.headers = requestInit.headers.filter(
(header) => header[0].toLowerCase() !== option["htv:fieldName"].toLowerCase()
);
requestInit.headers.push([option["htv:fieldName"], option["htv:fieldValue"]]);
}

Expand Down
117 changes: 114 additions & 3 deletions packages/binding-http/test/http-client-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ interface TestVector {
method?: string;
schema?: DataSchema;
payload?: DataSchemaValue;
headers?: Record<string, string>;
form: Form;
}

Expand Down Expand Up @@ -153,6 +154,10 @@ class TestHttpServer implements ProtocolServer {
expect(req.method).to.equal(this.testVector.method);
expect(req.url).to.equal(new URL(this.testVector.form.href).pathname);

if (this.testVector.headers) {
expect(req.headers).to.include(this.testVector.headers);
}

if (this.testVector.payload !== undefined) {
// load payload
const body: Array<Uint8Array> = [];
Expand Down Expand Up @@ -211,6 +216,9 @@ class HttpClientTest1 {
form: <HttpForm>{
href: `http://localhost:${port1}/`,
},
headers: {
accept: "application/json",
},
};
HttpClientTest1.httpServer.setTestVector(inputVector1);
const resource = await this.client.readResource(inputVector1.form);
Expand All @@ -225,6 +233,9 @@ class HttpClientTest1 {
form: <HttpForm>{
href: `http://localhost:${port1}/`,
},
headers: {
"content-type": "application/json",
},
payload: "test",
};
HttpClientTest1.httpServer.setTestVector(inputVector2);
Expand All @@ -239,6 +250,10 @@ class HttpClientTest1 {
form: <HttpForm>{
href: `http://localhost:${port1}/`,
},
headers: {
"content-type": "application/json",
accept: "application/json",
},
payload: "test",
};
HttpClientTest1.httpServer.setTestVector(inputVector3);
Expand Down Expand Up @@ -269,7 +284,46 @@ class HttpClientTest1 {
body.toString("ascii").should.eql("");
}

@test async "should apply form information - read with POST instead of PUT"() {
@test async "should apply form information - read with not default content-type"() {
// read with defaults
const inputVector1 = {
op: ["readproperty"],
form: <HttpForm>{
href: `http://localhost:${port1}/`,
contentType: "text/plain",
},
headers: {
accept: "text/plain",
},
};
HttpClientTest1.httpServer.setTestVector(inputVector1);
const resource = await this.client.readResource(inputVector1.form);
const body = await resource.toBuffer();
body.toString("ascii").should.eql("");
}

@test async "should apply form information - read with header override"() {
// read with defaults
const inputVector1 = {
op: ["readproperty"],
form: <HttpForm>{
href: `http://localhost:${port1}/`,
"htv:headers": {
"htv:fieldName": "accept",
"htv:fieldValue": "text/plain",
},
},
headers: {
accept: "text/plain",
},
};
HttpClientTest1.httpServer.setTestVector(inputVector1);
const resource = await this.client.readResource(inputVector1.form);
const body = await resource.toBuffer();
body.toString("ascii").should.eql("");
}

@test async "should apply form information - write with POST instead of PUT"() {
// write with POST instead of PUT
const inputVector2 = {
op: ["writeproperty"],
Expand All @@ -283,7 +337,7 @@ class HttpClientTest1 {
await this.client.writeResource(inputVector2.form, new DefaultContent(Readable.from(inputVector2.payload)));
}

@test async "should apply form information - read with PUT instead of GET"() {
@test async "should apply form information - invoke with PUT instead of GET"() {
// invoke with PUT instead of POST
const inputVector3 = {
op: ["invokeaction"],
Expand All @@ -297,7 +351,7 @@ class HttpClientTest1 {
await this.client.invokeResource(inputVector3.form, new DefaultContent(Readable.from(inputVector3.payload)));
}

@test async "should apply form information - read with DELETE instead of POST"() {
@test async "should apply form information - invoke with DELETE instead of POST"() {
// invoke with DELETE instead of POST
const inputVector4 = {
op: ["invokeaction"],
Expand All @@ -309,6 +363,63 @@ class HttpClientTest1 {
HttpClientTest1.httpServer.setTestVector(inputVector4);
await this.client.invokeResource(inputVector4.form);
}

@test async "should apply form information - invoke with not default content-type and no inputs"() {
// invoke with DELETE instead of POST
const inputVector4 = {
op: ["invokeaction"],
form: <HttpForm>{
href: `http://localhost:${port1}/`,
contentType: "text/plain",
},
headers: {
accept: "text/plain",
},
};
HttpClientTest1.httpServer.setTestVector(inputVector4);
await this.client.invokeResource(inputVector4.form);
}

@test async "should apply form information - invoke with not default content-type"() {
// invoke with DELETE instead of POST
const inputVector4 = {
op: ["invokeaction"],
form: <HttpForm>{
href: `http://localhost:${port1}/`,
contentType: "text/plain",
},
headers: {
"content-type": "text/plain",
accept: "text/plain",
},
payload: "test",
};
HttpClientTest1.httpServer.setTestVector(inputVector4);
await this.client.invokeResource(
inputVector4.form,
new Content("text/plain", Readable.from(inputVector4.payload))
);
}

@test async "should apply form information - invoke with default content-type and response content-type"() {
// invoke with DELETE instead of POST
const inputVector4 = {
op: ["invokeaction"],
form: <HttpForm>{
href: `http://localhost:${port1}/`,
response: {
contentType: "text/plain",
},
},
headers: {
"content-type": "application/json",
accept: "text/plain",
},
payload: "test",
};
HttpClientTest1.httpServer.setTestVector(inputVector4);
await this.client.invokeResource(inputVector4.form, new DefaultContent(Readable.from(inputVector4.payload)));
}
}

@suite("HTTP client subscriptions")
Expand Down
Loading