An open API service indexing awesome lists of open source software.

https://github.com/juxt/pick

A Clojure library for HTTP server-driven content negotiation.
https://github.com/juxt/pick

clojure content-negotation http web

Last synced: 5 months ago
JSON representation

A Clojure library for HTTP server-driven content negotiation.

Awesome Lists containing this project

README

          

= pick

A Clojure library for HTTP server-driven content negotiation.

[WARNING]
--
STATUS: Stable. In use.
--

== Quick Start

Suppose you are writing a web service and want to provide proactive content
negotiation based on the accept headers in the client request. Suppose also that
you have established which representations you want to choose between.

A representation is a map that one or more of the following keys:

* `:juxt.http/content-type` -- the media-type and charset of the representation
* `:juxt.http/content-encoding` -- how the representation has been encoded (e.g. `gzip`)
* `:juxt.http/content-language` -- the natural language of the representation (e.g. `es`)
* `:juxt.http/content-length` -- the length of the payload

.An example representation
====
[source,clojure]
----
{

{:juxt.http/content-type "text/html;charset=utf-8"
:juxt.http/content-encoding "gzip"
:juxt.http/content-language "en-US"
:juxt.http/content-length 20578}

}
----
====

You can use *pick* to select the representation that is most acceptable to the user agent.

Here's how:

[source,clojure]
----
(require '[juxt.pick.ring :refer [pick]])

(pick
;; A ring request (where headers indicate preferences)
{:request-method :get
:uri "/"
:headers {"accept" "text/html"
"accept-language" "de"}}

;; Possible representations
[
{:id 1
:juxt.http/content-type "text/html;charset=utf-8"
:juxt.http/content-language "en"}

{:id 2
:juxt.http/content-type "text/html;charset=utf-8"
:juxt.http/content-language "de"}

{:id 3
:juxt.http/content-type "text/plain;charset=utf-8"}])

=>
{:juxt.pick/representation {…} ; most acceptable representation
:juxt.pick/representations […] ; all representations (scored)
}

----

== Introduction

Content negotiation is a feature of HTTP that allows different 'representations'
to be served from the same URI.

*pick* is a Clojure library that determines quality values of each of the
negotiable dimensions and corresponding rules specified in RFC 7231. These can
be used by content negotiation algorithms to determine the most appropriate
content to respond with.

*pick* _also_ includes a built-in reference implementation based on Apache's
_de-facto_
https://httpd.apache.org/docs/current/en/content-negotiation.html#algorithm[content
negotiation algorithm].

=== Problem Statement

Content negotiation is one of the primary ways that the web is
inclusive, supporting a wide range of user agents and other software-based
clients.

Content negotiation can also contribute to improved performance, by allowing the
compression of content for optimising network bandwidth and by cache
integration.

Also, since content negotiation allows for gradual migration from old to new
data formats it enables graceful and continuous improvement, balancing the needs
for both innovation and solid dependability.

However, while content negotiation is often used for static resources, which is
well implemented by web servers such as Apache's httpd and nginx, it is often
missing from, or poorly implemented by, the majority of web libraries and
frameworks.

=== API Reference

The `pick` function 3 arguments:

* The Ring request
* A collection of possible representations
* An option map

The option map supports the following entries:

[cols="3m,5"]
|===
|:juxt.pick/explain?|if truthy, provide an explain in the return value
|:juxt.pick/vary?|if truthy, compute the preferences that will vary the choice
|===

=== API alternative: Use of metadata strings

Up until now, we have described a representation as a Clojure map with keywords in the `juxt.http` namespace.
Alternatively, a representation can be any object with Clojure metadata.
The metadata should contain the string keywords corresponding to the HTTP representation metadata.

[%header,cols="1m,1m"]
|===
|keyword|metadata string alternative
|:juxt.http/content-type|"content-type"
|:juxt.http/content-encoding|"content-encoding"
|:juxt.http/content-language|"content-language"
|:juxt.http/content-length|"content-length"
|===

For example, here is a representation defined as a Clojure function.

[source,clojure]
----
^{"content-type" "text/html;charset=utf-8"
"content-language" "en"}
(fn [] …)
----

=== Alternatives

Most Clojure alternatives only deal with media-types, and perhaps charsets. Note
that *pick* fully implements _all_ proactive (server-driven) content negotiation
rules contained in https://tools.ietf.org/html/rfc7231[RFC 7231], including
media-types, charsets, encodings, languages, wildcards, qvalues and precedence
rules. Where not prescribed specifically by the RFC, *pick* adopts the
_de-facto_ decisions made by the Apache httpd engine.

https://github.com/ngrunwald/ring-middleware-format[ring-middleware-format] and
Metosin's https://github.com/metosin/muuntaja[Muuntaja] library negotiate
media-types and charsets, and perform automatic decoding/encoding of
request/response bodies

[CAUTION]
--
*pick* only provides the decision of which representation(s) to select,
given the context of an HTTP request and a set of representation to choose between.

Unlike Muuntaja, *pick* does _not_ include support for format transformation or coercion, which is considered _out of scope_ for this library.
--

https://github.com/clojure-liberator/liberator/commits/master[Liberator]

https://github.com/juxt/yada[yada]

== References

https://tools.ietf.org/html/rfc7231[RFC 7231] is the normative standard on content negotiation.

This https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation[MDN guide on content negotiation from Mozilla] is very informative.

https://httpd.apache.org/docs/current/en/content-negotiation.html#algorithm

While *pick* attempts to be reasonably performant, due to the per-request nature
of content negotiation some users may consider using a memoization strategy,
making use of a memoization library such as
https://github.com/clojure/core.memoize[clojure.core.memoize].

== License

The MIT License (MIT)

Copyright © 2020-2023 JUXT LTD.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.