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

feat: new hooks API #3054

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft

feat: new hooks API #3054

wants to merge 2 commits into from

Conversation

ronag
Copy link
Member

@ronag ronag commented Apr 4, 2024

While this is not breaking per se. It's pretty much unusable with existing handlers...

@ronag ronag mentioned this pull request Apr 4, 2024
7 tasks
@ronag ronag force-pushed the new-hooks-simple branch 21 times, most recently from 145ad9e to 0444839 Compare April 4, 2024 15:54
onRequestEnd?(): void;

/** Invoked after response is starting to be processed */
onResponseStart?(controller: Controller): void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're not passing a controller here.
Also, in the onRequestStart above, you don't pass it so I would remove the comment at all.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we have 2 different controllers, one for the request and one for the response, they may run in parallel.

Copy link
Contributor

@ShogunPanda ShogunPanda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One nit, rest LGTM!


if (channels.headers.hasSubscribers) {
channels.headers.publish({ request: this, response: { statusCode, headers, statusText } })
}

try {
return this[kHandler].onHeaders(statusCode, headers, resume, statusText)
this[kHandler].onResponseHeaders?.(parseHeaders(headers), statusCode, statusText)
Copy link
Member Author

@ronag ronag Apr 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we are no longer pass raw headers. We parse them immediately. As far as I can tell this is what most users do and it's unfortunate to force middleware to work on raw headers.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ShogunPanda @metcoder95 @mcollina any thoughts here? Is this a mistake (i.e. performance blocker) or just being pragmatic?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latter. Let's be pragmatic.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on pragmatism

Copy link
Member

@metcoder95 metcoder95 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, nice work; I can see the potential simplification of coordinating the flow control through the hooks thanks to the controller.

Just have some open questions left.

lib/core/request.js Outdated Show resolved Hide resolved
lib/core/request.js Show resolved Hide resolved
/** Invoked after headers data has been received */
onResponseHeaders?(headers: Record<string, string>, statusCode: number, statusText?: string): void;
/** Invoked after response payload data is received. */
onResponseData?(chunk: Buffer | string): void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious, how are we planning for the hook to hint the controller about backpressure, as it seems it does not receives the controller?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking the user saves a reference to the controller from the start hook. By I guess we could always send in the controller?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair, we can keep it simple and keep the controller for that

@codecov-commenter
Copy link

codecov-commenter commented Apr 5, 2024

Codecov Report

Attention: Patch coverage is 90.38462% with 10 lines in your changes are missing coverage. Please review.

Project coverage is 93.50%. Comparing base (cae5625) to head (d1c5e47).
Report is 5 commits behind head on main.

Files Patch % Lines
lib/core/request.js 89.58% 10 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3054      +/-   ##
==========================================
- Coverage   93.51%   93.50%   -0.02%     
==========================================
  Files          89       89              
  Lines       24331    24408      +77     
==========================================
+ Hits        22754    22822      +68     
- Misses       1577     1586       +9     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@ronag
Copy link
Member Author

ronag commented Apr 6, 2024

@mcollina @ShogunPanda @metcoder95

  • Should we have separate controllers for request and response or just one for both or just the response? In theory the request and response can propagate independently hence it might make sense to be able to pause/resume them separately, on the other hand it's not a usual case and maybe just adds unnecessary complexity?
  • Should it be possible to pause/resume the request? Or should we always leave it to the server to apply back-pressure? One use case would to be able to throttle the client upload.

@ShogunPanda
Copy link
Contributor

@mcollina @ShogunPanda @metcoder95

  • Should we have separate controllers for request and response or just one for both or just the response? In theory the request and response can propagate independently hence it might make sense to be able to pause/resume them separately, on the other hand it's not a usual case and maybe just adds unnecessary complexity?

I would keep one controller which acts on both. pause, resume and abort can act on both the request and response and the client can only be in either "sending request" or "receiving response" state at a time so no room for ambiguity.

  • Should it be possible to pause/resume the request? Or should we always leave it to the server to apply back-pressure? One use case would to be able to throttle the client upload.

I definitely would allow to also control the request flow.

@metcoder95
Copy link
Member

@mcollina @ShogunPanda @metcoder95

  • Should we have separate controllers for request and response or just one for both or just the response? In theory the request and response can propagate independently hence it might make sense to be able to pause/resume them separately, on the other hand it's not a usual case and maybe just adds unnecessary complexity?

I would keep one controller which acts on both. pause, resume and abort can act on both the request and response and the client can only be in either "sending request" or "receiving response" state at a time so no room for ambiguity.

Agree with @ShogunPanda towards a single controller, though I believe that most of the time implementers will look to control the stream flow over the response rather than the request; by providing a controller that allows them to also alter the flow of the request we might enable scenarios where users puts themselves into a weird spot and undici not being fully able to do its work accordingly.

The last being said, I'd prefer to keep the controller only for the response and we can evaluate later if there's a need for one over the response.

  • Should it be possible to pause/resume the request? Or should we always leave it to the server to apply back-pressure? One use case would to be able to throttle the client upload.

I definitely would allow to also control the request flow.

Couldn't we achieve that already with Dispatch#compose?
They can intercept the dispatch and throttle the body as they need

@ronag
Copy link
Member Author

ronag commented Apr 7, 2024

the client can only be in either "sending request" or "receiving response" state at a time so no room for ambiguity.

Actually, you can be sending and receiving at the same time. Though I'm not sure how common that is.

Couldn't we achieve that already with Dispatch#compose? They can intercept the dispatch and throttle the body as they need

Yes, that's also possible. Though less "nice".

@metcoder95 @ShogunPanda So in theory we could totally skip the request hooks (at least for now). However, there is one problem. We need to be able to abort the request before the response has been received (onResponseStart). That's the only place where we actually need to interact with the request.

i.e.

onRequestStart(controller: { abort: Function })

But I guess we can just implement that and leave the rest for the future. Or can you think of a nicer way? Preferably without any onRequestXXX hook.

One alternative is to keep onConnect (I'd prefer not).

@ShogunPanda
Copy link
Contributor

the client can only be in either "sending request" or "receiving response" state at a time so no room for ambiguity.

Actually, you can be sending and receiving at the same time. Though I'm not sure how common that is.

Gosh, you are right. Why do I always forget about pipelining? XD

Couldn't we achieve that already with Dispatch#compose? They can intercept the dispatch and throttle the body as they need

Yes, that's also possible. Though less "nice".

@metcoder95 @ShogunPanda So in theory we could totally skip the request hooks (at least for now). However, there is one problem. We need to be able to abort the request before the response has been received (onResponseStart). That's the only place where we actually need to interact with the request.

i.e.

onRequestStart(controller: { abort: Function })

But I guess we can just implement that and leave the rest for the future. Or can you think of a nicer way? Preferably without any onRequestXXX hook.

One alternative is to keep onConnect (I'd prefer not).

Let's skip it for now. I would just say let's code it in a way we can introduce it later in a semver-minor.

@metcoder95
Copy link
Member

@metcoder95 @ShogunPanda So in theory we could totally skip the >request hooks (at least for now). However, there is one problem. We >need to be able to abort the request before the response has been >received (onResponseStart). That's the only place where we actually >need to interact with the request.

i.e.

onRequestStart(controller: { abort: Function })

But I guess we can just implement that and leave the rest for the future.

Agree and in sync with @ShogunPanda, let's do that 👍

@namka279

This comment was marked as off-topic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants