summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/examples/http_server.md68
-rw-r--r--docs/runtime.md4
-rw-r--r--docs/runtime/http_server_apis.md256
-rw-r--r--docs/toc.json1
4 files changed, 318 insertions, 11 deletions
diff --git a/docs/examples/http_server.md b/docs/examples/http_server.md
index 5c793c6ea..af239dfd3 100644
--- a/docs/examples/http_server.md
+++ b/docs/examples/http_server.md
@@ -2,25 +2,75 @@
## Concepts
-- Use the std library [http module](https://deno.land/std@$STD_VERSION/http) to
- run your own web server.
+- Use Deno's integrated HTTP server to run your own web server.
## 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.
+> ℹ️ 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
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";
-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/`);
for await (const request of server) {
@@ -31,7 +81,7 @@ for await (const request of server) {
}
```
-Run this with:
+Then run this with:
```shell
deno run --allow-net webserver.ts
diff --git a/docs/runtime.md b/docs/runtime.md
index 8d9a8c5c6..2ff0829ed 100644
--- a/docs/runtime.md
+++ b/docs/runtime.md
@@ -15,8 +15,8 @@ For more details, view the chapter on
## `Deno` global
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
-subprocesses, etc.
+It has the APIs for reading from files, opening TCP sockets, serving HTTP, and
+executing subprocesses, etc.
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)
diff --git a/docs/runtime/http_server_apis.md b/docs/runtime/http_server_apis.md
new file mode 100644
index 000000000..b9a64a272
--- /dev/null
+++ b/docs/runtime/http_server_apis.md
@@ -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.
diff --git a/docs/toc.json b/docs/toc.json
index 9139124ff..1bb863e31 100644
--- a/docs/toc.json
+++ b/docs/toc.json
@@ -21,6 +21,7 @@
"program_lifecycle": "Program lifecycle",
"permission_apis": "Permission APIs",
"web_platform_apis": "Web Platform APIs",
+ "http_server_apis": "HTTP Server APIs",
"location_api": "Location API",
"workers": "Workers"
}