Skip to content

Commit

Permalink
[NO-ISSUE] Deliver content negotiation (#8)
Browse files Browse the repository at this point in the history
- feat: deliver content negotiation with the `res.format()` and `res.vary()` methods.
- feat: flesh out majority of missing `request` methods and properties:
  - `req.accepts()`
  - `req.acceptsCharsets()`
  - `req.acceptsEncodings()`
  - `req.acceptsLanguages()`
  - `req.is()`
  - `req.protocol`
  - `req.secure`
  - `req.subdomains`
  - `req.path`
  - `req.hostname`
  - `req.stale`
  - `req.xhr`
- chore: update to types.
- chore: add a content negotiation example to the examples.
- chore: update the docs with the new methods / properties.
  • Loading branch information
asos-craigmorten authored May 29, 2020
1 parent 0a1804e commit 7f39aab
Show file tree
Hide file tree
Showing 74 changed files with 4,665 additions and 856 deletions.
130 changes: 130 additions & 0 deletions .github/API/request.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,16 @@ console.dir(req.fresh);
// => true
```
#### req.hostname
Contains the hostname derived from the `Host` HTTP header.
```ts
// Host: "example.com:3000"
console.dir(req.hostname);
// => 'example.com'
```
#### req.method
Contains a string corresponding to the HTTP method of the request: `GET`, `POST`, `PUT`, and so on.
Expand Down Expand Up @@ -194,6 +204,15 @@ console.dir(req.path);
> When called from a middleware, the mount point is not included in `req.path`. See [app.use()](./application.md#appusepath-callback--callback) for more details.
#### req.protocol
Contains the request protocol string: either `http` or (for TLS requests) `https`.
```ts
console.dir(req.protocol);
// => 'http'
```
#### req.query
This property is an object containing a property for each query string parameter in the route.
Expand Down Expand Up @@ -246,8 +265,92 @@ Example output from the previous snippet:
methods: { get: true } }
```
#### req.secure
A Boolean property that is true if a TLS connection is established. Equivalent to:
```js
console.dir(req.protocol === "https");
// => true
```
#### req.stale
Indicates whether the request is "stale," and is the opposite of `req.fresh`. For more information, see [req.fresh](#req.fresh).
```ts
console.dir(req.stale);
// => true
```
#### req.subdomains
An array of subdomains in the domain name of the request.
```ts
// Host: "deno.dinosaurs.example.com"
console.dir(req.subdomains);
// => ['dinosaurs', 'deno']
```
The application property `subdomain offset`, which defaults to 2, is used for determining the
beginning of the subdomain segments. To change this behavior, change its value
using [app.set](/{{ page.lang }}/4x/api.html#app.set).
#### req.xhr
A Boolean property that is `true` if the request's `X-Requested-With` header field is "XMLHttpRequest", indicating that the request was issued by a client library such as jQuery.
```js
console.dir(req.xhr);
// => true
```
### Methods
#### req.accepts(types)
Checks if the specified content types are acceptable, based on the request's `Accept` HTTP header field. The method returns the best match, or if none of the specified content types is acceptable, returns empty (in which case, the application should respond with `406 "Not Acceptable"`).
The `type` value may be a single MIME type string (such as "application/json"), an extension name such as "json", a comma-delimited list, or an array. For a list or array, the method returns the _best_ match (if any).
```ts
// Accept: text/html
req.accepts("html");
// => "html"
// Accept: text/*, application/json
req.accepts("html");
// => "html"
req.accepts("text/html");
// => "text/html"
req.accepts(["json", "text"]);
// => "json"
req.accepts("application/json");
// => "application/json"
// Accept: text/*, application/json
req.accepts("image/png");
req.accepts("png");
// => false
// Accept: text/*;q=.5, application/json
req.accepts(["html", "json"]);
// => "json"
```
#### req.acceptsCharsets(charset [, ...])
Returns the first accepted charset of the specified character sets, based on the request's `Accept-Charset` HTTP header field. If none of the specified charsets is accepted, returns empty.
#### req.acceptsEncodings(encoding [, ...])
Returns the first accepted encoding of the specified encodings, based on the request's `Accept-Encoding` HTTP header field. If none of the specified encodings is accepted, returns empty.
#### req.acceptsLanguages(lang [, ...])
Returns the first accepted language of the specified languages, based on the request's `Accept-Language` HTTP header field. If none of the specified languages is accepted, returns empty.
#### req.get(field)
Returns the specified HTTP request header field (case-insensitive match). The `Referrer` and `Referer` fields are interchangeable.
Expand All @@ -262,3 +365,30 @@ req.get("content-type");
req.get("Something");
// => undefined
```
#### req.is(type)
Returns the matching content type if the incoming request's "Content-Type" HTTP header field matches the MIME type specified by the `type` parameter. If the request has no body, returns `null`. Returns `false` otherwise.
```js
// With Content-Type: text/html; charset=utf-8
req.is('html')
// => 'html'
req.is('text/html')
// => 'text/html'
req.is('text/*')
// => 'text/*'
// When Content-Type is application/json
req.is('json')
// => 'json'
req.is('application/json')
// => 'application/json'
req.is('application/*')
// => 'application/*'
req.is('html')
// => false
```
For more information, or if you have issues or concerns, see [type-is](https://github.com/expressjs/type-is).
55 changes: 55 additions & 0 deletions .github/API/response.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,53 @@ Sets the response `ETag` HTTP header using the specified `data` parameter. The e
res.etag(fileStat);
```

### res.format(object)

Performs content-negotiation on the `Accept` HTTP header on the request object, when present. It uses `req.accepts()` to select a handler for the request, based on the acceptable types ordered by their quality values. If the header is not specified, the first callback is invoked. When no match is found, the server responds with 406 "Not Acceptable", or invokes the `default` callback.

The `Content-Type` response header is set when a callback is selected. However, you may alter this within the callback using methods such as `res.set()` or `res.type()`.

The following example would respond with `{ "message": "hey" }` when the `Accept` header field is set to "application/json" or "\*/json" (however if it is "\*/\*", then the response will be "hey").

```ts
res.format({
"text/plain": function () {
res.send("hey");
},

"text/html": function () {
res.send("<p>hey</p>");
},

"application/json": function () {
res.send({ message: "hey" });
},

default: function () {
// log the request and respond with 406
res.status(406).send("Not Acceptable");
},
});
```

In addition to canonicalized MIME types, you may also use extension names mapped to these types for a slightly less verbose implementation:

```ts
res.format({
text: function () {
res.send("hey");
},

html: function () {
res.send("<p>hey</p>");
},

json: function () {
res.send({ message: "hey" });
},
});
```

#### res.get(field)

Returns the HTTP response header specified by `field`. The match is case-insensitive.
Expand Down Expand Up @@ -417,3 +464,11 @@ Remove the response's HTTP header `field`.
```ts
res.set("Content-Type");
```

#### res.vary(field)

Adds the field to the `Vary` response header, if it is not there already.

```ts
res.vary("User-Agent");
```
20 changes: 20 additions & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# ChangeLog

## [0.6.0] - 29-05-2020

- feat: deliver content negotiation with the `res.format()` and `res.vary()` methods.
- feat: flesh out majority of missing `request` methods and properties:
- `req.accepts()`
- `req.acceptsCharsets()`
- `req.acceptsEncodings()`
- `req.acceptsLanguages()`
- `req.is()`
- `req.protocol`
- `req.secure`
- `req.subdomains`
- `req.path`
- `req.hostname`
- `req.stale`
- `req.xhr`
- chore: update to types.
- chore: add a content negotiation example to the examples.
- chore: update the docs with the new methods / properties.

## [0.5.4] - 28-05-2020

- fix: better types for `RouterConstructor`.
Expand Down
13 changes: 1 addition & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,6 @@ This is a [Deno](https://deno.land/) module available to import direct from this

Before importing, [download and install Deno](https://deno.land/#installation).

> Please refer to the [version file](./version.ts) for a list of Deno versions supported by Opine.
>
> Once Deno is installed, you can easily switch between Deno versions using the `upgrade` command:
>
> ```bash
> # Upgrade to latest version:
> deno upgrade
>
> # Upgrade to a specific version, replace `<version>` with the version you want (e.g. `1.0.0`):
> deno upgrade --version <version>
> ```
You can then import Opine straight into your project:

```ts
Expand All @@ -58,6 +46,7 @@ import opine from "https://deno.land/x/opine@c21f8d6/mod.ts";
- Robust routing
- Focus on high performance
- HTTP helpers
- Content negotiation

And more to come as we achieve feature parity with [ExpressJS](https://github.com/expressjs/express).

Expand Down
5 changes: 5 additions & 0 deletions deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@ export {
charset,
} from "https://deno.land/x/[email protected]/mod.ts";
export { createHttpError } from "https://deno.land/x/[email protected]/httpError.ts";
export { Accepts } from "https://deno.land/x/accepts@master/mod.ts";
export { typeofrequest } from "https://deno.land/x/type_is@master/mod.ts";
export { isIP } from "https://deno.land/x/isIP@master/mod.ts";
export { vary } from "https://deno.land/x/vary@master/mod.ts";
export { lookup } from "https://deno.land/x/[email protected]/mod.ts";
2 changes: 1 addition & 1 deletion docs/assets/js/search.json

Large diffs are not rendered by default.

Loading

0 comments on commit 7f39aab

Please sign in to comment.