-
-
Notifications
You must be signed in to change notification settings - Fork 234
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
Fix generators, use text/event-stream
protocol
#743
base: main
Are you sure you want to change the base?
Conversation
What do you think @SaltyAom is this a breaking change or a bug fix? Previously the response was not following the |
Don't know if it's related, but having a better way to catch Aborts would be really great, instead of having to do something like |
@SaltyAom do you need any help with the review? I am already using this fork in production and it works great. Another problem of the current approach is that most servers won't flush the body until they find a new line, using the right |
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.
Hey, thanks a lot for your PR.
Would be great if you can help me with these changes and elysiajs/eden#114, after that let's get this merge.
src/handler.ts
Outdated
if (init.done) { | ||
if (set) return mapResponse(init.value, set, request) | ||
return mapCompactResponse(init.value, request) | ||
let init |
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.
You can remove try catch in here, handleStream
function is already wrapped in try-catch that handle onError already
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.
Currently the app will crash if you throw an error in a generator before yield, i added a test that reproduces this here: https://github.com/remorses/elysia/blob/932951679c89aef6bad08779b6eb7fe94c30335d/test/response/stream.test.ts#L75
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 fixed this crash removing the try catch when aot is false. In aot mode it still crashes.
the app crashes because the error is throws asynchronously while mapResponse is a not async, i think there are many more of these bugs hidden in the code, the ideal solution would be to always await mapResonse
in aot too
if (init?.value !== undefined && init?.value !== null) | ||
controller.enqueue( | ||
Buffer.from( | ||
`event: message\ndata: ${JSON.stringify(init.value)}\n\n` |
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.
Would make sense to check typeof object before using JSON.stringify
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.
If you don't always call JSON.stringify and the user yields a string that contains 2 new lines the response will break the text/event-stream protocol. If you always call JSON.stringify you can be sure that all the new lines are encoded with \n and put in a single line.
The parsing in Eden also becomes simpler because you know messages are always a JSON encoded string instead of using heuristics.
Yay! Thanks for the work) |
@SaltyAom @remorses In fact I don't think it should be up to elysia stream to handle exactly how to send the streams of the SSE interface, that should be left to the developer. It should be left to the developer, because exactly how to send each id, event, etc. should be set by the developer. For example I would like to handle the sent id by itself, it contains some of my rules, for example the id contains a timestamp, server id and a pointer. Or I want messages sent for different events to have different event names. Another example is that I may not need the id and event because ChatGPT's stream response only returns data and does not contain the id and event. there is also a case where I only want to send a single comment. In my opinion, all the above scenarios mean that the generator should only be responsible for sending the data as is, and not have to convert it into an SSE response. |
@HK-SHAO if you want customization you will need to implement it from scratch app.get('/events', async ({ set }) => {
set.headers['Content-Type'] = 'text/event-stream'
set.headers['Cache-Control'] = 'no-cache'
const stream = new ReadableStream({
async start(controller) {
for (let count = 0; count < 10; count++) {
const event = `data: Event ${count}\n\n`
controller.enqueue(event)
await Bun.sleep(1000)
}
controller.close()
}
})
return new Response(stream)
}) |
Yeah i agree. Elysia should folows specs but if you need sometning custom you can do it |
Fix #742
Fix #741