xyproto / algernon

Small self-contained pure-Go web server with Lua, Teal, Markdown, Ollama, HTTP/2, QUIC, Redis, SQLite and PostgreSQL support ++
https://algernon.roboticoverlords.org
BSD 3-Clause "New" or "Revised" License
2.82k stars 138 forks source link

ability to close the response stream early #124

Open diyism opened 2 years ago

diyism commented 2 years ago

In nginx-lua/openresty, I can close the http response early (avoid my users waiting) and continue doing things that cost time on my server:

ngx.say("bye bye, my user")
ngx.eof()
local http=require"httpc".create()
http:request{method="GET", url="https://a web page of very long content"}
local data,err=http:read"*a"

Does the algernon web server have such an API? for example "end()":

print("bye bye, my user")
flush()
end()
hc = HTTPClient()
hc:Get("https://a web page of very long content")
xyproto commented 2 years ago

Thanks for reporting! I think this is a good idea.

diyism commented 2 years ago

Indeed, I've tested the two mini lua web servers: redbean and mako, both of them have an async method now: redbean: https://github.com/jart/cosmopolitan/issues/615#issuecomment-1250253390 mako: https://github.com/RealTimeLogic/BAS/issues/4#issuecomment-1250271813

xyproto commented 2 years ago

end is already a reserved keyword in Lua, but I added a close() function. It sends a Connection: close header to the connected client, flushes the body and tries to stop Lua functions from writing any more data to the client.

It is also possible to get hold of the net.Conn and then close that one, but it's a tiny bit more involved, since one has to set up a ConnContext field for all involved Server structs that are in use in Algernon.

Please test if the current functionality in the main branch is sufficient to solve this issue. Thanks.

diyism commented 2 years ago

Thanks for your response. I've tested it with the following:

$ git clone --depth 1 https://github.com/xyproto/algernon/
$ cd algernon
$ go build -mod=vendor
$ sudo cp algernon /usr/local/bin/
$ mkdir www
$ cd www
$ cat sleep.lua
sleep(3)
print("welcome, my user")
$ cat index.lua
print("bye bye, my user")
close()
GET("http://127.0.0.1:3000/sleep.lua")
$ algernon

then I visit http://127.0.0.1:3000/index.lua it still cost about 3 seconds to show "bye bye, my user" it seems there is a problem somewhere.

xyproto commented 2 years ago

Thanks for testing! I'll add code to close the actual connection.

myselfghost commented 1 month ago

Thanks for testing! I'll add code to close the actual connection.

This problem has not been solved yet

xyproto commented 1 month ago

@myselfghost Hi! If the current implementation does not work, I want to fix it.

Is the connection not properly closed when the close() function is called?

Do you have an example index.lua file that demonstrates the issue, together with what the expected result should be?

I looked at the example above that @diyism posted, and when testing here, the close() function seems to work as it should.

myselfghost commented 1 month ago

@myselfghost Hi! If the current implementation does not work, I want to fix it.

Is the connection not properly closed when the close() function is called?

Do you have an example index.lua file that demonstrates the issue, together with what the expected result should be?

I looked at the example above that @diyism posted, and when testing here, the close() function seems to work as it should.

an example index.lua file

print("bye bye, my user")
close()
sleep(5)

expected result: The browser should immediately display the string "bye bye, my user"

Actual behavior: The browser waits 5 seconds before displaying the string

@xyproto

xyproto commented 1 month ago

@myselfghost I see! I can reproduce the issue now. Even with:

print("bye bye, my user")
flush()
close()
sleep(5)

Then it takes 5 seconds for the message to appear in the browser. Thanks for the clarification!

xyproto commented 1 month ago

I experimented a bit with flushing, and it was not a problem with regular net/http, but it is more involved when fasthttp is used.

Dropping fasthttp is one option. Also supporting fasthttp handlers that uses func(ctx *fasthttp.RequestCtx) handlers is another (here's an example).

I'll have to think a bit about how to best solve this.

xyproto commented 2 days ago

I'm planning to check if https://github.com/cloudwego/hertz can be used instead of fasthttp, and if it supports flushing.

diyism commented 2 days ago

Great, the "hertz" library seems promising.