Moddable-OpenSource / moddable

Tools for developers to create truly open IoT products using standard JavaScript on low cost microcontrollers.
http://www.moddable.com
1.32k stars 236 forks source link

embedded:network/http/client hangs if server sends no response body #1219

Closed tve closed 11 months ago

tve commented 11 months ago

Build environment: Linux Moddable SDK version: idf-v5 Target device: esp32-c3

Description If the server response does not include a response body then the client hangs after the onHeaders callback. There is no onDone callback and subsequent requests are never processed. A typical case is a POST that results in a 204 "No Content" status.

Steps to Reproduce

  1. Run the following variant of examples/io/tcp/httpclient:
    
    /*
    * Copyright (c) 2021-2022  Moddable Tech, Inc.
    *
    *   This file is part of the Moddable SDK.
    * 
    *   This work is licensed under the
    *       Creative Commons Attribution 4.0 International License.
    *   To view a copy of this license, visit
    *       <http://creativecommons.org/licenses/by/4.0>.
    *   or send a letter to Creative Commons, PO Box 1866,
    *   Mountain View, CA 94042, USA.
    *
    */

import TextDecoder from "text/decoder"

const HTTPClient = device.network.http.io; const http = new HTTPClient({ ...device.network.http, host: "httpbin.org" }); for (let i = 0; i < 3; i++) { http.request({ path: i == 1 ? /status/204 : /get?foo=bar, headers: new Map([ ["date", Date()], ["user-agent", "ecma-419 test"] ]), onHeaders(status, headers) { trace(Status ${status}, Content-Type ${headers.get("content-type")}\n); this.decoder = new TextDecoder; }, onReadable(count) { trace(this.decoder.decode(this.read(count), {stream: true})); }, onDone() { trace(\n\n **DONE ${i} **\n\n); } }); }

2. You should see

Status 200, Content-Type application/json { "args": { "foo": "bar" }, "headers": { "Date": "Sat Sep 23 2023 13:36:19 GMT-0700", "Host": "httpbin.org", "User-Agent": "ecma-419 test", "X-Amzn-Trace-Id": "Root=1-650f4c43-49b9506544dcfcce3fa4451d" }, "origin": "47.151.203.177", "url": "http://httpbin.org/get?foo=bar" }

DONE 0

Status 204, Content-Type text/html; charset=utf-8

3. After a minute or so (prob. when the server closes the socket) you should see:

DONE 1



**Expected behavior**
I expected to see `**DONE 1 **` immediately and I expected the third request to succeed.
phoddie commented 11 months ago

HTTP 204 is a special case where the response body is always empty. The usual Content-Length field is not included with a 0 because the status implies it. I think HTTP 205 is the same.

Changing these lines in httpclient.js...

https://github.com/Moddable-OpenSource/moddable/blob/c5845e51c450fd45cb382a65422bf6298e29970d/examples/io/tcp/httpclient/httpclient.js#L251-L261

...to the following, gives the expected result.

    if ((204 === this.#status) || (205 === this.#status))
        this.#remaining = 0;
    else if (undefined !== this.#chunk)
        this.#remaining = undefined;        // ignore content-length if chunked
    else if (undefined === this.#remaining)
        this.#remaining = Infinity;

    this.#current.onHeaders?.call(this.#current.request, this.#status, this.#headers);
    if (!this.#current) return;         // closed in callback

    this.#headers = undefined;
    this.#state = "receiveBody";
    this.#line = (undefined == this.#chunk) ? undefined : "";

    if (0 === this.#remaining)
        return void this.#done();

It looks like this might also help with the case where "Content-Length" is 0.

Does that work for you as well?

phoddie commented 11 months ago

Fix committed. Closing. (If problem persists, please reopen.)