Home > Enterprise >  Serve Base64 GIF in Nginx
Serve Base64 GIF in Nginx

Time:09-25

Is it possible to serve a base64 image (GIF) in Nginx that will load in the browser? The encoded string below is a 1x1 white pixel GIF.

I tried this, which isn't working

location /img.gif {
  default_type "image/gif;base64";
  return 200 "R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=";
}

That image loads fine with

<img width="64" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=">

CodePudding user response:

I have tested a njs approach as you are very flexible with this solution. However - I am not understanding the use case to a 100% but if this is the only solution because you are maybe getting the image bytes b64 encoded from another service on the fly and unable to store them as a file that could be the way to go.

First load the NGINX njs module load_module modules/ngx_http_js_module.so;

Create your NJS file

export default {b64d}

function b64d(r) {
  var imgB64 = "R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=";
  var bytes = Buffer.from(imgB64, 'base64');
  return r.return(200, bytes);

}

save this as gif.js

NGINX Configuration

js_import gif from conf.d/gif.js;

server {
  listen 80;
  location / {
    add_header "Content-Type" "image/gif";
    js_content gif.b64d;
  }
}

This will return the GIF fully rendered.

$:/etc/nginx/conf.d# curl -s 127.1 --output test.gif && file test.gif
test.gif: GIF image data, version 89a, 1 x 1

With this you are able to decode and send any image you want. Reference: https://nginx.org/en/docs/njs/reference.html#string

CodePudding user response:

It seems like you suffer from a misconception about what base64 is supposed to achieve in the first place. The purpose of encodings like base64 is to inline arbitrary binary data into a plain text document. That's why you use base64 encodings to embed data into HTML or CSS.

However on the HTTP transport level, past the initial headers (which are plain text) the actual content is transferred as-is. You'd use base64 to embed the GIF into a HTTP header (where it'd sit rather useless, unless a HTTP client interprets it in some way).

The nginx return statement specifies the very content, that's sent right after the headers. And since the nginx configuration is a text file, it'd be a tough call to embed a GIF there. BUT: Although you can encode and place a GIF there in base64 perfectly well, which will transmitted just so, image/gif;base64 isn't a content type expected by a browser for regular HTTP. It's quite nonsensical; why bloat the transmission with an encoding where binary is just fine.

Technically it would be possible to patch nginx to decode the base64 on its end, but why go through all this effort?

The solution is simple: Just serve the file! Put your white pixel GIF as a file on your server, and in your location rule send out this one specific file. This is the most straightforward, least bandwidth consuming, most efficient way to do it.

Update

I think I should maybe elaborate, why serving out an on-disk file is the most efficient way to serve this pixel GIF:

Most operating systems kernels offer a syscall that allows to directly submit the contents of a file into a stream (pipe, socket, etc.); this syscall is usually called sendfile. Obviously nginx uses sendfile:

https://github.com/nginx/nginx/blob/master/src/os/unix/ngx_darwin_sendfile_chain.c

https://github.com/nginx/nginx/blob/master/src/os/unix/ngx_solaris_sendfilev_chain.c

https://github.com/nginx/nginx/blob/master/src/os/unix/ngx_freebsd_sendfile_chain.c

https://github.com/nginx/nginx/blob/master/src/os/unix/ngx_linux_sendfile_chain.c

Now it becomes important to understand how modern OSs allocate and manage memory: Ever since paging and swap memory was invented, to make life simpler, the memory allocations user space applications are working with are in fact cut from the storage I/O cache buffers (in case there's a swap partition/file this cache buffer is mapped to that swap partition, so when stuff needs to be paged out, all the management structures are already in place).

In short, when you read from a file, eventually things will go through this I/O cache, the "hot" parts of file will be cached there, without additional programmer effort. sendfile will simply send out those in-cache memory without invoking any file read-to-buffer write-from-buffer copy overhead. If it's a "hot" file, it will be sent out from there, with the in-memory copy already at hand. And if it's not resident, then the storage I/O will pull it directly into the socket send buffers.

Moreover, for a few years now, at least Linux and FreeBSD support socket level TLS (https://www.kernel.org/doc/html/v5.6/networking/tls.html); this is nifty. If you got "standard" hardware, the TLS will be done in software (of course using the cryptographic instructions of the CPU if those are available). But there're network adapters that implement hardware TLS acceleration; and to reach that offload, you also go through the socket TLS layer. Using sendfile on that, will give you the shortest, most direct way for your GIF's data out to the network.

Now compare this with putting the GIF into your nginx configuration: The nginx-process has to hold that string somewhere in its memory. It's most likely not properly page aligned. When the request is made, that piece of memory must be wrapped up with the headers, then send to the socket, where a copy of that memory is made. The potential overhead is much larger.

Long story short: You want it to be efficient: Serve a file!

  • Related