Tutorial: Providing images for <img> (#307)

This commit is contained in:
Patrik Keller
2020-03-17 12:59:40 -05:00
committed by GitHub
parent a343536539
commit 334dda611c
2 changed files with 127 additions and 0 deletions
+126
View File
@@ -0,0 +1,126 @@
Providing images for <img>
==========================
:author: `pkel <https://github.com/pkel>`_
In this how-to, you will learn how to create an endpoint for providing images to HTML :code:`<img>` tags without client side javascript.
The resulting HTML might look like this:
.. code-block:: html
<img src="http://host/files/42/cats.jpeg" alt="Cute Kittens"/>
In fact, the presented technique is suitable for providing not only images, but arbitrary files.
We will start with a minimal example that highlights the general concept.
Afterwards we present are more detailed solution that fixes a few shortcomings of the first approach.
Minimal Example
---------------
PostgREST returns binary data on requests that set the :code:`Accept: application/octet-stream` header.
The general idea is to configure the reverse proxy in front of the API to set this header for all requests to :code:`/files/`.
We will show how to achieve this using Nginx.
First, we need a public table for storing the files.
.. code-block:: postgres
create table files(
id int primary key
, blob bytea
);
Let's assume this table contains an image of two cute kittens with id 42.
We can retrieve this image in binary format from our PostgREST API by requesting :code:`/files?select=blob&id=eq.42` with the :code:`Accept: application/octet-stream` header.
Unfortunately, putting the URL into the :code:`src` of an :code:`<img>` tag will not work.
That's because browsers do not send the required header.
Luckily, we can configure our `Nginx reverse proxy <../admin.html>`_ to fix this problem for us.
We assume that PostgREST is running on port 3000.
We provide a new location :code:`/files/` that redirects requests to our endpoint with the :code:`Accept` header set to :code:`application/octet-stream`.
.. code-block:: nginx
server {
# rest of reverse proxy and web server configuration
...
location /files/ {
# /files/<id>/* ---> /files?select=blob&id=eq.<id>
rewrite /files/([^/]+).* /files?select=blob&id=eq.$1 break;
# if id is missing
return 404;
# request binary output
proxy_set_header Accept application/octet-stream;
# usual proxy setup
proxy_hide_header Content-Location;
add_header Content-Location /api/$upstream_http_content_location;
proxy_set_header Connection "";
proxy_http_version 1.1;
proxy_pass http://localhost:3000/;
}
With this setup, we can request the cat image at :code:`localhost/files/42/cats.jpeg` without setting any headers.
In fact, you can replace :code:`cats.jpeg` with any other filename or simply omit it.
Putting the URL into the :code:`src` of an :code:`<img>` tag should now work as expected.
Improved Version
----------------
The basic solution has some shortcomings:
1. The response :code:`Content-Type` header is set to :code:`application/octet-stream`.
This might confuse clients and users.
2. Download requests (e.g. Right Click -> Save Image As) to :code:`files/42` will propose :code:`42` as filename.
This might confuse users.
3. Requests to the binary endpoint are not cached.
This will cause unnecessary load on the database.
The following improved version addresses these problems.
First, we store the media types and names of our files in the database.
.. code-block:: postgres
create table files(
id int primary key
, type text
, name text
, blob bytea
);
Next, we set up an RPC endpoint that sets the content type and filename.
We use this opportunity to configure some basic, client-side caching.
For production, you probably want to configure additional caches, e.g. on the reverse proxy.
.. code-block:: postgres
create function file(id int) returns bytea as
$$
declare headers text;
declare blob bytea;
begin
select format(
'[{"Content-Type": "%s"},'
'{"Content-Disposition": "inline; filename=\"%s\""},'
'{"Cache-Control": "max-age=259200"}]'
, files.type, files.name)
from files where files.id = file.id into headers;
perform set_config('response.headers', headers, true);
select files.blob from files where files.id = file.id into blob;
if found
then return(blob);
else raise sqlstate 'PT404' using
message = 'NOT FOUND',
detail = 'File not found',
hint = format('%s seems to be an invalid file id', file.id);
end if;
end
$$ language plpgsql;
With this, we can obtain the cat image from :code:`/rpc/file?id=42`.
Consequently, we have to replace our previous rewrite rule in the Nginx recipe with the following.
.. code-block:: nginx
rewrite /files/([^/]+).* /rpc/file?id=$1 break;
+1
View File
@@ -126,6 +126,7 @@ These are recipes that'll help you address specific use-cases.
- :doc:`how-tos/casting-type-to-custom-json`
- :doc:`how-tos/embedding-table-from-another-schema`
- :doc:`how-tos/providing-images-for-img`
Topic guides
------------