From 334dda611c4230c3c3758c4b051b14eacf294077 Mon Sep 17 00:00:00 2001 From: Patrik Keller Date: Tue, 17 Mar 2020 18:59:40 +0100 Subject: [PATCH] Tutorial: Providing images for (#307) --- how-tos/providing-images-for-img.rst | 126 +++++++++++++++++++++++++++ index.rst | 1 + 2 files changed, 127 insertions(+) create mode 100644 how-tos/providing-images-for-img.rst diff --git a/how-tos/providing-images-for-img.rst b/how-tos/providing-images-for-img.rst new file mode 100644 index 000000000..8bb510087 --- /dev/null +++ b/how-tos/providing-images-for-img.rst @@ -0,0 +1,126 @@ +Providing images for +========================== + +:author: `pkel `_ + +In this how-to, you will learn how to create an endpoint for providing images to HTML :code:`` tags without client side javascript. +The resulting HTML might look like this: + +.. code-block:: html + + 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:`` 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//* ---> /files?select=blob&id=eq. + 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:`` 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; diff --git a/index.rst b/index.rst index 75f5c5bc1..0f7deaea9 100644 --- a/index.rst +++ b/index.rst @@ -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 ------------