━━━━━━━━
PLZ.EL
━━━━━━━━
[file:http://elpa.gnu.org/packages/plz.svg]
`plz' is an HTTP library for Emacs. It uses `curl' as a backend, which
avoids some of the issues with using Emacs's built-in `url' library. It
supports both synchronous and asynchronous requests. Its API is
intended to be simple, natural, and expressive. Its code is intended to
be simple and well-organized. Every feature is tested against
[httpbin].
[file:http://elpa.gnu.org/packages/plz.svg]
<http://elpa.gnu.org/packages/plz.html>
[httpbin] <https://httpbin.org/>
1 Installation
══════════════
1.1 GNU ELPA
────────────
`plz' is available in [GNU ELPA]. It may be installed in Emacs using
the `package-install' command.
[GNU ELPA] <http://elpa.gnu.org/packages/plz.html>
1.2 Manual
──────────
`plz' has no dependencies other than Emacs and `curl'. It's known to
work on Emacs 26.3 or later. To install it manually, simply place
`plz.el' in your `load-path' and `(require 'plz)'.
2 Usage
═══════
The main public function is `plz', which sends an HTTP request and
returns either the result of the specified type (for a synchronous
request), or the `curl' process object (for asynchronous requests).
For asynchronous requests, callback, error-handling, and finalizer
functions may be specified, as well as various other options.
2.1 Examples
────────────
Synchronously `GET' a URL and return the response body as a decoded
string (here, raw JSON):
┌────
│ (plz 'get "https://httpbin.org/user-agent")
└────
┌────
│ "{\n \"user-agent\": \"curl/7.35.0\"\n}\n"
└────
Synchronously `GET' a URL that returns a JSON object, and parse and
return it as an alist:
┌────
│ (plz 'get "https://httpbin.org/get" :as #'json-read)
└────
┌────
│ ((args)
│ (headers
│ (Accept . "*/*")
│ (Accept-Encoding . "deflate, gzip")
│ (Host . "httpbin.org")
│ (User-Agent . "curl/7.35.0"))
│ (url . "https://httpbin.org/get"))
└────
Asynchronously `POST' a JSON object in the request body, then parse a
JSON object from the response body, and call a function with the
result:
┌────
│ (plz 'post "https://httpbin.org/post"
│ :headers '(("Content-Type" . "application/json"))
│ :body (json-encode '(("key" . "value")))
│ :as #'json-read
│ :then (lambda (alist)
│ (message "Result: %s" (alist-get 'data alist))))
└────
┌────
│ Result: {"key":"value"}
└────
Synchronously download a JPEG file, then create an Emacs image object
from the data:
┌────
│ (let ((jpeg-data (plz 'get "https://httpbin.org/image/jpeg" :as 'binary)))
│ (create-image jpeg-data nil 'data))
└────
┌────
│ (image :type jpeg :data ""ÿØÿà^@^PJFIF...")
└────
2.2 Functions
─────────────
`plz'
/(method url &key headers body else finally noquery (as 'string)
(then 'sync) (body-type 'text) (decode t decode-s)
(connect-timeout plz-connect-timeout) (timeout plz-timeout))/
Request `METHOD' from `URL' with curl. Return the curl process
object or, for a synchronous request, the selected result.
`HEADERS' may be an alist of extra headers to send with the
request.
`BODY' may be a string, a buffer, or a list like `(file
FILENAME)' to upload a file from disk.
`BODY-TYPE' may be `text' to send `BODY' as text, or `binary' to
send it as binary.
`AS' selects the kind of result to pass to the callback function
`THEN', or the kind of result to return for synchronous
requests. It may be:
• `buffer' to pass the response buffer, which will be narrowed
to the response body and decoded according to `DECODE'.
• `binary' to pass the response body as an un-decoded string.
• `string' to pass the response body as a decoded string.
• `response' to pass a `plz-response' structure.
• `file' to pass a temporary filename to which the response body
has been saved without decoding.
• `(file ~FILENAME)' to pass `FILENAME' after having saved the
response body to it without decoding. `FILENAME' must be a
non-existent file; if it exists, it will not be overwritten,
and an error will be signaled.
• A function, which is called in the response buffer with it
narrowed to the response body (suitable for,
e.g. `json-read').
If `DECODE' is non-nil, the response body is decoded
automatically. For binary content, it should be nil. When `AS'
is `binary', `DECODE' is automatically set to nil.
`THEN' is a callback function, whose sole argument is selected
above with `AS'; if the request fails and no `ELSE' function is
given (see below), the argument will be a `plz-error' structure
describing the error. Or `THEN' may be `sync' to make a
synchronous request, in which case the result is returned
directly from this function.
`ELSE' is an optional callback function called when the request
fails (i.e. if curl fails, or if the `HTTP' response has a
non-2xx status code). It is called with one argument, a
`plz-error' structure. If `ELSE' is nil, a `plz-curl-error' or
`plz-http-error' is signaled when the request fails, with a
`plz-error' structure as the error data. For synchronous
requests, this argument is ignored.
`NOTE': In v0.8 of `plz', only one error will be signaled:
`plz-error'. The existing errors, `plz-curl-error' and
`plz-http-error', inherit from `plz-error' to allow applications
to update their code while using v0.7 (i.e. any `condition-case'
forms should now handle only `plz-error', not the other two).
`FINALLY' is an optional function called without argument after
`THEN' or `ELSE', as appropriate. For synchronous requests,
this argument is ignored.
`CONNECT-TIMEOUT' and `TIMEOUT' are a number of seconds that
limit how long it takes to connect to a host and to receive a
response from a host, respectively.
`NOQUERY' is passed to `make-process', which see.
2.3 Queueing
────────────
`plz' provides a simple system for queueing HTTP requests. First,
make a `plz-queue' struct by calling `make-plz-queue'. Then call
`plz-queue' with the struct as the first argument, and the rest of the
arguments being the same as those passed to `plz'. Then call
`plz-run' to run the queued requests.
All of the queue-related functions return the queue as their value,
making them easy to use. For example:
┌────
│ (defvar my-queue (make-plz-queue :limit 2))
│
│ (plz-run
│ (plz-queue my-queue
│ 'get "https://httpbin.org/get?foo=0"
│ :then (lambda (body) (message "%s" body))))
└────
Or:
┌────
│ (let ((queue (make-plz-queue :limit 2
│ :finally (lambda ()
│ (message "Queue empty."))))
│ (urls '("https://httpbin.org/get?foo=0"
│ "https://httpbin.org/get?foo=1")))
│ (plz-run
│ (dolist (url urls queue)
│ (plz-queue queue 'get url
│ :then (lambda (body) (message "%s" body))))))
└────
You may also clear a queue with `plz-clear', which cancels any active
or queued requests and calls their `:else' functions. And
`plz-length' returns the number of a queue's active and queued
requests.
2.4 Tips
────────
⁃ You can customize settings in the `plz' group, but this can only be
used to adjust a few defaults. It's not intended that changing or
binding global variables be necessary for normal operation.
3 Changelog
═══════════
3.1 0.7
───────
*Changes*
⁃ A new error signal, `plz-error', is defined. The existing signals,
`plz-curl-error' and `plz-http-error', inherit from it, so handling
`plz-error' catches both.
*NOTE:* The existing signals, `plz-curl-error' and `plz-http-error',
are hereby deprecated, and they will be removed in v0.8.
Applications should be updated while using v0.7 to only expect
`plz-error'.
*Fixes*
⁃ Significant improvement in reliability by implementing failsafes and
workarounds for Emacs's process-handling code. (See [#3].)
⁃ STDERR output from curl processes is not included in response bodies
(which sometimes happened, depending on Emacs's internal race
conditions). (Fixes [#23].)
⁃ Use `with-local-quit' for synchronous requests (preventing Emacs
from complaining sometimes). (Fixes [#26].)
⁃ Various fixes for `:as 'buffer' result type: decode body when
appropriate; unset multibyte for binary; narrow to body; don't kill
buffer prematurely.
⁃ When clearing a queue, don't try to kill finished processes.
*Internal*
⁃ Response processing now happens outside the process sentinel, so any
errors (e.g. in user callbacks) are not signaled from inside the
sentinel. (This avoids the 2-second pause Emacs imposes in such
cases.)
⁃ Tests run against a local instance of [httpbin] (since the
`httpbin.org' server is often overloaded).
⁃ No buffer-local variables are defined anymore; process properties
are used instead.
[#3] <https://github.com/alphapapa/plz.el/issues/3>
[#23] <https://github.com/alphapapa/plz.el/issues/23>
[#26] <https://github.com/alphapapa/plz.el/issues/26>
[httpbin] <https://github.com/postmanlabs/httpbin>
3.2 0.6
───────
*Additions*
⁃ Function `plz''s `:body' argument now accepts a list like `(file
FILENAME)' to upload a file from disk (by passing the filename to
curl, rather than reading its content into Emacs and sending it to
curl through the pipe).
*Fixes*
⁃ Function `plz''s docstring now mentions that the `:body' argument
may also be a buffer (an intentional feature that was accidentally
undocumented).
⁃ Handle HTTP 3xx redirects when using `:as 'response'.
3.3 0.5.4
─────────
*Fixes*
⁃ Only run queue's `finally' function after queue is empty. (New
features should not be designed and released on a Friday.)
3.4 0.5.3
─────────
*Fixes*
⁃ Move new slot in `plz-queue' struct to end to prevent invalid
byte-compiler expansions for already-compiled applications (which
would require them to be recompiled after upgrading `plz').
3.5 0.5.2
─────────
*Fixes*
⁃ When clearing a queue, only call `plz-queue''s `finally' function
when specified.
3.6 0.5.1
─────────
*Fixes*
⁃ Only call `plz-queue''s `finally' function when specified. (Thanks
to [Dan Oriani] for reporting.)
[Dan Oriani] <https://github.com/redchops>
3.7 0.5
───────
*Additions*
⁃ Struct `plz-queue''s `finally' slot, a function called when the
queue is finished.
3.8 0.4
───────
*Additions*
⁃ Support for HTTP `HEAD' requests. (Thanks to [USHIN, Inc.] for
sponsoring.)
*Changes*
⁃ Allow sending `POST' and `PUT' requests without bodies. ([#16].
Thanks to [Joseph Turner] for reporting. Thanks to [USHIN, Inc.]
for sponsoring.)
*Fixes*
⁃ All 2xx HTTP status codes are considered successful. ([#17].
Thanks to [Joseph Turner] for reporting. Thanks to [USHIN, Inc.]
for sponsoring.)
⁃ Errors are signaled with error data correctly.
*Internal*
⁃ Test suite explicitly tests with both HTTP/1.1 and HTTP/2.
⁃ Test suite also tests with Emacs versions 27.2, 28.1, and 28.2.
[USHIN, Inc.] <https://ushin.org/>
[#16] <https://github.com/alphapapa/plz.el/issues/16>
[Joseph Turner] <https://github.com/josephmturner>
[#17] <https://github.com/alphapapa/plz.el/issues/17>
3.9 0.3
───────
*Additions*
⁃ Handle HTTP proxy headers from Curl. ([#2]. Thanks to [Alan Third]
and [Sawyer Zheng] for reporting.)
*Fixes*
⁃ Replaced words not in Ispell's default dictionaries (so `checkdoc'
linting succeeds).
[#2] <https://github.com/alphapapa/plz.el/issues/2>
[Alan Third] <https://github.com/alanthird>
[Sawyer Zheng] <https://github.com/sawyerzheng>
3.10 0.2.1
──────────
*Fixes*
⁃ Handle when Curl process is interrupted.
3.11 0.2
────────
*Added*
⁃ Simple request queueing.
3.12 0.1
────────
Initial release.
4 Credits
═════════
⁃ Thanks to [Chris Wellons], author of the [Elfeed] feed reader and
the popular blog [null program], for his invaluable advice, review,
and encouragement.
[Chris Wellons] <https://github.com/skeeto>
[Elfeed] <https://github.com/skeeto/elfeed>
[null program] <https://nullprogram.com/>
5 Development
═════════════
Bug reports, feature requests, suggestions — /oh my/!
Note that `plz' is a young library, and its only client so far is
[Ement.el]. There are a variety of HTTP and `curl' features it does
not yet support, since they have not been needed by the author.
Patches are welcome, as long as they include passing tests.
[Ement.el] <https://github.com/alphapapa/ement.el>
5.1 Copyright assignment
────────────────────────
This package is part of [GNU Emacs], being distributed in [GNU ELPA].
Contributions to this project must follow GNU guidelines, which means
that, as with other parts of Emacs, patches of more than a few lines
must be accompanied by having assigned copyright for the contribution
to the FSF. Contributors who wish to do so may contact
[emacs-devel@gnu.org] to request the assignment form.
[GNU Emacs] <https://www.gnu.org/software/emacs/>
[GNU ELPA] <https://elpa.gnu.org/>
[emacs-devel@gnu.org] <mailto:emacs-devel@gnu.org>
6 License
═════════
GPLv3