I'm looking for a very simple async HTTP server. Something like tiny_http, but async.
Context: I'm using OAuth2. That means I have to spawn an HTTP server, which should only wait for the very first connection, read out the URL query parameters, respond with "Authorization succesfull", and then close the server. Right now I do it with tiny_http:
fn catch_redirect() -> anyhow::Result<RedirectArgs> {
// listen for request
let server = Server::http(SOCKET_ADDRESS).map_err(anyhow::Error::from_boxed)?;
let request = server.recv()?;
// extract params
let url = format!("http://localhost{}", request.url());
let args = extract_args(url);
// respond
let response = match &args {
Ok(_) => Response::from_string(
"Authorization successful! You can now close this tab and return to the app.",
),
Err(err) => Response::from_string(format!("Error: {}", err)),
};
request.respond(response)?;
args
}
As you can see, it really only responds to the first request and then closes the server. (In this case by dropping it.)
Now I want to build it pretty much exactly like this, but in async. That means, the only difference really should be that server.recv() would be replaced by something like server.recv().await. I can't just use tokio::task::spawn_blocking(...), because I need to be able to abort the task when the user cancels the OAuth2 process. That's the reason I need async.
But unfortunately, I can't find a good crate for that. The go-to async HTTP server seems to be hyper, but it seems like it requires this Service architecture. A Service responds to all incoming requests. That would make it unnecessarily complicated to realize my use case: I would need something like a oneshot channel or so and then abort the server from "externally". Instead I really just want something like server.receive().await, so that it doesn't even care about the second request.
I don't see why async makes a difference for cencelation.
are you using an embedded webview or the system browser?
if you are using an external browser, how to you know the user canceled the login process? e.g. if the user just close the browser tab/window, you will not get notified in any way.
if you are using an embedded webview to present the login page, then you know when it was canceled. but still, why async? [1]
in either case, I don't think async is the right solution for your problem. this async server thing feels like an "xy problem" to me.
no, it was just listening on localhost for the oauth response, which is a http redirect (a callback, if you will), to extract the authentication code/token.
side note: if you use embedded webview, you don't really need a server to handle the redirect, you can just extract the redirect uri from the webview ↩︎
@nerditation No, async really is important, in order to be able to abort the task.
Let me explain it in more detail. I have a GUI application. It (optionally) allows the user to connect it to their e.g. GitHub account. So there is a button "Sign In". Now when the user clicks the button, this is what happens:
I automatically open the browser (not embedded, but doesn't matter) with the correct GitHub OAuth web page where the user can sign in.
I open an HTTP server on localhost:12345 in order to wait for the redirect of the user.
On my GUI application I change the screen to "Continue in the browser.." and I have a button "Cancel OAuth".
Now the problem is, what happens when the user clicks "cancel"? There might be multiple reasons for this, e.g.:
they forgot their login credentials
they don't have an internet connection, so the login page doesn't load
they don't like how many permission scopes I request for their GitHub account
...
When they cancel, what I need to do is I need to shutdown the HTTP server. After all I don't expect a redirect anymore. And the crucial point is:
If the HTTP server is sync and blocks when waiting for the request, you can't shut it down. You can't abort a blocking function, or shutdown a thread or something like this.
On the other hand, if it waits async for the request, then it can be aborted. (async creates natural "pause points", the await points, and I can simply shut it down when I'm at an await. In technical terms, i can just stop polling the future.)
So that's why I need async. And no, I can't simply keep the HTTP server running. For multiple reasons which are not important here, but you can really really trust me that I can't keep the HTTP server running. I need to shut it down.
I'm trying to understand why you can't do what you need with tiny_http. I see the Server type has try_recv method which would allow you to loop and check a cancel flag. And there is also unblock if you wanted to continue using recv.
I haven't used tiny_http so I may have a misunderstanding.
see, this is an xy problem after all. what you want to do is to shutdown the http server, but you asked a question about async http server.
there are plenty of ways you can cancel socket IO and shutdown a server, one of the first ideas is to force the accept() or recv() call to fail, this is doable, but it is a bit hacky since platform specific tricks are required, so I wouldn't recommend it.
instead, I would suggest just send a (either invalid or empty, or predefined) request to the socket and the server will naturally return an error.
another workaround is to use a polling based server loop (with timeouts).
however, the best part is no part. you can get rid of the http server completely if you use the device flow, but you need to enable device flow for your oauth app registration first.
personally, I prefer device flow for native programs ("native" meaning not a web app). see github's documentation for details:
I open an HTTP server on localhost:12345 in order to wait for the redirect of the user.
Unfortunately various sites might send garbage requests to localhost too - perhaps trying to track users or understand their network topology & stack - so it's not guaranteed that the first request will be the relevant one.