mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 17:34:47 -05:00
docs: document Deno's HTTP Server API (#10280)
Co-authored-by: Satya Rohith <me@satyarohith.com>
This commit is contained in:
parent
3b78f6c449
commit
21372d7b25
4 changed files with 318 additions and 11 deletions
|
@ -2,25 +2,75 @@
|
||||||
|
|
||||||
## Concepts
|
## Concepts
|
||||||
|
|
||||||
- Use the std library [http module](https://deno.land/std@$STD_VERSION/http) to
|
- Use Deno's integrated HTTP server to run your own web server.
|
||||||
run your own web server.
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
With just a few lines of code you can run your own http web server with control
|
With just a few lines of code you can run your own HTTP web server with control
|
||||||
over the response status, request headers and more.
|
over the response status, request headers and more.
|
||||||
|
|
||||||
|
> ℹ️ The _native_ HTTP server is currently unstable, meaning the API is not
|
||||||
|
> finalized and may change in breaking ways in future version of Deno. To have
|
||||||
|
> the APIs discussed here available, you must run Deno with the `--unstable`
|
||||||
|
> flag.
|
||||||
|
|
||||||
## Sample web server
|
## Sample web server
|
||||||
|
|
||||||
In this example, the user-agent of the client is returned to the client:
|
In this example, the user-agent of the client is returned to the client:
|
||||||
|
|
||||||
```typescript
|
**webserver.ts**:
|
||||||
/**
|
|
||||||
* webserver.ts
|
```ts
|
||||||
*/
|
// Start listening on port 8080 of localhost.
|
||||||
|
const server = Deno.listen({ port: 8080 });
|
||||||
|
console.log(`HTTP webserver running. Access it at: http://localhost:8080/`);
|
||||||
|
|
||||||
|
// Connections to the server will be yielded up as an async iterable.
|
||||||
|
for await (const conn of server) {
|
||||||
|
// In order to not be blocking, we need to handle each connection individually
|
||||||
|
// in its own async function.
|
||||||
|
(async () => {
|
||||||
|
// This "upgrades" a network connection into an HTTP connection.
|
||||||
|
const httpConn = Deno.serveHttp(conn);
|
||||||
|
// Each request sent over the HTTP connection will be yielded as an async
|
||||||
|
// iterator from the HTTP connection.
|
||||||
|
for await (const requestEvent of httpConn) {
|
||||||
|
// The native HTTP server uses the web standard `Request` and `Response`
|
||||||
|
// objects.
|
||||||
|
const body = `Your user-agent is:\n\n${requestEvent.request.headers.get(
|
||||||
|
"user-agent",
|
||||||
|
) ?? "Unknown"}`;
|
||||||
|
// The requestEvent's `.respondWith()` method is how we send the response
|
||||||
|
// back to the client.
|
||||||
|
requestEvent.respondWith(
|
||||||
|
new Response(body, {
|
||||||
|
status: 200,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run this with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
deno run --allow-net --unstable webserver.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Then navigate to `http://localhost:8080/` in a browser.
|
||||||
|
|
||||||
|
### Using the `std/http` library
|
||||||
|
|
||||||
|
If you do not want to use the unstable APIs, you can still use the standard
|
||||||
|
library's HTTP server:
|
||||||
|
|
||||||
|
**webserver.ts**:
|
||||||
|
|
||||||
|
```ts
|
||||||
import { serve } from "https://deno.land/std@$STD_VERSION/http/server.ts";
|
import { serve } from "https://deno.land/std@$STD_VERSION/http/server.ts";
|
||||||
|
|
||||||
const server = serve({ hostname: "0.0.0.0", port: 8080 });
|
const server = serve({ port: 8080 });
|
||||||
console.log(`HTTP webserver running. Access it at: http://localhost:8080/`);
|
console.log(`HTTP webserver running. Access it at: http://localhost:8080/`);
|
||||||
|
|
||||||
for await (const request of server) {
|
for await (const request of server) {
|
||||||
|
@ -31,7 +81,7 @@ for await (const request of server) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Run this with:
|
Then run this with:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
deno run --allow-net webserver.ts
|
deno run --allow-net webserver.ts
|
||||||
|
|
|
@ -15,8 +15,8 @@ For more details, view the chapter on
|
||||||
## `Deno` global
|
## `Deno` global
|
||||||
|
|
||||||
All APIs that are not web standard are contained in the global `Deno` namespace.
|
All APIs that are not web standard are contained in the global `Deno` namespace.
|
||||||
It has the APIs for reading from files, opening TCP sockets, and executing
|
It has the APIs for reading from files, opening TCP sockets, serving HTTP, and
|
||||||
subprocesses, etc.
|
executing subprocesses, etc.
|
||||||
|
|
||||||
The TypeScript definitions for the Deno namespaces can be found in the
|
The TypeScript definitions for the Deno namespaces can be found in the
|
||||||
[`lib.deno.ns.d.ts`](https://github.com/denoland/deno/blob/$CLI_VERSION/cli/dts/lib.deno.ns.d.ts)
|
[`lib.deno.ns.d.ts`](https://github.com/denoland/deno/blob/$CLI_VERSION/cli/dts/lib.deno.ns.d.ts)
|
||||||
|
|
256
docs/runtime/http_server_apis.md
Normal file
256
docs/runtime/http_server_apis.md
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
## HTTP Server APIs
|
||||||
|
|
||||||
|
As of Deno 1.9 and later, _native_ HTTP server APIs were introduced which allow
|
||||||
|
users to create robust and performant web servers in Deno.
|
||||||
|
|
||||||
|
The API tries to leverage as much of the web standards as is possible as well as
|
||||||
|
tries to be simple and straight forward.
|
||||||
|
|
||||||
|
> ℹ️ The APIs are currently unstable, meaning they can change in the future in
|
||||||
|
> breaking ways and should be carefully considered before using in production
|
||||||
|
> code. They require the `--unstable` flag to make them available.
|
||||||
|
|
||||||
|
### Listening for a connection
|
||||||
|
|
||||||
|
In order to accept requests, first you need to listen for a connection on a
|
||||||
|
network port. To do this in Deno, you use `Deno.listen()`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const server = Deno.listen({ port: 8080 });
|
||||||
|
```
|
||||||
|
|
||||||
|
> ℹ️ When supplying a port, Deno assumes you are going to listen on a TCP socket
|
||||||
|
> as well as bind to the localhost. You can specify `transport: "tcp"` to be
|
||||||
|
> more explicit as well as provide an IP address or hostname in the `hostname`
|
||||||
|
> property as well.
|
||||||
|
|
||||||
|
If there is an issue with opening the network port, `Deno.listen()` will throw,
|
||||||
|
so often in a server sense, you will want to wrap it in the `try ... catch`
|
||||||
|
block in order to handle exceptions, like the port already being in use.
|
||||||
|
|
||||||
|
You can also listen for a TLS connection (e.g. HTTPS) using `Deno.listenTls()`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const server = Deno.listenTls({
|
||||||
|
port: 8443,
|
||||||
|
certFile: "localhost.crt",
|
||||||
|
keyFile: "localhost.key",
|
||||||
|
alpnProtocols: ["h2", "http/1.1"],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The `certFile` and `keyFile` options are required and point to the appropriate
|
||||||
|
certificate and key files for the server. They are relative to the CWD for Deno.
|
||||||
|
The `alpnProtocols` property is optional, but if you want to be able to support
|
||||||
|
HTTP/2 on the server, you add the protocols here, as the protocol negotiation
|
||||||
|
happens during the TLS negotiation with the client and server.
|
||||||
|
|
||||||
|
> ℹ️ Generating SSL certificates is outside of the scope of this documentation.
|
||||||
|
> There are many resources on the web which address this.
|
||||||
|
|
||||||
|
### Handling connections
|
||||||
|
|
||||||
|
Once we are listening for a connection, we need to handle the connection. The
|
||||||
|
return value of `Deno.listen()` or `Deno.listenTls()` is a `Deno.Listener` which
|
||||||
|
is an async iterable which yields up `Deno.Conn` connections as well as provide
|
||||||
|
a couple methods for handling connections.
|
||||||
|
|
||||||
|
To use it as an async iterable we would do something like this:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const server = Deno.listen({ port: 8080 });
|
||||||
|
|
||||||
|
for await (const conn of server) {
|
||||||
|
// ...handle the connection...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Every connection made would yielded up a `Deno.Conn` assigned to `conn`. Then
|
||||||
|
further processing can be applied to the connection.
|
||||||
|
|
||||||
|
There is also the `.accept()` method on the listener which can be used:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const server = Deno.listen({ port: 8080 });
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const conn = server.accept();
|
||||||
|
if (conn) {
|
||||||
|
// ... handle the connection ...
|
||||||
|
} else {
|
||||||
|
// The listener has closed
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Whether using the async iterator or the `.accept()` method, exceptions can be
|
||||||
|
thrown and robust production code should handle these using `try ... catch`
|
||||||
|
blocks. Especially when it comes to accepting TLS connections, there can be many
|
||||||
|
conditions, like invalid or unknown certificates which can be surfaced on the
|
||||||
|
listener and might need handling in the user code.
|
||||||
|
|
||||||
|
A listener also has a `.close()` method which can be used to close the listener.
|
||||||
|
|
||||||
|
### Serving HTTP
|
||||||
|
|
||||||
|
Once a connection is accepted, you can use `Deno.serveHttp()` to handle HTTP
|
||||||
|
requests and responses on the connection. `Deno.serveHttp()` returns a
|
||||||
|
`Deno.HttpConn`. A `Deno.HttpConn` is like a `Deno.Listener` in that requests
|
||||||
|
the connection receives from the client are asynchronously yielded up as a
|
||||||
|
`Deno.RequestEvent`.
|
||||||
|
|
||||||
|
To deal with HTTP requests as async iterable it would look something like this:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const server = Deno.listen({ port: 8080 });
|
||||||
|
|
||||||
|
for await (const conn of server) {
|
||||||
|
(async () => {
|
||||||
|
const httpConn = Deno.serveHttp(conn);
|
||||||
|
for await (const requestEvent of httpConn) {
|
||||||
|
// ... handle requestEvent ...
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Deno.HttpConn` also has the method `.nextRequest()` which can be used to
|
||||||
|
await the next request. It would look something like this:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const server = Deno.listen({ port: 8080 });
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const conn = server.accept();
|
||||||
|
if (conn) {
|
||||||
|
(async () => {
|
||||||
|
const httpConn = Deno.serveHttp(conn);
|
||||||
|
while (true) {
|
||||||
|
const requestEvent = await httpConn.nextRequest();
|
||||||
|
if (requestEvent) {
|
||||||
|
// ... handle requestEvent ...
|
||||||
|
} else {
|
||||||
|
// the connection has finished
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
} else {
|
||||||
|
// The listener has closed
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that in both cases we are using an IIFE to create an inner function to deal
|
||||||
|
with each connection. If we awaited the HTTP requests in the same function scope
|
||||||
|
as the one we were receiving the connections, we would be blocking accepting
|
||||||
|
additional connections, which would make it seem that our server was "frozen".
|
||||||
|
In practice, it might make more sense to have a separate function all together:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
async function handle(conn: Deno.Conn) {
|
||||||
|
const httpConn = Deno.serveHttp(conn);
|
||||||
|
for await (const requestEvent of httpConn) {
|
||||||
|
// ... handle requestEvent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = Deno.listen({ port: 8080 });
|
||||||
|
|
||||||
|
for await (const conn of server) {
|
||||||
|
handle(conn);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the examples from this point on, we will focus on what would occur within an
|
||||||
|
example `handle()` function and remove the listening and connection
|
||||||
|
"boilerplate".
|
||||||
|
|
||||||
|
### HTTP Requests and Responses
|
||||||
|
|
||||||
|
HTTP requests and responses in Deno are essentially the inverse of web standard
|
||||||
|
[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). The
|
||||||
|
Deno HTTP Server API and the Fetch API leverage the
|
||||||
|
[`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) and
|
||||||
|
[`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) object
|
||||||
|
classes. So if you are familiar with the Fetch API you just need to flip them
|
||||||
|
around in your mind and now it is a server API.
|
||||||
|
|
||||||
|
As mentioned above, a `Deno.HttpConn` asynchronously yields up
|
||||||
|
`Deno.RequestEvent`s. These request events contain a `.request` property and a
|
||||||
|
`.respondWith()` method.
|
||||||
|
|
||||||
|
The `.request` property is an instance of the `Request` class with the
|
||||||
|
information about the request. For example, if we wanted to know what URL path
|
||||||
|
was being requested, we would do something like this:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
async function handle(conn: Deno.Conn) {
|
||||||
|
const httpConn = Deno.serveHttp(conn);
|
||||||
|
for await (const requestEvent of httpConn) {
|
||||||
|
const url = new URL(requestEvent.request.url);
|
||||||
|
console.log(`path: ${url.path}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `.respondWith()` method is how we complete a request. The method takes
|
||||||
|
either a `Response` object or a `Promise` which resolves with a `Response`
|
||||||
|
object. Responding with a basic "hello world" would look like this:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
async function handle(conn: Deno.Conn) {
|
||||||
|
const httpConn = Deno.serveHttp(conn);
|
||||||
|
for await (const requestEvent of httpConn) {
|
||||||
|
await requestEvent.respondWith(new Response("hello world"), {
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that we awaited the `.respondWith()` method. It isn't required, but in
|
||||||
|
practice any errors in processing the response will cause the promise returned
|
||||||
|
from the method to be rejected, like if the client disconnected before all the
|
||||||
|
response could be sent. While there may not be anything your application needs
|
||||||
|
to do, not handling the rejection will cause an "unhandled rejection" to occur
|
||||||
|
which will terminate the Deno process, which isn't so good for a server. In
|
||||||
|
addition, you might want to await the promise returned in order to determine
|
||||||
|
when to do any cleanup from for the request/response cycle.
|
||||||
|
|
||||||
|
The web standard `Response` object is pretty powerful, allowing easy creation of
|
||||||
|
complex and rich responses to a client, and Deno strives to provide a `Response`
|
||||||
|
object that as closely matches the web standard as possible, so if you are
|
||||||
|
wondering how to send a particular response, checkout out the documentation for
|
||||||
|
the web standard
|
||||||
|
[`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response).
|
||||||
|
|
||||||
|
### HTTP/2 Support
|
||||||
|
|
||||||
|
HTTP/2 support is effectively transparent within the Deno runtime. Typically
|
||||||
|
HTTP/2 is negotiated between a client and a server during the TLS connection
|
||||||
|
setup via
|
||||||
|
[ALPN](https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation). To
|
||||||
|
enable this, you need to provide the protocols you want to support when you
|
||||||
|
start listening via the `alpnProtocols` property. This will enable the
|
||||||
|
negotiation to occur when the connection is made. For example:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const server = Deno.listenTls({
|
||||||
|
port: 8443,
|
||||||
|
certFile: "localhost.crt",
|
||||||
|
keyFile: "localhost.key",
|
||||||
|
alpnProtocols: ["h2", "http/1.1"],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The protocols are provided in order of preference. In practice, the only two
|
||||||
|
protocols that are supported currently are HTTP/2 and HTTP/1.1 which are
|
||||||
|
expressed as `h2` and `http/1.1`.
|
||||||
|
|
||||||
|
Currently Deno does not support upgrading a plain-text HTTP/1.1 connection to an
|
||||||
|
HTTP/2 cleartext connection via the `Upgrade` header (see:
|
||||||
|
[#10275](https://github.com/denoland/deno/issues/10275)), so therefore HTTP/2
|
||||||
|
support is only available via a TLS/HTTPS connection.
|
|
@ -21,6 +21,7 @@
|
||||||
"program_lifecycle": "Program lifecycle",
|
"program_lifecycle": "Program lifecycle",
|
||||||
"permission_apis": "Permission APIs",
|
"permission_apis": "Permission APIs",
|
||||||
"web_platform_apis": "Web Platform APIs",
|
"web_platform_apis": "Web Platform APIs",
|
||||||
|
"http_server_apis": "HTTP Server APIs",
|
||||||
"location_api": "Location API",
|
"location_api": "Location API",
|
||||||
"workers": "Workers"
|
"workers": "Workers"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue