Add bytea to working with types and improve img example (#542)

This commit is contained in:
Laurence Isla
2022-05-27 17:12:47 -05:00
committed by GitHub
parent 209b70cbfc
commit 96c169e593
4 changed files with 104 additions and 56 deletions
+23 -55
View File
@@ -5,25 +5,18 @@ Providing images for ``<img>``
:author: `pkel <https://github.com/pkel>`_ :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. In this how-to, you will learn how to create an endpoint for providing images to HTML :code:`<img>` tags without client side JavaScript. In fact, the presented technique is suitable for providing not only images, but arbitrary files.
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. 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. Afterwards we present a more detailed solution that fixes a few shortcomings of the first approach.
.. warning::
Be careful when saving binaries in the database, having a separate storage service for these is preferable in most cases. See `Storing Binary files in the Database <https://wiki.postgresql.org/wiki/BinaryFilesInDB>`_.
Minimal Example 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. First, we need a public table for storing the files.
.. code-block:: postgres .. code-block:: postgres
@@ -36,64 +29,40 @@ First, we need a public table for storing the files.
Let's assume this table contains an image of two cute kittens with id 42. 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. 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. 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. That's because browsers do not send the required :code:`Accept: application/octet-stream` header.
Luckily, we can configure our :doc:`Nginx reverse proxy <../admin>` to fix this problem for us. Luckily we can specify the accepted media types in the :ref:`raw-media-types` configuration variable.
We assume that PostgREST is running on port 3000. In this case, the :code:`Accept: image/webp` header is sent by many web browsers by default, so let's add it to the configuration variable, like this: :code:`raw-media-types="image/webp"`.
We provide a new location :code:`/files/` that redirects requests to our endpoint with the :code:`Accept` header set to :code:`application/octet-stream`. Now, the image will be displayed in the HTML page:
.. code-block:: nginx .. code-block:: html
server { <img src="http://localhost:3000/files?select=blob&id=eq.42" alt="Cute Kittens"/>
# 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 Improved Version
---------------- ----------------
The basic solution has some shortcomings: The basic solution has some shortcomings:
1. The response :code:`Content-Type` header is set to :code:`application/octet-stream`. 1. The response :code:`Content-Type` header is set to :code:`image/webp`.
This might confuse clients and users. This might be a problem if you want to specify a different format for the file.
2. Download requests (e.g. Right Click -> Save Image As) to :code:`files/42` will propose :code:`42` as filename. 2. Download requests (e.g. Right Click -> Save Image As) to :code:`/files?select=blob&id=eq.42` will propose :code:`files` as filename.
This might confuse users. This might confuse users.
3. Requests to the binary endpoint are not cached. 3. Requests to the binary endpoint are not cached.
This will cause unnecessary load on the database. This will cause unnecessary load on the database.
The following improved version addresses these problems. The following improved version addresses these problems.
First, we store the media types and names of our files in the database. First, in addition to the minimal example, we need to store the media types and names of our files in the database.
.. code-block:: postgres .. code-block:: postgres
create table files( alter table files
id int primary key add column type text,
, type text add column name text;
, name text
, blob bytea
);
Next, we set up an RPC endpoint that sets the content type and filename. 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. 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. For production, you probably want to configure additional caches, e.g. on the :ref:`reverse proxy <admin>`.
.. code-block:: postgres .. code-block:: postgres
@@ -120,9 +89,8 @@ For production, you probably want to configure additional caches, e.g. on the re
end end
$$ language plpgsql; $$ language plpgsql;
With this, we can obtain the cat image from :code:`/rpc/file?id=42`. With this, we can obtain the cat image from :code:`/rpc/file?id=42`. Thus, the resulting HTML will be:
Consequently, we have to replace our previous rewrite rule in the Nginx recipe with the following.
.. code-block:: nginx .. code-block:: html
rewrite /files/([^/]+).* /rpc/file?id=$1 break; <img src="http://localhost:3000/rpc/file?id=42" alt="Cute Kittens"/>
@@ -5,6 +5,10 @@ Working with PostgreSQL data types
PostgREST makes use of PostgreSQL string representations to work with data types. Thanks to this, you can use special values, such as ``now`` for timestamps, ``yes`` for booleans or time values including the time zones. This page describes how you can take advantage of these string representations to perform operations on different PostgreSQL data types. PostgREST makes use of PostgreSQL string representations to work with data types. Thanks to this, you can use special values, such as ``now`` for timestamps, ``yes`` for booleans or time values including the time zones. This page describes how you can take advantage of these string representations to perform operations on different PostgreSQL data types.
.. contents::
:local:
:depth: 1
Timestamps Timestamps
---------- ----------
@@ -461,6 +465,80 @@ Finally, do the request :ref:`casting the range column <casting_columns>`:
create cast (mytsrange as json) with function mytsrange_to_json(mytsrange) as assignment; create cast (mytsrange as json) with function mytsrange_to_json(mytsrange) as assignment;
Bytea
-----
To send raw binary to PostgREST you need a function with a single unnamed parameter of `bytea type <https://www.postgresql.org/docs/current/datatype-binary.html>`_. For example, let's create a table that will save some files and a function that inserts data to that table:
.. code-block:: postgres
create table files (
id int primary key generated always as identity,
file bytea
);
create function upload_binary(bytea) returns void as $$
insert into files (file) values ($1);
$$ language sql;
Next, let's use the PostgREST logo for our test.
.. code-block:: bash
curl "https://postgrest.org/en/latest/_images/logo.png" -o postgrest-logo.png
Now, to send the file ``postgrest-logo.png`` we need to set the ``Content-Type: application/octet-stream`` header in the request:
.. tabs::
.. code-tab:: http
POST /rpc/upload_binary HTTP/1.1
Content-Type: application/octet-stream
postgrest-logo.png
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/upload_binary" \
-X POST -H "Content-Type: application/octet-stream" \
--data-binary "@postgrest-logo.png"
To get the image from the database, you will need to set the ``Accept: application/octet-stream`` header in the request and select only the
``bytea`` column.
.. tabs::
.. code-tab:: http
GET /files?select=file&id=eq.1 HTTP/1.1
Accept: application/octet-stream
.. code-tab:: bash Curl
curl "http://localhost:3000/files?select=file&id=eq.1" \
-H "Accept: application/octet-stream"
You can also use more accurate headers depending on the type of the files by using the :ref:`raw-media-types` configuration. For example, adding the ``raw-media-types="image/png"`` setting to the configuration file will allow you to use the ``Accept: image/png`` header:
.. tabs::
.. code-tab:: http
GET /files?select=file&id=eq.1 HTTP/1.1
Accept: image/png
.. code-tab:: bash Curl
curl "http://localhost:3000/files?select=file&id=eq.1" \
-H "Accept: image/png"
See :ref:`providing_img` for a step-by-step example on how to handle images in HTML.
.. warning::
Be careful when saving binaries in the database, having a separate storage service for these is preferable in most cases. See `Storing Binary files in the Database <https://wiki.postgresql.org/wiki/BinaryFilesInDB>`_.
hstore hstore
------ ------
+2 -1
View File
@@ -203,7 +203,8 @@ These are recipes that'll help you address specific use-cases.
:caption: How-to guides :caption: How-to guides
:hidden: :hidden:
how-tos/* how-tos/working-with-postgresql-data-types
how-tos/providing-images-for-img
- :doc:`how-tos/providing-images-for-img` - :doc:`how-tos/providing-images-for-img`
- :doc:`how-tos/working-with-postgresql-data-types` - :doc:`how-tos/working-with-postgresql-data-types`
+1
View File
@@ -15,6 +15,7 @@ booleans
Bouscal Bouscal
buildpack buildpack
bugfixes bugfixes
Bytea
Cardano Cardano
cd cd
centric centric