max
max•8mo ago

With Deno.serveHttp marked as deprecated is there a way to run an HTTP server off of a Deno.Conn?

I have a bit of weird use-case where I want to start a listener, accept the first TCP connection (and serve HTTP to it) and then reject all future TCP connections. I have the following code:
const server = Deno.listen({
hostname: "0.0.0.0",
port: 0,
});
const conn = await server.accept();
(async () => {
// Reject all additional connections.
for await (const conn of server) {
conn.close();
}
})();
const httpConn = Deno.serveHttp(conn);
for await (const requestEvent of httpConn) {
// etc...
const server = Deno.listen({
hostname: "0.0.0.0",
port: 0,
});
const conn = await server.accept();
(async () => {
// Reject all additional connections.
for await (const conn of server) {
conn.close();
}
})();
const httpConn = Deno.serveHttp(conn);
for await (const requestEvent of httpConn) {
// etc...
Is there a way to do this using Deno.serve, or something else?
7 Replies
Deno AI Helper
Deno AI Helper•8mo ago
The documentation indicates that Deno.serveHttp() is indeed used to handle HTTP requests and responses on a connection. It returns a Deno.HttpConn, which is an async iterable that yields Deno.RequestEvent objects for each HTTP request that the connection receives. Your use case, where you want to accept the first TCP connection to serve HTTP and then reject all future TCP connections, is supported by the Deno.serveHttp() function. Your current code snippet is correctly using Deno.serveHttp() to serve HTTP over the first accepted connection and then closes any subsequent connections. As for using Deno.serve, it is a higher-level API that abstracts away the details of creating a listener and directly serves HTTP. However, it does not provide the same level of control over individual connections as manually creating a listener and using Deno.serveHttp() does. Given your specific requirement to accept only the first connection and reject others, continuing to use Deno.serveHttp() as you are currently doing is the appropriate approach. There is no need to switch to Deno.serve unless you want to simplify your code and do not need to manage individual TCP connections. Here is a simplified version of your code that demonstrates the intended behavior using Deno.serveHttp():
const server = Deno.listen({
hostname: "0.0.0.0",
port: 0,
});

// Accept the first connection and serve HTTP.
const conn = await server.accept();
const httpConn = Deno.serveHttp(conn);
for await (const requestEvent of httpConn) {
// Handle the HTTP request event.
}

// Close the server to reject all future connections.
server.close();
const server = Deno.listen({
hostname: "0.0.0.0",
port: 0,
});

// Accept the first connection and serve HTTP.
const conn = await server.accept();
const httpConn = Deno.serveHttp(conn);
for await (const requestEvent of httpConn) {
// Handle the HTTP request event.
}

// Close the server to reject all future connections.
server.close();
In this example, after serving HTTP on the first connection, the server is closed, which will prevent any new connections from being
Deno AI Helper
Deno AI Helper•8mo ago
accepted. This aligns with your requirement to serve only the first connection. 🦕
max
maxOP•8mo ago
The AI is helpful but seems less afraid of the looming 2.0 soft removal
bartlomieju
bartlomieju•7mo ago
@mmastrac any suggestion here?
mmastrac
mmastrac•7mo ago
I think we should probably add a Deno.serveOn API, but in the meantime you could accept the connection manually and proxy it to your internal Deno.serve listener that is listening only locally. I have a feeling Deno.serveHttp will be around for a while, so I wouldn't be too afraid of using it. We don't have a specific removal date for that API and it's pretty heavily used.
max
maxOP•7mo ago
ok, sounds good, thanks. I am a little sensitive to the overhead of spawning another connection to proxy to Deno.serve, but will keep using serveHttp for now. Would be very interested in a Deno.serveOn api.
pihentagy
pihentagy•6mo ago
I also have this usecase, when I'm requesting user consent for accessing google account. So were there any solutions to a one-off serving? My code btw:
async function getCodeFromServing(port = 3000) {
console.log(`Waiting for a request on port ${port}`);
const conn = await Deno.listen({ port }).accept();

const requestEvent = await Deno.serveHttp(conn).nextRequest();
if (!requestEvent) throw new Error("Oh no, connetion closed");

const code = new URL(requestEvent.request.url).searchParams.get("code");

await requestEvent.respondWith(new Response("hello", { status: 200 }));
return code;
}
async function getCodeFromServing(port = 3000) {
console.log(`Waiting for a request on port ${port}`);
const conn = await Deno.listen({ port }).accept();

const requestEvent = await Deno.serveHttp(conn).nextRequest();
if (!requestEvent) throw new Error("Oh no, connetion closed");

const code = new URL(requestEvent.request.url).searchParams.get("code");

await requestEvent.respondWith(new Response("hello", { status: 200 }));
return code;
}
I modified it, but needs an extra setTimeout for server.shutdown, of throws an error
async function getCodeFromServing(port = 3000) {
let code: string | null = null;
const server = Deno.serve({ port }, (req) => {
code = new URL(req.url).searchParams.get("code");
console.info(`Got code ${code}\nAbout to shut down the server on port ${port}`)
server.shutdown(); // FIXME needs to be setTimeout(()=>server.shutdown()) to work
return new Response("Thanks for stopping by");
});

await server.finished;
return code;
}
async function getCodeFromServing(port = 3000) {
let code: string | null = null;
const server = Deno.serve({ port }, (req) => {
code = new URL(req.url).searchParams.get("code");
console.info(`Got code ${code}\nAbout to shut down the server on port ${port}`)
server.shutdown(); // FIXME needs to be setTimeout(()=>server.shutdown()) to work
return new Response("Thanks for stopping by");
});

await server.finished;
return code;
}
About to shut down the server on port 3000
error: Uncaught (in promise) Interrupted: operation canceled
About to shut down the server on port 3000
error: Uncaught (in promise) Interrupted: operation canceled