Skip to content

Support streams (http) #97

Closed
Closed
@haukepribnow

Description

@haukepribnow

Repro steps

  1. Using programming model v4, create an HTTP-triggered function where:

    • The handler function provides a ReadableStream in the return object's body field.
    • The ReadableStream enqueues data with a time delay.
    • The ReadableStream continues to enqueue data after the handler function has returned.
    • While enqueuing data, context.log() gets called.
  2. Call the function via HTTP.

(I recommend to check the source code below; I guess it's more telling than my description here.)

Expected behavior

When the function is called:

  • When new data is enqueued, it should directly be sent to the HTTP client as a chunk (using Transfer-Encoding: chunked).
  • The context.log() calls should log "as usual" and should not raise warnings/errors.

Actual behavior

  • While Transfer-Encoding: chunked is set in the HTTP response, newly enqueued data is not directly sent when it becomes available. Instead, the completion of the stream is awaited before any data is sent. All data is then sent in a single chunk.
  • While the context.log() calls (that happen when data is enqueued after the handler function has returned) do output the log message, they also output a warning: Unexpected call to 'log' on the context object after function execution has completed. Please check for asynchronous calls that are not awaited. Function name: streamingTest. Invocation Id: [removed].

Known workarounds

n/a

Related information

  • Programming language used: TypeScript
Source
import { ReadableStream } from "stream/web";
import { HttpRequest, HttpResponse, InvocationContext, app } from "@azure/functions";

// Test function that returns a ReadableStream as body.
export async function streamingTest(request: HttpRequest, context: InvocationContext): Promise<HttpResponse> {
    context.log(`Http function processed request for url "${request.url}"`);

    const encoder = new TextEncoder();
    let times = 0;

    const stream = new ReadableStream({
        type: 'bytes',
        async start(controller) {
            function onTimer() {
                if (times >= 1000) {
                    controller.close();
                    return;
                }

                context.log(`${times}`);
                // This raises a warning:
                // Unexpected call to 'log' on the context object after function execution
                // has completed. Please check for asynchronous calls that are not awaited.
                // Function name: streamingTest. Invocation Id: [removed].

                times++;
                const data = encoder.encode(`${times}\r\n`);
                controller.enqueue(data);
                setTimeout(onTimer, 10);
            }
            onTimer();
        } 
    }, {
       highWaterMark: 1 
    });

    return new HttpResponse({
        status: 200,
        headers: {
            "Content-Type": "text/plain"
        },
        body: stream
    });
};

app.http('streamingTest', {
    methods: ['GET'],
    authLevel: 'anonymous',
    handler: streamingTest 
});
Screenshot of Wireshark showing information about the HTTP request & response

image

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions