diff --git a/docs/examples/http_server.md b/docs/examples/http_server.md index 5c793c6ea2..af239dfd3c 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 8d9a8c5c6e..2ff0829eda 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 0000000000..b9a64a272d --- /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 9139124ff7..1bb863e31e 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" }