-
Notifications
You must be signed in to change notification settings - Fork 5.4k
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: native HTTP bindings #9935
Conversation
abc88b3
to
6af6480
Compare
5a4a122
to
936af2a
Compare
cli/dts/lib.deno.unstable.d.ts
Outdated
* const requests = await Deno.startHttp(conn); | ||
* ``` | ||
*/ | ||
export function startHttp(conn: Conn): AsyncIterableIterator<HttpEvent>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should downgrade this return type to AsyncIterable<HttpEvent>
, and now or in the future upgrade that to HttpConn extends AsyncIterable<HttpEvent>
which exposes .rid
and .close()
if we want those things.
Returning or extending AsyncIterableIterator<_>
(and overloading .return()
for closing) isn't the way to go. I pushed against that for Listener
early on, but unfortunately watchFs()
slipped through and demonstrated this. I've been meaning to fix that for 2.0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning or extending AsyncIterableIterator<_> (and overloading .return() for closing) isn't the way to go
What's your reasoning behind this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For starters .return()
is optional on AsyncIterableIterator
: https://github.com/denoland/deno/blob/main/cli/tests/unit/fs_events_test.ts#L74. It doesn't align with the default implementation of .return()
on generators, which is supposed to break any pending loops without error. I don't think this is supposed to be manually implemented. It's async for no reason, but closing should always be synchronous.
The iterator protocol methods like .next()
aren't a nice thing to directly expose on structures with other things, it would be better to have just [Symbol.asyncIterator]()
and maybe add an accept()
method to await one request like with Listener
. However we don't actually need to adjust the implementation from what it is now (other than removing .return()
) since it's probably more efficient -- that would also match Listener
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I have changed this around so that there is no return
method and that the requests iterator must be closed manually. Is this what you had in mind?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and that the requests iterator must be closed manually
The resource should still be closed automatically if connectionClosed
is true, if I seemed to have suggested otherwise. (re 336728c#diff-2b90f55ce18f5dd4bac8df7c9f0ff3a77558866bc8fcf02146dee2548b4948abL50-R54)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how to write the rewrite the .next()
calls with AsyncIterable<_>
. But to have a proper API for receiving one request, I suggest adding an equivalent of Listener.accept()
.
cli/dts/lib.deno.unstable.d.ts
Outdated
respondWith(r: Response | Promise<Response>): void; | ||
} | ||
|
||
export interface HttpConn extends AsyncIterableIterator<RequestEvent> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export interface HttpConn extends AsyncIterableIterator<RequestEvent> { | |
export interface HttpConn extends AsyncIterable<RequestEvent> { |
cli/tests/unit/http_test.ts
Outdated
const listener = Deno.listen({ port: 4501 }); | ||
const conn = await listener.accept(); | ||
const requests = Deno.startHttp(conn); | ||
const { value: { request, respondWith }, done } = await requests.next(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const { value: { request, respondWith }, done } = await requests.next(); | |
const { value: { request, respondWith }, done } = await requests[Symbol.asyncIterator]().next(); |
cli/tests/unit/http_test.ts
Outdated
const listener = Deno.listen({ port: 4501 }); | ||
const conn = await listener.accept(); | ||
const requests = Deno.startHttp(conn); | ||
const { value: { request, respondWith } } = await requests.next(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const { value: { request, respondWith } } = await requests.next(); | |
const { value: { request, respondWith } } = await requests[Symbol.asyncIterator]().next(); |
cli/tests/unit/http_test.ts
Outdated
// TODO(ry) If we don't call requests.next() here we get "error sending | ||
// request for url (https://localhost:4501/): connection closed before | ||
// message completed". | ||
const { done } = await requests.next(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const { done } = await requests.next(); | |
const { done } = await requests[Symbol.asyncIterator]().next(); |
cli/tests/unit/http_test.ts
Outdated
const listener = Deno.listen({ port: 4501 }); | ||
const conn = await listener.accept(); | ||
const requests = Deno.startHttp(conn); | ||
const { done } = await requests.next(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const { done } = await requests.next(); | |
const { done } = await requests[Symbol.asyncIterator]().next(); |
cli/tests/unit/http_test.ts
Outdated
}); | ||
const conn = await listener.accept(); | ||
const requests = Deno.startHttp(conn); | ||
const { value: { request, respondWith } } = await requests.next(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const { value: { request, respondWith } } = await requests.next(); | |
const { value: { request, respondWith } } = await requests[Symbol.asyncIterator]().next(); |
cli/tests/unit/http_test.ts
Outdated
// TODO(ry) If we don't call requests.next() here we get "error sending | ||
// request for url (https://localhost:4501/): connection closed before | ||
// message completed". | ||
const { done } = await requests.next(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const { done } = await requests.next(); | |
const { done } = await requests[Symbol.asyncIterator]().next(); |
@nayeemrmn That API seems rather annoying. Why shouldn't it be an |
I think generally nayeem is on the right path. It makes sense to me if the API looked like this: interface HttpConn extends AsyncIterable<RequestEvent> {
readonly rid: number;
next(): Promise<RequestEvent | null>;
close(): void;
} |
|
Co-authered-by: Luca Casonato <[email protected]> Co-authered-by: Ben Noordhuis <[email protected]> Co-authered-by: Ryan Dahl <[email protected]>
I'm in favour of the interface HttpConn extends AsyncIterable<RequestEvent> {
readonly rid: number;
accept(): Promise<RequestEvent>;
close(): void;
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ship it 🚀
if self.inner.borrow().is_some() { | ||
Poll::Pending | ||
} else { | ||
Poll::Ready(Ok(())) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe is Poll::Pending
is returned this task will never be woken up again
function readRequest(requestRid, zeroCopyBuf) { | ||
return Deno.core.jsonOpAsync( | ||
"op_http_request_read", | ||
requestRid, | ||
zeroCopyBuf, | ||
); | ||
} | ||
|
||
function respond(responseSenderRid, resp, zeroCopyBuf) { | ||
return Deno.core.jsonOpSync("op_http_response", [ | ||
responseSenderRid, | ||
resp.status ?? 200, | ||
flatEntries(resp.headers ?? {}), | ||
], zeroCopyBuf); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For a follow up: these two functions seem superfluous
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Although this looks like one large PR, this is actually the cumulation of months of attempts at building a binding to hyper. @bartlomieju drove this to completion but various proof-of-concepts were done by @bnoordhuis and @lucacasonato. The binding relies heavily on the serde_v8 optimization work that @AaronO has been doing - this would have been a much larger and slower undertaking without serde_v8.
Towards #3995