Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44f3192a03 |
@@ -30,11 +30,12 @@ make docker
|
|||||||
docker run -p 8080:8080 -e PIXA_SIGNING_KEY="$(openssl rand -base64 32)" pixa:latest
|
docker run -p 8080:8080 -e PIXA_SIGNING_KEY="$(openssl rand -base64 32)" pixa:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
`make build` compiles pixa on your machine. That needs the libraries
|
`make build` compiles pixa on your machine into `bin/pixad`. `pixad` is the
|
||||||
`script/bootstrap --cgo` installs (see Deployment).
|
server program. Compiling needs the libraries `script/bootstrap --cgo` installs
|
||||||
|
(see Deployment).
|
||||||
|
|
||||||
The signing key is pixa's one secret. It must be 32 characters or longer. The
|
The signing key is pixa's one required secret. It must be 32 characters or
|
||||||
placeholder in the example file is refused at startup.
|
longer. The placeholder in the example file is refused at startup.
|
||||||
|
|
||||||
Once pixa runs, it serves images through URLs of this form:
|
Once pixa runs, it serves images through URLs of this form:
|
||||||
|
|
||||||
@@ -69,8 +70,6 @@ variable wins over the same setting in the file.
|
|||||||
pixa listens on plain HTTP. It runs behind a reverse proxy: a server in front of
|
pixa listens on plain HTTP. It runs behind a reverse proxy: a server in front of
|
||||||
it that receives the public requests and passes them on. The reverse proxy
|
it that receives the public requests and passes them on. The reverse proxy
|
||||||
terminates TLS, the encryption behind `https`.
|
terminates TLS, the encryption behind `https`.
|
||||||
[`configs/Caddyfile`](configs/Caddyfile) is an example for Caddy. Caddy gets its
|
|
||||||
own TLS certificate and does everything in this list without further settings.
|
|
||||||
|
|
||||||
The reverse proxy must:
|
The reverse proxy must:
|
||||||
|
|
||||||
@@ -80,12 +79,16 @@ The reverse proxy must:
|
|||||||
form from its pages unless `Origin` or `Referer` names the host in `Host`. It
|
form from its pages unless `Origin` or `Referer` names the host in `Host`. It
|
||||||
builds encrypted URLs from `Host`. It checks `Referer` against
|
builds encrypted URLs from `Host`. It checks `Referer` against
|
||||||
`referer_blocklist`.
|
`referer_blocklist`.
|
||||||
- set `X-Forwarded-For` to the client's address, with `trusted_proxies` naming
|
- set `X-Forwarded-For` to the client's address. `trusted_proxies` must hold the
|
||||||
the address its requests reach pixa from. The login limit and
|
proxy's address as pixa sees it. The login limit and
|
||||||
`image_requests_per_minute` then count each client by its own address.
|
`image_requests_per_minute` then count each client by its own address.
|
||||||
- wait for pixa's answer for at least `downstream_timeout` (default `60s`). That
|
- wait for pixa's answer for at least `downstream_timeout` (default `60s`). That
|
||||||
is the longest pixa takes to fetch, convert and send an image.
|
is the longest pixa takes to fetch, convert and send an image.
|
||||||
|
|
||||||
|
[`configs/Caddyfile`](configs/Caddyfile) is an example for Caddy. Caddy does
|
||||||
|
everything in this list without further settings, and gets its own TLS
|
||||||
|
certificate.
|
||||||
|
|
||||||
The reverse proxy may also refuse `/metrics`, as the example does. Only a
|
The reverse proxy may also refuse `/metrics`, as the example does. Only a
|
||||||
scraper that reaches pixa directly can then read it. pixa itself asks for
|
scraper that reaches pixa directly can then read it. pixa itself asks for
|
||||||
`metrics.username` and `metrics.password` there.
|
`metrics.username` and `metrics.password` there.
|
||||||
@@ -114,7 +117,7 @@ database and the disk cache:
|
|||||||
the limit for a while. Leave room on the volume beyond it.
|
the limit for a while. Leave room on the volume beyond it.
|
||||||
- Set `cache_max_bytes` for a lasting deployment. Its default is worked out at
|
- Set `cache_max_bytes` for a lasting deployment. Its default is worked out at
|
||||||
each start from the free space on the volume (see Configuration). Anything
|
each start from the free space on the volume (see Configuration). Anything
|
||||||
else that fills or frees space on the volume moves it.
|
else that fills or frees space on the volume moves that default.
|
||||||
|
|
||||||
A load balancer's health check can request `/.well-known/healthcheck.json`. It
|
A load balancer's health check can request `/.well-known/healthcheck.json`. It
|
||||||
answers 200 whenever pixa is running, in maintenance mode too (see
|
answers 200 whenever pixa is running, in maintenance mode too (see
|
||||||
@@ -122,28 +125,28 @@ answers 200 whenever pixa is running, in maintenance mode too (see
|
|||||||
|
|
||||||
On `SIGTERM` or `SIGINT`, pixa stops accepting connections. The requests in
|
On `SIGTERM` or `SIGINT`, pixa stops accepting connections. The requests in
|
||||||
progress and the images being processed get 5 seconds to finish. A request not
|
progress and the images being processed get 5 seconds to finish. A request not
|
||||||
finished by then is cut off. pixa then writes the last-used times of cached
|
finished by then is cut off. pixa then saves the last-used times it has not yet
|
||||||
images that still wait to be written (see Storage), and exits. It exits with
|
written (see Storage), and exits. It exits with status 1 when:
|
||||||
status 1 when:
|
|
||||||
|
|
||||||
- images were still being processed after those 5 seconds;
|
- images were still being processed after those 5 seconds;
|
||||||
- some of those times could not be written;
|
- some of those times could not be saved;
|
||||||
- another part of pixa failed to stop.
|
- another part of pixa failed to stop.
|
||||||
|
|
||||||
Otherwise it exits with 0. `docker stop` waits 10 seconds before it kills the
|
Otherwise it exits with 0. `docker stop` waits 10 seconds before it kills the
|
||||||
container.
|
container.
|
||||||
|
|
||||||
Outside Docker, pixa needs libvips and libheif at run time. libvips is the image
|
Outside Docker, pixa needs these at run time:
|
||||||
library pixa converts images with. pixa does not start unless libvips has its
|
|
||||||
JPEG XL support. On Alpine that is the `vips-jxl` package. The nix and brew
|
- libvips, the image library pixa converts images with, including its JPEG XL
|
||||||
packages of libvips include it. So do the apt packages from Debian 12 and Ubuntu
|
support. pixa does not start without that support. On Alpine it is the
|
||||||
24.04 on. Compiling pixa also needs the development files of libvips and
|
`vips-jxl` package. The nix and brew packages of libvips include it. So do the
|
||||||
libheif, `pkg-config` and a C compiler. `script/bootstrap --cgo` installs all of
|
apt packages from Debian 12 and Ubuntu 24.04 on.
|
||||||
these. The `Dockerfile` installs its own packages. Plain `script/bootstrap`
|
- libheif, which libvips needs for AVIF.
|
||||||
installs git, make and Go; `script/setup` and `script/cibuild` run it. It also
|
- the system's CA certificates, which HTTPS fetches need.
|
||||||
installs Node, Yarn and prettier, which format the markdown. It installs none of
|
|
||||||
the C libraries: the checks compile pixa in Docker. Docker itself must already
|
Compiling pixa needs the development files of libvips and libheif, `pkg-config`
|
||||||
be installed.
|
and a C compiler. `script/bootstrap --cgo` installs them, and libvips and
|
||||||
|
libheif with them. The `Dockerfile` installs its own packages.
|
||||||
|
|
||||||
## Running under upaas
|
## Running under upaas
|
||||||
|
|
||||||
@@ -161,19 +164,20 @@ run under. Its app for pixa needs:
|
|||||||
comma-separated; default none. Every image on a listed host is served to
|
comma-separated; default none. Every image on a listed host is served to
|
||||||
anyone, so list only hosts you control, where nobody else can put images.
|
anyone, so list only hosts you control, where nobody else can put images.
|
||||||
- `PIXA_CACHE_MAX_BYTES`: the disk cache limit in bytes; `0` turns the disk
|
- `PIXA_CACHE_MAX_BYTES`: the disk cache limit in bytes; `0` turns the disk
|
||||||
cache off. Set it. The volume is a directory on the host's filesystem,
|
cache off. Set it. The volume is a directory on the host's filesystem. The
|
||||||
shared with the host and every other app. The default is 75% of the sum of
|
host and every other app use that filesystem too. The default is 75% of
|
||||||
that filesystem's free space and what the cache holds, and at least 500
|
the sum of that filesystem's free space and what the cache already holds.
|
||||||
MiB. So the cache can grow to three quarters or more of the space they all
|
So by default the cache may grow to three quarters of the space the host
|
||||||
have left, and to all of it when that is 500 MiB or less.
|
and the other apps have left. The default is also at least 500 MiB. So
|
||||||
|
when that space is 500 MiB or less, the cache may take all of it.
|
||||||
- the rest are in the list under Configuration.
|
- the rest are in the list under Configuration.
|
||||||
- **Memory:** pixa can use up to `max_concurrent_processing` times 4.2 GiB for
|
- **Memory:** pixa can use up to `max_concurrent_processing` times 4.2 GiB for
|
||||||
the images it processes at once. On top of that, the fetched source images
|
the images it processes at once. On top of that, the fetched source images
|
||||||
waiting for processing can take `upstream_connections` times
|
waiting for processing can take `upstream_connections` times
|
||||||
`upstream_max_response_size`. 4.2 GiB is a measured value. It is the most
|
`upstream_max_response_size`. 4.2 GiB is a measured value. It is the largest
|
||||||
resident memory grew while pixa processed one 8192x8192 source, the largest it
|
rise in resident memory measured while pixa saved one 8192x8192 source, the
|
||||||
accepts, saving it at full size to each output format in turn. Memory grew
|
largest it accepts, to each output format at full size. The rise was largest
|
||||||
most for a 16-bit JPEG XL of random pixels saved as WebP. At the defaults that
|
for a 16-bit JPEG XL of random pixels saved as WebP. At the defaults the total
|
||||||
is half the CPUs pixa sees times 4.2 GiB, plus 64 times 50 MiB. On a host
|
is half the CPUs pixa sees times 4.2 GiB, plus 64 times 50 MiB. On a host
|
||||||
where pixa sees 48 CPUs, that is 24 times 4.2 GiB plus 3.1 GiB, about 104 GiB.
|
where pixa sees 48 CPUs, that is 24 times 4.2 GiB plus 3.1 GiB, about 104 GiB.
|
||||||
Set `PIXA_MAX_CONCURRENT_PROCESSING` and `PIXA_UPSTREAM_CONNECTIONS` so that
|
Set `PIXA_MAX_CONCURRENT_PROCESSING` and `PIXA_UPSTREAM_CONNECTIONS` so that
|
||||||
@@ -197,17 +201,18 @@ run under. Its app for pixa needs:
|
|||||||
- **Health check:** the image's `HEALTHCHECK` requests
|
- **Health check:** the image's `HEALTHCHECK` requests
|
||||||
`/.well-known/healthcheck.json`. upaas reads the container's health 60 seconds
|
`/.well-known/healthcheck.json`. upaas reads the container's health 60 seconds
|
||||||
after a deploy and marks the deploy failed unless it is `healthy`. The probe
|
after a deploy and marks the deploy failed unless it is `healthy`. The probe
|
||||||
takes the port from `PORT` (default `8080`). A port changed only in a mounted
|
takes the port from `PORT` (default `8080`). It does not see a port changed
|
||||||
config file is not seen by it, so change the port with `PORT`.
|
only in a mounted config file, so change the port with `PORT`.
|
||||||
|
|
||||||
## Rationale
|
## Rationale
|
||||||
|
|
||||||
A web application that shows images needs each one at several sizes and in
|
A web application that shows images needs each one at several sizes and in
|
||||||
several formats. pixa makes them on request from one original, and keeps what it
|
several formats. pixa makes them on request from one original, and keeps what it
|
||||||
made. The application stores one image per picture and hands out URLs. pixa is
|
made. The application stores one image per picture and hands out URLs. pixa is
|
||||||
one binary. At run time it needs only the image libraries named under
|
one binary. At run time it needs only the image libraries and the CA
|
||||||
Deployment. Signed URLs with an expiry keep strangers from using it to resize
|
certificates named under Deployment. Signed URLs with an expiry keep strangers
|
||||||
anything they like. Allowlisted hosts give open access where that is wanted.
|
from using it to resize anything they like. Allowlisted hosts give open access
|
||||||
|
where that is wanted.
|
||||||
|
|
||||||
## Design
|
## Design
|
||||||
|
|
||||||
@@ -249,18 +254,15 @@ These terms are the names the code uses:
|
|||||||
|
|
||||||
### Routes
|
### Routes
|
||||||
|
|
||||||
pixa answers these routes. Any other path answers 404. A path in this list asked
|
pixa answers these routes. Any other path answers 404. A listed path answers 405
|
||||||
with a method the list does not give answers 405. A browser's CORS preflight
|
to any method the list does not give for it.
|
||||||
request to any path under `/v1/` answers 200, in maintenance mode too. CORS is
|
|
||||||
the browser rule that lets a page on one site read responses from another. The
|
|
||||||
preflight is the `OPTIONS` request the browser sends first.
|
|
||||||
|
|
||||||
- `GET /` — the login page, or the generator page with a login session (see
|
- `GET /` — the login page, or the generator page with a login session (see
|
||||||
Encrypted URLs). Needs nothing. Answers 200.
|
Encrypted URLs). Needs nothing. Answers 200.
|
||||||
- `POST /` — log in with the signing key typed into the login page. Needs the
|
- `POST /` — log in with the signing key typed into the login page. Needs the
|
||||||
login page's form (below). Answers 303 to `/`, with a login session cookie,
|
login page's form (below). For the right key it answers 303 to `/` and sets
|
||||||
for the right key. Answers 200 with the login page and an error for a wrong
|
the login session cookie. For a wrong key it answers 200 with the login page
|
||||||
key. Answers 429 over the login limit (see Limits).
|
and an error. Answers 429 over the login limit (see Limits).
|
||||||
- `POST /generate` — make an encrypted URL from the generator page's form. Needs
|
- `POST /generate` — make an encrypted URL from the generator page's form. Needs
|
||||||
a login session and the generator page's form. Without a login session it
|
a login session and the generator page's form. Without a login session it
|
||||||
answers 303 to `/`. Answers 200 with the page showing the URL. Answers 400
|
answers 303 to `/`. Answers 200 with the page showing the URL. Answers 400
|
||||||
@@ -342,28 +344,36 @@ preflight is the `OPTIONS` request the browser sends first.
|
|||||||
Answers 401 without them. Answers 404 when they are not set, as the route then
|
Answers 401 without them. Answers 404 when they are not set, as the route then
|
||||||
does not exist.
|
does not exist.
|
||||||
|
|
||||||
|
A browser's CORS preflight request to any path under `/v1/` answers 200, in
|
||||||
|
maintenance mode too. CORS is the browser rule that lets a page on one site read
|
||||||
|
responses from another. The preflight is the `OPTIONS` request the browser sends
|
||||||
|
first.
|
||||||
|
|
||||||
Every response carries an `X-Request-Id` header with the request's ID. A client
|
Every response carries an `X-Request-Id` header with the request's ID. A client
|
||||||
can quote it when reporting a problem. The ID is the request's own
|
can quote it when reporting a problem. A reverse proxy may send an
|
||||||
`X-Request-Id` header, which a reverse proxy may send, when that is 1 to 64
|
`X-Request-Id` header with the request. pixa keeps that ID when it is 1 to 64
|
||||||
letters, digits, `-`, `_` or `.`. Otherwise it is a random one pixa makes, which
|
letters, digits, `-`, `_` or `.`. Otherwise pixa makes a random ID, which tells
|
||||||
tells nothing about the machine or the other requests. pixa's log line for the
|
nothing about the machine or the other requests. pixa's log line for the request
|
||||||
request carries the same ID as `request_id`. So do the lines it logs when it
|
carries the same ID as `request_id`. So do the lines it logs when it fetches,
|
||||||
fetches, converts and serves an image. The fetch sends it to the upstream host
|
converts and serves an image. The fetch sends it to the upstream host as
|
||||||
as `X-Request-Id`.
|
`X-Request-Id`.
|
||||||
|
|
||||||
The `POST` routes accept only a form that pixa's own page served. The page puts
|
The `POST` routes accept only a form that pixa's own page served. The page puts
|
||||||
a token in the form and sets a cookie to match. A request without both is
|
a token in the form and sets a cookie to match. A request without both is
|
||||||
refused with 403. So another site cannot submit the form from a visitor's
|
refused with 403. So another site cannot submit the form from a visitor's
|
||||||
browser. The login and generator pages are meant to be opened over HTTPS. While
|
browser. A form is also refused with 403 when the page's host is not the `Host`
|
||||||
`debug` is off, a form sent from a page opened over plain HTTP is refused
|
header pixa receives. So a reverse proxy in front of pixa must pass that header
|
||||||
with 403. While `debug` is on, a form sent from a page opened over HTTPS is
|
on unchanged. A form body over 1 MiB is refused with 413.
|
||||||
refused instead. Plain HTTP is for development on the browser's own machine. The
|
|
||||||
login session cookie is always marked `Secure`. Over plain HTTP a browser keeps
|
The login and generator pages are meant to be opened over HTTPS. While `debug`
|
||||||
such a cookie only for its own machine (`localhost`), if at all. A form is also
|
is off, a form sent from a page opened over plain HTTP is refused with 403.
|
||||||
refused with 403 when the page's host is not the `Host` header pixa receives. So
|
While `debug` is on, a form sent from a page opened over HTTPS is refused
|
||||||
a reverse proxy in front of pixa must pass that header on unchanged. A form body
|
instead. Plain HTTP is for development on the browser's own machine. The login
|
||||||
over 1 MiB is refused with 413. The image routes answer the errors listed for
|
session cookie is always marked `Secure`. Over plain HTTP a browser keeps such a
|
||||||
them with a JSON body holding `error`, `status` and `timestamp`.
|
cookie only for its own machine (`localhost`), if at all.
|
||||||
|
|
||||||
|
The image routes answer the errors listed for them with a JSON body holding
|
||||||
|
`error`, `status` and `timestamp`.
|
||||||
|
|
||||||
### Image URLs
|
### Image URLs
|
||||||
|
|
||||||
@@ -462,11 +472,11 @@ it would come out above 8192, the request is refused with 400. The fit modes:
|
|||||||
at `100x0` is 100x1 by its proportions, but comes out at 8192x1. A side that
|
at `100x0` is 100x1 by its proportions, but comes out at 8192x1. A side that
|
||||||
would come out above 8192 is refused with 400.
|
would come out above 8192 is refused with 400.
|
||||||
|
|
||||||
An AVIF image of more than 16,777,216 pixels is refused with 400 before anything
|
An AVIF output of more than 16,777,216 pixels is refused with 400 before
|
||||||
is decoded. That is the number of pixels in 4096x4096 or 8192x2048. A larger
|
anything is decoded. That is the number of pixels in 4096x4096 or 8192x2048. A
|
||||||
AVIF encode could take more than 40 seconds (see Limits). The limit includes
|
larger AVIF encode could take more than 40 seconds (see Limits). The limit
|
||||||
`orig` of an AVIF source, and `auto` when it chooses AVIF. So a client whose
|
includes `orig` of an AVIF source, and `auto` when it chooses AVIF. So a client
|
||||||
`Accept` names `image/avif` but not `image/jxl` is refused such a size.
|
whose `Accept` names `image/avif` but not `image/jxl` is refused such a size.
|
||||||
|
|
||||||
**Source formats.** The source may be JPEG, PNG, GIF, WebP, AVIF or JPEG XL. The
|
**Source formats.** The source may be JPEG, PNG, GIF, WebP, AVIF or JPEG XL. The
|
||||||
upstream's `Content-Type` must name its format, and the image's first bytes must
|
upstream's `Content-Type` must name its format, and the image's first bytes must
|
||||||
@@ -485,8 +495,9 @@ from its `Accept` header, in this order:
|
|||||||
1. JPEG XL, when the header names `image/jxl`;
|
1. JPEG XL, when the header names `image/jxl`;
|
||||||
2. AVIF, when it names `image/avif`;
|
2. AVIF, when it names `image/avif`;
|
||||||
3. WebP, when it names `image/webp`;
|
3. WebP, when it names `image/webp`;
|
||||||
4. JPEG, when the first of `image/jpeg`, `image/*` and `*/*` that it names
|
4. JPEG, when the most specific of `image/jpeg`, `image/*` and `*/*` that the
|
||||||
allows it, or when there is no `Accept` header or it is empty.
|
header names has a `q` above 0, or when there is no `Accept` header or it is
|
||||||
|
empty.
|
||||||
|
|
||||||
An entry with `q=0` refuses its format. Other `q` values do not change the
|
An entry with `q=0` refuses its format. Other `q` values do not change the
|
||||||
order. JPEG XL, AVIF and WebP must be named, as clients that cannot show them
|
order. JPEG XL, AVIF and WebP must be named, as clients that cannot show them
|
||||||
@@ -589,9 +600,8 @@ change what it asks for.
|
|||||||
1. Open `/` in a browser over HTTPS and log in with the signing key. The login
|
1. Open `/` in a browser over HTTPS and log in with the signing key. The login
|
||||||
session lasts 30 days, or until someone logs out with the generator page's
|
session lasts 30 days, or until someone logs out with the generator page's
|
||||||
Logout button.
|
Logout button.
|
||||||
2. On the generator page, give the source image's URL, the width and height, the
|
2. On the generator page, fill in the form. Its fields are listed below. Then
|
||||||
format, quality and fit, and how long the URL lasts. Then submit the form
|
submit it (`POST /generate`).
|
||||||
(`POST /generate`).
|
|
||||||
3. The page shows the URL, `https://<host>/v1/e/<token>/img.<format>`, and when
|
3. The page shows the URL, `https://<host>/v1/e/<token>/img.<format>`, and when
|
||||||
it expires. `<host>` is the host the page was opened on. While `debug` is on,
|
it expires. `<host>` is the host the page was opened on. While `debug` is on,
|
||||||
the URL starts with `http` instead. The name after the token is ignored. It
|
the URL starts with `http` instead. The name after the token is ignored. It
|
||||||
@@ -600,12 +610,20 @@ change what it asks for.
|
|||||||
|
|
||||||
The form's fields:
|
The form's fields:
|
||||||
|
|
||||||
- The format is JPEG XL unless another is chosen. A form sent with an empty
|
- The source image's URL. Its scheme is not kept (below).
|
||||||
format, or none, also makes a JPEG XL URL.
|
- The width and height. Both empty or `0` keep the source's size. When only one
|
||||||
- Width and height both empty or `0` keep the source's size. When only one of
|
of them is empty or `0`, that side is worked out from the source's proportions
|
||||||
them is empty or `0`, that side is worked out from the source's proportions
|
|
||||||
when the image is made (see Image URLs).
|
when the image is made (see Image URLs).
|
||||||
- How long the URL lasts is chosen from 1 minute to 1 year, or never.
|
- The format: `orig`, `auto`, `jpeg`, `png`, `webp`, `avif`, `jxl` or `gif`. It
|
||||||
|
is JPEG XL unless another is chosen. A form sent with an empty format, or
|
||||||
|
none, also makes a JPEG XL URL.
|
||||||
|
- The quality, a whole number from 1 to 100. The page offers five values, and
|
||||||
|
`85` unless another is chosen.
|
||||||
|
- The fit, one of the five fit modes (see Image URLs). It is `cover` unless
|
||||||
|
another is chosen.
|
||||||
|
- How long the URL lasts, in seconds, or `0` for never. The page offers never, 1
|
||||||
|
minute, 1 hour, 1 day, 1 week, 30 days and 1 year, and never unless another is
|
||||||
|
chosen.
|
||||||
|
|
||||||
The page refuses a form with 400 when:
|
The page refuses a form with 400 when:
|
||||||
|
|
||||||
@@ -613,25 +631,25 @@ The page refuses a form with 400 when:
|
|||||||
- the source URL's path holds a `..` segment, as `/v1/image/` refuses one;
|
- the source URL's path holds a `..` segment, as `/v1/image/` refuses one;
|
||||||
- a width, height, quality or expiry is not a number, or is out of range;
|
- a width, height, quality or expiry is not a number, or is out of range;
|
||||||
- the fit is not one of the five fit modes;
|
- the fit is not one of the five fit modes;
|
||||||
- the source URL would be fetched written another way (below).
|
- pixa would fetch the source URL in another form (below).
|
||||||
|
|
||||||
pixa fetches a source URL only as it was written. The page refuses one that pixa
|
The page refuses a source URL that pixa would fetch in another form, and shows
|
||||||
would fetch another way, and shows the URL pixa would fetch. A URL with a user
|
the URL pixa would fetch. A URL with a user name and password is one example:
|
||||||
name and password, which pixa does not send, is one example. A path holding
|
pixa does not send them. A URL ending in `?` with no query is another. A path
|
||||||
`%2F`, which pixa would fetch as `/`, or `(`, which it would fetch as `%28`, is
|
holding `%2F` is another: pixa would fetch it as `/`. So is a path holding `(`:
|
||||||
another (see Image URLs).
|
pixa would fetch it as `%28` (see Image URLs).
|
||||||
|
|
||||||
The image is made only when the URL is requested. A side that would come out
|
The image is made only when the URL is requested. A side that would come out
|
||||||
above 8192, or an AVIF output of more than 16,777,216 pixels, is refused with
|
above 8192, or an AVIF output of more than 16,777,216 pixels, is refused with
|
||||||
400 then (see Image URLs).
|
400 then (see Image URLs).
|
||||||
|
|
||||||
Every login is made with the one signing key, so logging out ends every login
|
Every login is made with the one signing key. So logging out ends every login
|
||||||
session, in every browser, a copy of the session cookie included. pixa keeps a
|
session, in every browser, and a copied session cookie too. pixa keeps a random
|
||||||
random value in its database. It makes the value when the database has none, and
|
value in its database. It makes the value when the database has none, and
|
||||||
replaces it at each logout. A login session carries the value current at its
|
replaces it at each logout. A login session carries the value current at its
|
||||||
login. It is refused once the value has changed, also after a restart. The check
|
login. Once the value has changed, the session is refused, after a restart too.
|
||||||
compares no time, so a login after a logout works whatever the host's clock
|
The check compares no time, so a login after a logout works whatever the host's
|
||||||
says. The 30 days are still measured by the host's clock.
|
clock says. The 30 days are still measured by the host's clock.
|
||||||
|
|
||||||
The token holds the source's host, path and query, and the size, format,
|
The token holds the source's host, path and query, and the size, format,
|
||||||
quality, fit and expiry. It is encrypted with a key derived from `signing_key`.
|
quality, fit and expiry. It is encrypted with a key derived from `signing_key`.
|
||||||
@@ -698,7 +716,8 @@ The rest of the rules:
|
|||||||
- JPEG XL output is the one exception, with libvips 8.16 and later. libvips then
|
- JPEG XL output is the one exception, with libvips 8.16 and later. libvips then
|
||||||
writes an EXIF block of its own, which pixa cannot stop. The block holds the
|
writes an EXIF block of its own, which pixa cannot stop. The block holds the
|
||||||
image's size and fixed values: an orientation of 1 and a resolution of 72 dpi.
|
image's size and fixed values: an orientation of 1 and a resolution of 72 dpi.
|
||||||
Nothing in it comes from the source.
|
Nothing in it comes from the source. The Docker image has libvips 8.16, so its
|
||||||
|
JPEG XL output carries this block.
|
||||||
|
|
||||||
### Limits
|
### Limits
|
||||||
|
|
||||||
@@ -712,34 +731,40 @@ client goes away. The shared fetch sends the first request's ID upstream. The
|
|||||||
lines logged for the fetch and the conversion carry that ID.
|
lines logged for the fetch and the conversion carry that ID.
|
||||||
|
|
||||||
**Timeouts.** `downstream_timeout` (default `60s`) bounds the whole answer to
|
**Timeouts.** `downstream_timeout` (default `60s`) bounds the whole answer to
|
||||||
one request. The upstream fetch counts toward it. So do the waits for an
|
one request. The upstream fetch counts toward it. So do the waits for a
|
||||||
upstream connection and for a processing slot, up to 10 seconds each. Keep it
|
connection slot and for a processing slot. The wait for a shared
|
||||||
longer than `upstream_fetch_timeout` plus 20 seconds.
|
`upstream_connections` slot ends after 10 seconds, and so does the wait for a
|
||||||
|
processing slot. The wait for an `upstream_connections_per_host` slot ends only
|
||||||
|
with `downstream_timeout`. Keep `downstream_timeout` longer than
|
||||||
|
`upstream_fetch_timeout` plus 20 seconds.
|
||||||
|
|
||||||
A conversion whose request's `downstream_timeout` has ended stops waiting for a
|
A conversion whose request's `downstream_timeout` has ended stops waiting for a
|
||||||
processing slot. Once it has one, it gives the slot back without beginning to
|
processing slot. Once it has one, it gives the slot back without beginning to
|
||||||
encode. For a shared conversion, the first request's deadline counts. A request
|
encode. For a shared conversion, the first request's deadline counts. When a
|
||||||
waiting for a shared conversion that stops this way is answered 503
|
shared conversion stops this way, each request waiting for it is answered 503
|
||||||
`server busy, try again later`, though its own `downstream_timeout` may not have
|
`server busy, try again later`, even if its own `downstream_timeout` has not
|
||||||
ended. govips, the library pixa drives libvips through, gives no way to stop an
|
ended. govips, the library pixa drives libvips through, gives no way to stop an
|
||||||
encode once it has begun. So an encode keeps its slot until it finishes, which
|
encode once it has begun. So an encode keeps its slot until it finishes, which
|
||||||
can be after `downstream_timeout`.
|
can be after `downstream_timeout`.
|
||||||
|
|
||||||
**Encode times.** The limits on image size bound how long an encode can take.
|
**Encode times.** The limits on image size bound how long a conversion can take.
|
||||||
They are 8192 on each side of the source and of the output, and 16,777,216
|
They are 8192 on each side of the source and of the output, and 16,777,216
|
||||||
pixels for an AVIF output. The WebP and JPEG XL encoders run below libvips'
|
pixels for an AVIF output. Decoding an 8192x8192 source takes up to about 11
|
||||||
default effort. AVIF runs at the lowest effort govips can set. With one libvips
|
seconds. The WebP and JPEG XL encoders run below libvips' default effort. AVIF
|
||||||
thread, as pixa runs it, 8192x8192 random pixels with an alpha channel take up
|
runs at the lowest effort govips can set. With one libvips thread, as pixa runs
|
||||||
to about:
|
it, encoding 8192x8192 random pixels takes up to about:
|
||||||
|
|
||||||
- 23 seconds as WebP, and 16 without an alpha channel;
|
- 1 second as JPEG;
|
||||||
- 27 seconds as JPEG XL, and 13 without an alpha channel.
|
- 15 seconds as PNG;
|
||||||
|
- 16 seconds as GIF;
|
||||||
|
- 23 seconds as WebP with an alpha channel, and 16 without;
|
||||||
|
- 27 seconds as JPEG XL with an alpha channel, and 13 without.
|
||||||
|
|
||||||
An AVIF output of 4096x4096, 8192x2048 or 2048x8192 random pixels with an alpha
|
An AVIF output of 4096x4096, 8192x2048 or 2048x8192 random pixels takes up to
|
||||||
channel takes up to about 34 seconds. At 8192x8192 and quality 85 it would take
|
about 34 seconds with an alpha channel, and 22 without. At 8192x8192 and quality
|
||||||
about 96. These times hold at any quality and at 8 or 16 bits per sample. No
|
85 it would take about 96 seconds. These times hold at any quality and at 8 or
|
||||||
encode within the limits takes more than about 40 seconds of the default
|
16 bits per sample. No encode within the limits takes more than about 40 seconds
|
||||||
`downstream_timeout` of 60.
|
of the default `downstream_timeout` of 60.
|
||||||
|
|
||||||
**Processing and connections.** `max_concurrent_processing` images are decoded
|
**Processing and connections.** `max_concurrent_processing` images are decoded
|
||||||
and encoded at once. A request that finds every slot in use waits up to 10
|
and encoded at once. A request that finds every slot in use waits up to 10
|
||||||
@@ -749,24 +774,26 @@ seconds for one. If none frees up, it is answered 503
|
|||||||
together, and `upstream_connections_per_host` those to one host. A fetch holds
|
together, and `upstream_connections_per_host` those to one host. A fetch holds
|
||||||
its connection until its image has been processed. A fetch that finds every
|
its connection until its image has been processed. A fetch that finds every
|
||||||
shared connection in use waits up to 10 seconds the same way, then answers 503.
|
shared connection in use waits up to 10 seconds the same way, then answers 503.
|
||||||
|
A fetch that finds every connection to its host in use waits for one until
|
||||||
|
`downstream_timeout` ends.
|
||||||
|
|
||||||
**Client limits.** The login form (`POST /`) is limited to 5 attempts per minute
|
**Client limits.** The login form (`POST /`) is limited to 5 attempts per minute
|
||||||
per client address. An IPv6 client is counted by its `/64`. An attempt over the
|
per client address. An attempt over the limit is refused with 429 and a
|
||||||
limit is refused with 429 and a `Retry-After` header. One client may send
|
`Retry-After` header. One client may send `image_requests_per_minute` requests
|
||||||
`image_requests_per_minute` requests per minute (default 600) to `/v1/image/`
|
per minute (default 600) to `/v1/image/` and `/v1/e/` together, `GET` and
|
||||||
and `/v1/e/` together, `GET` and `HEAD`, cache hits included. A request over the
|
`HEAD`, cache hits included. A request over the limit is refused with 429 and a
|
||||||
limit is refused with 429 and a `Retry-After` header. A browser's CORS preflight
|
`Retry-After` header. A browser's CORS preflight is not counted. `0` turns the
|
||||||
is not counted. `0` turns the limit off.
|
limit off. Both limits count an IPv6 client by its `/64`.
|
||||||
|
|
||||||
**The client address.** Behind a reverse proxy, the client address comes from
|
**The client address.** Behind a reverse proxy, pixa takes the client address
|
||||||
`X-Forwarded-For` only when the address pixa sees the request come from is in
|
from `X-Forwarded-For` only when the request arrives from an address in
|
||||||
`trusted_proxies`. Otherwise all users behind the proxy count as one client.
|
`trusted_proxies`. Otherwise all users behind the proxy count as one client. The
|
||||||
That address is not always the proxy's own. A proxy on the Docker host that
|
address the request arrives from is not always the proxy's own. A proxy on the
|
||||||
connects to pixa over `127.0.0.1` is seen as the gateway of the container's
|
Docker host that connects to pixa over `127.0.0.1` is seen as the gateway of the
|
||||||
Docker network, such as `172.17.0.1` on the default bridge. A proxy that
|
container's Docker network, such as `172.17.0.1` on the default bridge. A proxy
|
||||||
connects through another of the host's addresses is seen with that address. To
|
that connects through another of the host's addresses is seen with that address.
|
||||||
be sure, read it as `remoteIP` in pixa's request log while it is not in
|
To be sure, read the address as `remoteIP` in pixa's request log while it is not
|
||||||
`trusted_proxies` (see `trusted_proxies` under Configuration). The default
|
in `trusted_proxies` (see `trusted_proxies` under Configuration). The default
|
||||||
`trusted_proxies` is the RFC 1918 ranges, the private address ranges. With that
|
`trusted_proxies` is the RFC 1918 ranges, the private address ranges. With that
|
||||||
default, a client with a private address can choose the address it is counted
|
default, a client with a private address can choose the address it is counted
|
||||||
by, through its own `X-Forwarded-For`, because its own address is trusted too.
|
by, through its own `X-Forwarded-For`, because its own address is trusted too.
|
||||||
@@ -789,7 +816,8 @@ with 413.
|
|||||||
|
|
||||||
`<ab>` and `<cd>` are the first and second pairs of characters of the file's
|
`<ab>` and `<cd>` are the first and second pairs of characters of the file's
|
||||||
name. The database is one SQLite file. SQLite is the database library pixa
|
name. The database is one SQLite file. SQLite is the database library pixa
|
||||||
embeds; nothing else needs to run.
|
embeds; nothing else needs to run. In WAL mode, the default, SQLite keeps
|
||||||
|
`state.sqlite3-wal` and `state.sqlite3-shm` beside it while pixa runs.
|
||||||
|
|
||||||
The image cache keeps these tables in the database:
|
The image cache keeps these tables in the database:
|
||||||
|
|
||||||
@@ -815,7 +843,7 @@ Each source and variant file is written under a temporary name and synced to
|
|||||||
disk before it is renamed into place. So a crash never leaves part of a file
|
disk before it is renamed into place. So a crash never leaves part of a file
|
||||||
under its own name.
|
under its own name.
|
||||||
|
|
||||||
When pixa starts, and every five minutes after, it checks the cached files
|
When pixa starts, and then at the spacing below, it checks the cached files
|
||||||
against the database. It removes each source or variant whose file is empty, or
|
against the database. It removes each source or variant whose file is empty, or
|
||||||
not the size its row records. It logs a warning naming the file. A later request
|
not the size its row records. It logs a warning naming the file. A later request
|
||||||
for that image fetches the source or makes the variant again. The check also
|
for that image fetches the source or makes the variant again. The check also
|
||||||
@@ -853,20 +881,26 @@ also removes the expired rows of the negative cache every five minutes, at most
|
|||||||
|
|
||||||
Every setting can be given as an environment variable, in a YAML config file
|
Every setting can be given as an environment variable, in a YAML config file
|
||||||
(`--config`), or both. A variable present in the environment wins over the file,
|
(`--config`), or both. A variable present in the environment wins over the file,
|
||||||
even when it is empty. The file wins over the built-in default. The one
|
even when it is empty. The file wins over the built-in default. A variable's
|
||||||
exception is a variable set in the file's `env:` section. pixa sets that
|
value is read as the same text in quotes in the file would be. A setting that
|
||||||
variable while loading the file, so it wins over the environment pixa started
|
holds a list takes comma-separated entries, with the spaces around each trimmed.
|
||||||
with. It also wins over the file's key for the same setting. A variable's value
|
An empty variable is an empty list.
|
||||||
is parsed as the same text in the file would be. A setting that holds a list
|
|
||||||
takes comma-separated entries, with the spaces around each trimmed. An empty
|
The file's `env:` section is the one exception to that order. pixa sets each
|
||||||
variable is an empty list. A value that does not parse or is invalid aborts
|
variable named there while it loads the file. So that variable wins over the
|
||||||
startup, naming the setting and its variable. A key in the file set to null,
|
environment pixa started with, and over the file's key for the same setting.
|
||||||
`key:` with nothing after it included, aborts startup too: omit the key to use
|
|
||||||
the default. pixa refuses to start when a variable starts with `PIXA_` but is
|
pixa refuses to start when a value does not parse or is invalid. The error names
|
||||||
not in the list below, such as a misspelled one or `PIXA_PORT`. The error names
|
the setting. It also refuses to start when a key in the file is set to null,
|
||||||
the variable, as it names an unknown key in the file. pixa also accepts
|
`key:` with nothing after it included. Omit the key to use the default. It
|
||||||
`PIXA_CONFIG_PATH`, which names the config file, like `--config`. The variables
|
refuses to start when the file has a key that is not in the list below, and the
|
||||||
set by the file's `env:` section are checked the same way.
|
error names the key. It refuses to start when a variable starts with `PIXA_` but
|
||||||
|
is not in the list below, such as a misspelled one or `PIXA_PORT`. The error
|
||||||
|
names the variable. The variables set by the file's `env:` section are checked
|
||||||
|
the same way.
|
||||||
|
|
||||||
|
pixa also accepts `PIXA_CONFIG_PATH`, which names the config file, like
|
||||||
|
`--config`.
|
||||||
|
|
||||||
pixa reads at most one config file. It reads the file given with `--config` (or
|
pixa reads at most one config file. It reads the file given with `--config` (or
|
||||||
`-c`), or else the one `PIXA_CONFIG_PATH` names. pixa refuses to start when that
|
`-c`), or else the one `PIXA_CONFIG_PATH` names. pixa refuses to start when that
|
||||||
@@ -936,14 +970,27 @@ Each item below is one setting, named by its config key:
|
|||||||
`Referer`, for example with `Referrer-Policy: no-referrer`, is not stopped.
|
`Referer`, for example with `Referrer-Policy: no-referrer`, is not stopped.
|
||||||
The login and generator pages are not covered.
|
The login and generator pages are not covered.
|
||||||
- `blocked_networks`: variable `PIXA_BLOCKED_NETWORKS`, default none. CIDR
|
- `blocked_networks`: variable `PIXA_BLOCKED_NETWORKS`, default none. CIDR
|
||||||
ranges pixa never fetches from, on top of the built-in ones. The built-in
|
ranges pixa never fetches from, on top of the built-in ones. An invalid CIDR
|
||||||
ranges are loopback, private, link-local, carrier-grade NAT, benchmarking,
|
aborts startup. An IPv6 address in the IPv4-mapped form (`::ffff:a.b.c.d`) is
|
||||||
NAT64 and the like, and always apply. An invalid CIDR aborts startup. An IPv6
|
matched only as the IPv4 address it holds. So an IPv6 range such as `::/0`
|
||||||
address in the IPv4-mapped form (`::ffff:a.b.c.d`) is matched only as the IPv4
|
refuses neither it nor a plain IPv4 address. An entry written in that form is
|
||||||
address it holds. So an IPv6 range such as `::/0` refuses neither it nor a
|
read as the IPv4 range it covers: `::ffff:10.0.0.0/104` refuses what
|
||||||
plain IPv4 address. An entry written in that form is read as the IPv4 range it
|
`10.0.0.0/8` refuses. Such an entry shorter than `/96` covers more than IPv4
|
||||||
covers: `::ffff:10.0.0.0/104` refuses what `10.0.0.0/8` refuses. Such an entry
|
addresses and aborts startup. The built-in ranges always apply. They are:
|
||||||
shorter than `/96` covers more than IPv4 addresses and aborts startup.
|
- `0.0.0.0/8`, this network, `0.0.0.0` included;
|
||||||
|
- `10.0.0.0/8`, `172.16.0.0/12` and `192.168.0.0/16`, the private ranges;
|
||||||
|
- `100.64.0.0/10`, carrier-grade NAT;
|
||||||
|
- `127.0.0.0/8`, loopback;
|
||||||
|
- `169.254.0.0/16`, link-local;
|
||||||
|
- `192.0.0.0/24`, IETF protocol assignments;
|
||||||
|
- `198.18.0.0/15`, benchmarking;
|
||||||
|
- `224.0.0.0/4`, multicast;
|
||||||
|
- `::`, the unspecified address;
|
||||||
|
- `::1`, loopback;
|
||||||
|
- `64:ff9b::/96` and `64:ff9b:1::/48`, NAT64;
|
||||||
|
- `fc00::/7`, the unique local (private) range;
|
||||||
|
- `fe80::/10`, link-local;
|
||||||
|
- `ff00::/8`, multicast.
|
||||||
- `trusted_proxies`: variable `PIXA_TRUSTED_PROXIES`, default the RFC 1918
|
- `trusted_proxies`: variable `PIXA_TRUSTED_PROXIES`, default the RFC 1918
|
||||||
private ranges: `10.0.0.0/8`, `172.16.0.0/12` and `192.168.0.0/16`. CIDR
|
private ranges: `10.0.0.0/8`, `172.16.0.0/12` and `192.168.0.0/16`. CIDR
|
||||||
ranges of the reverse proxies in front of pixa. `X-Forwarded-For` is believed
|
ranges of the reverse proxies in front of pixa. `X-Forwarded-For` is believed
|
||||||
@@ -964,16 +1011,18 @@ Each item below is one setting, named by its config key:
|
|||||||
is for testing only.
|
is for testing only.
|
||||||
- `upstream_connections_per_host`: variable
|
- `upstream_connections_per_host`: variable
|
||||||
`PIXA_UPSTREAM_CONNECTIONS_PER_HOST`, default `20`. The most connections to
|
`PIXA_UPSTREAM_CONNECTIONS_PER_HOST`, default `20`. The most connections to
|
||||||
one upstream host at once. It must be at least 1.
|
one upstream host at once. It must be at least 1. A fetch that finds every
|
||||||
|
connection to its host in use waits for one until `downstream_timeout` ends
|
||||||
|
(see Limits).
|
||||||
- `upstream_connections`: variable `PIXA_UPSTREAM_CONNECTIONS`, default `64`.
|
- `upstream_connections`: variable `PIXA_UPSTREAM_CONNECTIONS`, default `64`.
|
||||||
The most connections to all upstream hosts together at once, on top of the
|
The most connections to all upstream hosts together at once, on top of the
|
||||||
limit per host. It must be at least 1. A fetch holds its connection until its
|
limit per host. It must be at least 1. A fetch holds its connection until its
|
||||||
image has been processed (see Limits).
|
image has been processed (see Limits).
|
||||||
- `max_concurrent_processing`: variable `PIXA_MAX_CONCURRENT_PROCESSING`,
|
- `max_concurrent_processing`: variable `PIXA_MAX_CONCURRENT_PROCESSING`,
|
||||||
default half the CPUs pixa can use, rounded down, and at least 1. The most
|
default half the CPUs Go uses (`GOMAXPROCS`), rounded down, and at least 1.
|
||||||
images decoded and encoded at once. The number of CPUs follows a container's
|
The most images decoded and encoded at once. `GOMAXPROCS` follows a
|
||||||
CPU limit. A value that is not a whole number of at least 1 aborts startup.
|
container's CPU limit. A value that is not a whole number of at least 1 aborts
|
||||||
Each image can take a lot of memory (see Running under upaas).
|
startup. Each image can take a lot of memory (see Running under upaas).
|
||||||
- `image_requests_per_minute`: variable `PIXA_IMAGE_REQUESTS_PER_MINUTE`,
|
- `image_requests_per_minute`: variable `PIXA_IMAGE_REQUESTS_PER_MINUTE`,
|
||||||
default `600`. How many requests one client may send per minute to
|
default `600`. How many requests one client may send per minute to
|
||||||
`/v1/image/` and `/v1/e/` together (see Limits). `0` turns the limit off. A
|
`/v1/image/` and `/v1/e/` together (see Limits). `0` turns the limit off. A
|
||||||
@@ -996,11 +1045,12 @@ Each item below is one setting, named by its config key:
|
|||||||
digits, hyphens and dots, with a letter in its last part) or an IP address
|
digits, hyphens and dots, with a letter in its last part) or an IP address
|
||||||
(IPv6 in brackets, in its shortest form). It may have a port from 1 to 65535
|
(IPv6 in brackets, in its shortest form). It may have a port from 1 to 65535
|
||||||
that has no leading zero and is not the scheme's default. Any other value
|
that has no leading zero and is not the scheme's default. Any other value
|
||||||
aborts startup, another scheme such as a browser extension's included.
|
aborts startup, including an origin with another scheme, such as a browser
|
||||||
|
extension's.
|
||||||
- `metrics.username`: variable `PIXA_METRICS_USERNAME`, default empty. The
|
- `metrics.username`: variable `PIXA_METRICS_USERNAME`, default empty. The
|
||||||
username for `/metrics`. pixa serves `/metrics`, and measures requests for it,
|
username for `/metrics`. pixa serves `/metrics`, and measures every HTTP
|
||||||
only when the username and the password are both set. Setting one without the
|
request, only when the username and the password are both set. Setting one
|
||||||
other aborts startup.
|
without the other aborts startup.
|
||||||
- `metrics.password`: variable `PIXA_METRICS_PASSWORD`, default empty. The
|
- `metrics.password`: variable `PIXA_METRICS_PASSWORD`, default empty. The
|
||||||
password for `/metrics`. Set it together with the username.
|
password for `/metrics`. Set it together with the username.
|
||||||
- `sentry_dsn`: variable `PIXA_SENTRY_DSN`, default empty. Sentry is an error
|
- `sentry_dsn`: variable `PIXA_SENTRY_DSN`, default empty. Sentry is an error
|
||||||
@@ -1009,12 +1059,12 @@ Each item below is one setting, named by its config key:
|
|||||||
- `debug`: variable `PIXA_DEBUG`, default `false`. Turns on debug logging and
|
- `debug`: variable `PIXA_DEBUG`, default `false`. Turns on debug logging and
|
||||||
plain-HTTP local development (see Routes).
|
plain-HTTP local development (see Routes).
|
||||||
- `maintenance_mode`: variable `PIXA_MAINTENANCE_MODE`, default `false`. While
|
- `maintenance_mode`: variable `PIXA_MAINTENANCE_MODE`, default `false`. While
|
||||||
`true`, `/v1/image/` and `/v1/e/` answer every request with 503, a
|
`true`, `/v1/image/` and `/v1/e/` answer every `GET` and `HEAD` request with
|
||||||
`Retry-After` header of 300 seconds and a JSON error body. The health check
|
503, a `Retry-After` header of 300 seconds and a JSON error body. The health
|
||||||
still answers 200 and reports `"maintenance_mode": true`. It stays 200 because
|
check still answers 200 and reports `"maintenance_mode": true`. It stays 200
|
||||||
the image's Docker `HEALTHCHECK` requests it, and upaas marks a deploy failed
|
because the image's Docker `HEALTHCHECK` requests it, and upaas marks a deploy
|
||||||
when its container is unhealthy. The login and generator pages and `/metrics`
|
failed when its container is unhealthy. The login and generator pages and
|
||||||
keep working.
|
`/metrics` keep working.
|
||||||
|
|
||||||
`configs/config.example.yml` names every setting with its default.
|
`configs/config.example.yml` names every setting with its default.
|
||||||
|
|
||||||
@@ -1022,7 +1072,7 @@ Each item below is one setting, named by its config key:
|
|||||||
|
|
||||||
- **Dependency injection**: Uber fx, a library that builds the program's parts
|
- **Dependency injection**: Uber fx, a library that builds the program's parts
|
||||||
in dependency order and starts and stops them. `internal/app` puts every part
|
in dependency order and starts and stops them. `internal/app` puts every part
|
||||||
of pixad in one fx graph. `internal/providers` builds the image cache, the
|
of `pixad` in one fx graph. `internal/providers` builds the image cache, the
|
||||||
image proxy and the other parts that serve images and logins. Each setting's
|
image proxy and the other parts that serve images and logins. Each setting's
|
||||||
default is declared once, in `internal/config`.
|
default is declared once, in `internal/config`.
|
||||||
- **Packages**: these packages under `internal/` work with the concepts above:
|
- **Packages**: these packages under `internal/` work with the concepts above:
|
||||||
@@ -1042,7 +1092,7 @@ Each item below is one setting, named by its config key:
|
|||||||
- `session`: the login session
|
- `session`: the login session
|
||||||
- **HTTP router**: go-chi
|
- **HTTP router**: go-chi
|
||||||
- **Image processing**: govips, a Go wrapper for libvips
|
- **Image processing**: govips, a Go wrapper for libvips
|
||||||
- **Database**: SQLite via modernc.org/sqlite
|
- **Database**: SQLite via `modernc.org/sqlite`
|
||||||
- **Static assets**: embedded in the binary via `//go:embed`
|
- **Static assets**: embedded in the binary via `//go:embed`
|
||||||
- **Metrics**: Prometheus, at `/metrics`. Prometheus is a monitoring system that
|
- **Metrics**: Prometheus, at `/metrics`. Prometheus is a monitoring system that
|
||||||
reads metrics from that route. pixa serves `/metrics` and measures HTTP
|
reads metrics from that route. pixa serves `/metrics` and measures HTTP
|
||||||
@@ -1074,21 +1124,22 @@ Each item below is one setting, named by its config key:
|
|||||||
This repository adheres to the
|
This repository adheres to the
|
||||||
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
|
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
|
||||||
standard. The scripts in `script/` are the entrypoints for the development
|
standard. The scripts in `script/` are the entrypoints for the development
|
||||||
workflow, and the Makefile targets are thin shims that call them. We provide:
|
workflow, and most Makefile targets are thin shims that call them. We provide:
|
||||||
|
|
||||||
- `script/bootstrap` — install git, make, Go, Node, Yarn and prettier, and
|
- `script/bootstrap` — install git, make, Go, Node, Yarn and prettier, and
|
||||||
download the Go modules. It can be run again at any time. With `--cgo`, it
|
download the Go modules. It can be run again at any time. With `--cgo`, it
|
||||||
installs the C compiler and the libvips (with its JPEG XL support) and libheif
|
installs a C compiler, `pkg-config`, libvips (with its JPEG XL support) and
|
||||||
libraries instead of Node, Yarn and prettier. Compiling and testing pixa need
|
libheif instead of Node, Yarn and prettier. Compiling pixa outside Docker, as
|
||||||
those.
|
`make build` and `script/bench` do, needs those. Docker itself must already be
|
||||||
|
installed.
|
||||||
- `script/setup` — make a fresh clone ready for development: bootstrap, then
|
- `script/setup` — make a fresh clone ready for development: bootstrap, then
|
||||||
install-precommit.
|
install-precommit.
|
||||||
- `script/projectname` — output the project name (`pixa`).
|
- `script/projectname` — output the project name (`pixa`).
|
||||||
- `script/test` — run the test suite: build the `test` phase of the
|
- `script/test` — run the test suite: build the `test` phase of the
|
||||||
`Dockerfile`, writing no image.
|
`Dockerfile`, writing no image.
|
||||||
- `script/lint` — run golangci-lint: build the `lint` phase of the `Dockerfile`,
|
- `script/lint` — run `golangci-lint`: build the `lint` phase of the
|
||||||
writing no image. The linter never runs on the host.
|
`Dockerfile`, writing no image. The linter never runs on the host.
|
||||||
- `script/fmt` — format the Go code with gofmt and the markdown with prettier
|
- `script/fmt` — format the Go code with `gofmt` and the markdown with prettier
|
||||||
(writes).
|
(writes).
|
||||||
- `script/fmt-check` — check the same formatting (read-only), on the host.
|
- `script/fmt-check` — check the same formatting (read-only), on the host.
|
||||||
- `script/check` — run test, lint, and fmt-check.
|
- `script/check` — run test, lint, and fmt-check.
|
||||||
@@ -1097,8 +1148,8 @@ workflow, and the Makefile targets are thin shims that call them. We provide:
|
|||||||
and `test` phases, so this runs them too.
|
and `test` phases, so this runs them too.
|
||||||
- `script/docker-smoke` — build the image, start it, and wait for it to be
|
- `script/docker-smoke` — build the image, start it, and wait for it to be
|
||||||
healthy.
|
healthy.
|
||||||
- `script/loadtest` — measure pixad's throughput, latency and peak memory. It is
|
- `script/loadtest` — measure `pixad`'s throughput, latency and peak memory. It
|
||||||
a benchmark, not part of `script/check` (see Load Test).
|
is a benchmark, not part of `script/check` (see Load Test).
|
||||||
- `script/bench` — run the Go benchmark that serves cache hits in parallel
|
- `script/bench` — run the Go benchmark that serves cache hits in parallel
|
||||||
through the image handler, with Go on the host after `script/bootstrap --cgo`.
|
through the image handler, with Go on the host after `script/bootstrap --cgo`.
|
||||||
It is a benchmark, not part of `script/check` (see Load Test).
|
It is a benchmark, not part of `script/check` (see Load Test).
|
||||||
@@ -1110,28 +1161,42 @@ workflow, and the Makefile targets are thin shims that call them. We provide:
|
|||||||
`script/precommit`.
|
`script/precommit`.
|
||||||
|
|
||||||
Every `docker build` in these scripts passes a new value of the `CACHEBUST`
|
Every `docker build` in these scripts passes a new value of the `CACHEBUST`
|
||||||
build argument, which every stage of the `Dockerfile` declares. So every `RUN`
|
build argument. Every stage of the `Dockerfile` declares it with an
|
||||||
step after that line, the lint and test steps included, runs on every build
|
`ARG CACHEBUST` line. So every `RUN` step after that line, the lint and test
|
||||||
instead of coming from the build cache. Above the line are the steps that
|
steps included, runs on every build instead of coming from the build cache. The
|
||||||
install system packages and download the Go modules. Docker keeps their layers
|
steps above that line install system packages and download the Go modules.
|
||||||
until the base image, the step itself, `go.mod` or `go.sum` changes. Go's build
|
Docker keeps their layers until the base image, the step itself, `go.mod` or
|
||||||
cache is kept from build to build: the steps that compile mount it, so a package
|
`go.sum` changes.
|
||||||
whose inputs are unchanged is not compiled again. Both `go test` runs pass
|
|
||||||
|
Go's build cache is kept from build to build: the steps that compile mount it,
|
||||||
|
so a package whose inputs are unchanged is not compiled again. The test step
|
||||||
|
runs `go test`, and reruns it with `-v` after a failure. Both runs pass
|
||||||
`-count=1`, so no stored test result is replayed. The lint step also mounts
|
`-count=1`, so no stored test result is replayed. The lint step also mounts
|
||||||
golangci-lint's cache. That cache reuses a package's findings only while its
|
`golangci-lint`'s cache. That cache reuses a package's findings only while its
|
||||||
files, its imports, `go.mod`, `.golangci.yml` and the golangci-lint version are
|
files, its imports, `go.mod`, `.golangci.yml` and the `golangci-lint` version
|
||||||
unchanged. The `test` phase runs on the Debian Go image, with Debian's libvips
|
are unchanged.
|
||||||
and libheif. The image pixa ships is built and runs on Alpine. These scripts are
|
|
||||||
the standard copies from `sneak/prompts`, kept identical to them:
|
The `test` phase runs on the Debian Go image, with Debian's libvips and libheif.
|
||||||
`script/check`, `script/cibuild`, `script/docker`, `script/lint`, `script/test`,
|
The image pixa ships is built and runs on Alpine.
|
||||||
`script/setup` and `script/install-precommit`. `script/fmt` and
|
|
||||||
`script/fmt-check` are the standard copies with pixa's `gofmt` step kept before
|
These scripts are the standard copies from `sneak/prompts`, kept identical to
|
||||||
prettier. prettier formats the markdown only, not the HTML templates, as it
|
them:
|
||||||
cannot parse a Go template action inside a tag.
|
|
||||||
|
- `script/check`
|
||||||
|
- `script/cibuild`
|
||||||
|
- `script/docker`
|
||||||
|
- `script/lint`
|
||||||
|
- `script/test`
|
||||||
|
- `script/setup`
|
||||||
|
- `script/install-precommit`
|
||||||
|
|
||||||
|
`script/fmt` and `script/fmt-check` are the standard copies with pixa's `gofmt`
|
||||||
|
step kept before prettier. prettier formats the markdown only, not the HTML
|
||||||
|
templates, as it cannot parse a Go template action inside a tag.
|
||||||
|
|
||||||
## Load Test
|
## Load Test
|
||||||
|
|
||||||
`script/loadtest` (or `make loadtest`) measures how fast pixad answers and how
|
`script/loadtest` (or `make loadtest`) measures how fast `pixad` answers and how
|
||||||
much memory it uses. It is a benchmark, not a check: `script/check` does not run
|
much memory it uses. It is a benchmark, not a check: `script/check` does not run
|
||||||
it. It needs Docker and Go.
|
it. It needs Docker and Go.
|
||||||
|
|
||||||
@@ -1140,23 +1205,24 @@ script/loadtest # 10 seconds per scenario, 4 clients
|
|||||||
script/loadtest 30s 32 # 30 seconds per scenario, 32 clients
|
script/loadtest 30s 32 # 30 seconds per scenario, 32 clients
|
||||||
```
|
```
|
||||||
|
|
||||||
It builds the image with `script/docker` and the load tool,
|
It builds the image with `script/docker`. It also builds the load tool,
|
||||||
[vegeta](https://github.com/tsenart/vegeta), from a pinned commit. Each scenario
|
[vegeta](https://github.com/tsenart/vegeta), from a pinned commit. Each scenario
|
||||||
starts a new pixad container and a new origin container, `cmd/loadtest-origin`.
|
starts a new `pixad` container and a new origin container,
|
||||||
The origin is an upstream host that answers every path with the same generated
|
`cmd/loadtest-origin`. The origin is an upstream host that answers every path
|
||||||
1600x1200 JPEG. vegeta then sends requests from the given number of clients.
|
with the same generated 1600x1200 JPEG. vegeta then sends requests from the
|
||||||
Each client sends its next request as soon as its last one is answered. Every
|
given number of clients. Each client sends its next request as soon as its last
|
||||||
request asks for an image resized to 400x300 WebP:
|
one is answered. Every request asks for an image resized to 400x300 WebP:
|
||||||
|
|
||||||
- `hit`: the same image every time, put in the cache first;
|
- `hit`: the same image every time, put in the cache first;
|
||||||
- `miss`: a new source image every time, so pixad fetches and converts each one;
|
- `miss`: a new source image every time, so `pixad` fetches and converts each
|
||||||
- `herd`: each new source image once per client in a row. All clients then ask
|
one;
|
||||||
for it at the same time and share one fetch and one conversion (see Limits).
|
- `herd`: every client asks for each new source image at the same moment. The
|
||||||
|
clients then share one fetch and one conversion (see Limits).
|
||||||
|
|
||||||
pixad refuses upstream hosts with private or local addresses, so the containers
|
`pixad` refuses upstream hosts with private or local addresses, so the
|
||||||
share a Docker network in `203.0.113.0/24`, a range set aside for documentation.
|
containers share a Docker network in `203.0.113.0/24`, a range set aside for
|
||||||
A second run on the same Docker host while one is going fails, as it cannot
|
documentation. A second run on the same Docker host while one is going fails, as
|
||||||
create that network.
|
it cannot create that network.
|
||||||
|
|
||||||
For each scenario the script prints vegeta's report and two lines of its own:
|
For each scenario the script prints vegeta's report and two lines of its own:
|
||||||
|
|
||||||
@@ -1166,19 +1232,20 @@ For each scenario the script prints vegeta's report and two lines of its own:
|
|||||||
to the end of its answer. `50`, `95` and `99` are the 50th, 95th and 99th
|
to the end of its answer. `50`, `95` and `99` are the 50th, 95th and 99th
|
||||||
percentiles;
|
percentiles;
|
||||||
- `Status Codes` and `Error Set`: anything other than `200` means the other
|
- `Status Codes` and `Error Set`: anything other than `200` means the other
|
||||||
numbers are not for the scenario described, such as `503` when pixad was busy;
|
numbers are not for the scenario described, such as `503` when `pixad` was
|
||||||
|
busy;
|
||||||
- `Bytes In`: `0`, as vegeta is told not to keep the images it receives;
|
- `Bytes In`: `0`, as vegeta is told not to keep the images it receives;
|
||||||
- `pixad peak memory (VmHWM)`: the peak resident memory of pixad's process since
|
- `pixad peak memory (VmHWM)`: the peak resident memory of the `pixad` process
|
||||||
its container started, in kB; for `hit` it includes the request that put the
|
since its container started, in kB; for `hit` it includes the request that put
|
||||||
image in the cache;
|
the image in the cache;
|
||||||
- `requests to the origin`: the fetches pixad made. That is one for `hit`, one
|
- `requests to the origin`: the fetches `pixad` made. That is one for `hit`, one
|
||||||
per request for `miss`, and one per image for `herd`: the requests sent
|
per request for `miss`, and one per image for `herd`: the requests sent
|
||||||
divided by the number of clients.
|
divided by the number of clients.
|
||||||
|
|
||||||
The numbers depend on the machine and on whatever else runs on it.
|
The numbers depend on the machine and on whatever else runs on it.
|
||||||
|
|
||||||
`script/bench` (or `make bench`) measures cache hits alone, without the network
|
`script/bench` (or `make bench`) measures cache hits alone, without the network
|
||||||
and with no pixad process. A Go benchmark in `internal/handlers` serves one
|
and with no `pixad` process. A Go benchmark in `internal/handlers` serves one
|
||||||
cached image through the `/v1/image/` handler from as many goroutines at once as
|
cached image through the `/v1/image/` handler from as many goroutines at once as
|
||||||
the machine has CPUs. It reports the hits served per second as `hits/s`. Each
|
the machine has CPUs. It reports the hits served per second as `hits/s`. Each
|
||||||
run starts with an empty cache and an empty database, a SQLite file of its own,
|
run starts with an empty cache and an empty database, a SQLite file of its own,
|
||||||
|
|||||||
Reference in new issue
Block a user