Author Topic: What actual data is used to return a favicon to the browser?  (Read 13221 times)

0 Members and 1 Guest are viewing this topic.

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 8349
  • Country: fi
    • My home page and email address
Re: What actual data is used to return a favicon to the browser?
« Reply #50 on: July 05, 2022, 09:45:36 pm »
If you use Linux, or have gzip installed, I recommend you use gzip -kn9 favicon.ico to get maximally gzip-compressed file.
It compresses your 1150-byte favicon.ico (with sha256sum 2c01904c0df69815f18b28d2a7e8e438c353b5352b42fb3699726b332b507be9) to just 111 bytes (included in example below).

Quote
"Content-Length: " STRINGIFY(FAVICON_BODY_LEN) "\r\n"
Is this a GCC compiler function?
No.  It is just a macro
    #define  STRINGIFY_(arg)  #arg
    #define  STRINGIFY(arg)   STRINGIFY_(arg)
that first expands the value of FAVICON_BODY_LEN, and then converts it to a string.

It would be useful to insert a compile-time computed sizeof() into the header.
Yep, but it ain't possible.  You can do something like
Code: [Select]
struct bucket {
    union {
        struct {
            int                         id;   /* == -1 */
            size_t                      value;
        }                               size;
        struct {
            int                         len;  /* > 0 */
            const unsigned char *const  ptr;
        }                               data;
        struct {
            int                         id; /* = 0 */
            size_t                      zero;
        }                               common;
    };
};

static const unsigned char  favicon_header_1[] =
    "HTTP/1.1 200 OK\r\n"
    "Content-Encoding: gzip\r\n"
    "Content-Type: image/x-icon\r\n"
    "Content-Length: "
;
static const unsigned char  favicon_header_2[] =
                       "\r\n"
    "Date: Sat, 01 Jan 2022 00:00:00 GMT\r\n"
    "Cache-Control: max-age=473384600\r\n"
    "\r\n"
;

static const unsigned char favicon_body[] = {
    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x63, 0x60, 0x60, 0x04, 0x42, 0x01,
    0x01, 0x06, 0x20, 0xa9, 0xc0, 0x90, 0xc1, 0xc2, 0xc0, 0x20, 0xc6, 0xc0, 0xc0, 0xa0, 0x01, 0xc4,
    0x40, 0x21, 0xa0, 0x08, 0x44, 0x1c, 0x0c, 0x80, 0x72, 0xde, 0x31, 0x10, 0x0c, 0x03, 0xff, 0x87,
    0x11, 0x60, 0x62, 0x12, 0x80, 0x63, 0x5c, 0x72, 0xc4, 0x98, 0x41, 0x8a, 0x38, 0x31, 0xea, 0x88,
    0xd5, 0x8b, 0x4d, 0x2d, 0x29, 0x7a, 0xd1, 0xfd, 0x49, 0xaa, 0x5e, 0x64, 0x3d, 0xe4, 0xe8, 0x45,
    0xd7, 0x47, 0x89, 0xfd, 0x94, 0xf8, 0x9f, 0x92, 0xb8, 0x43, 0x4f, 0x27, 0xc8, 0x62, 0xe4, 0x86,
    0xc9, 0x60, 0x06, 0x0c, 0x14, 0x02, 0x00, 0xc2, 0xd1, 0x9b, 0xb4, 0x7e, 0x04, 0x00, 0x00
};

static const struct bucket  favicon_response[] = {
    { .data = { .len = sizeof favicon_header_1 - 1, .ptr = favicon_header_1 } },
    { .size = { .id = -1, .value = sizeof favicon_body } },
    { .data = { .len = sizeof favicon_header_2 - 1, .ptr = favicon_header_2 } },
    { .data = { .len = sizeof favicon_body - 1,     .ptr = favicon_body     } },
    { .common = { .id = 0, .zero = 0 } }
};

int send_all(int fd, const struct bucket *all)
{
    int err;

    for (; all->common.id != 0; all++) {
        if (all->common.id > 0) {

            /* all->data */
            err = send_data(fd, all->data.ptr, all->data.len);
            if (err)
                return err;

        } else
        if (all->common.id == -1) {

            /* all->size */
            unsigned char  number[3 * sizeof (size_t) + 4];
            unsigned char *digit = number + sizeof number;
            size_t         value = all->size.value;
            do {
                *(--digit) = '0' + (value % 10);
                value /= 10;
            } while (value > 0);
            err = send_data(fd, digit, (size_t)(number + sizeof number - digit));
            if (err)
                return err;

        } else {

            /* Invalid bucket ID */
            return BAD_BUCKET;

        }
    }

    return 0;
}
where send_data(descriptor, pointer, length) sends data to the recipient, returning 0 if success and an error code otherwise; and BAD_BUCKET is a macro that evaluates to a suitable error code.

The idea is that each bucket can be a string or a size (unsigned integer value), or possibly something else you might wish to use at run time (date and time in HTTP format, perhaps?).

However, at least in Linux, I would just generate a favicon.h header file from a favicon.ico file at build time.

If you have Bash or sh (POSIX-compatible shell), then
Code: [Select]
#!/bin/sh
if [ -r favicon.ico ]; then
    rm -f favicon.ico.gz
    gzip -kn9 favicon.ico
fi
if [ ! -r favicon.ico ]; then
    echo "No 'favicon.ico' file."
    exit 1
fi
bytes="$(od -t x1 -A none favicon.ico.gz | sed -e 's|^ *|0x|; s| |, 0x|g; s|^|    |;s|$|,|')"
length=$(stat -c '%s' favicon.ico.gz)
httpdate="$(LANG=C LC_ALL=C date -uR | sed -e 's| +*-*00*$| GMT|')"
rm -f favicon.h
cat >favicon.h <<EOF
#ifndef   FAVICON_H
#define   FAVICON_H

const unsigned char  FAVICON_HEADER[] =
    "HTTP/1.1 200 OK\r\n"
    "Content-Encoding: gzip\r\n"
    "Content-Type: image/x-icon\r\n"
    "Content-Length: $length\r\n"
    "Date: $httpdate\r\n"
    "Cache-Control: max-age=157680000, public\r\n"
    "\r\n"
;

#define  FAVICON_HEADER_LEN  ((sizeof FAVICON_HEADER) - 1)
#define  FAVICON_BODY_LEN    $length

const unsigned char  FAVICON_BODY[FAVICON_BODY_LEN] = {
$bytes
};

#endif    FAVICON_H
EOF
if run first at build time, will generate a favicon.h file with the proper headers, with todays date and expiry time in five years.
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 6032
  • Country: gb
  • Doing electronics since the 1960s...
Re: What actual data is used to return a favicon to the browser?
« Reply #51 on: July 05, 2022, 09:54:35 pm »
One could send out the header in two parts, with the size dynamically generated (sprinf) at runtime.

Or set up the header in RAM (not a "const") and modify the size.

Or,  in the good old days, use self modifying code ;)
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 8349
  • Country: fi
    • My home page and email address
Re: What actual data is used to return a favicon to the browser?
« Reply #52 on: July 05, 2022, 10:29:37 pm »
One could send out the header in two parts, with the size dynamically generated (sprinf) at runtime.
That's basically the struct bucket implementation above, except that it covers both the header and data, and in an extensible format.

But yes, there definitely are many different ways to implement these, each with their own benefits and drawbacks.
I like these sprawling discussions when approaches are suggested, and those benefits and drawbacks weighed and discussed.
It might not be optimal for you for the current case at hand, but all this sprawl can be very useful later on, in other cases.  In my experience, they do tend to be.
 

Offline mariush

  • Super Contributor
  • ***
  • Posts: 5343
  • Country: ro
  • .
Re: What actual data is used to return a favicon to the browser?
« Reply #53 on: July 06, 2022, 09:49:41 am »
If you have to get one reading per second (1 Hz) or a reading every few seconds... you may want to consider keeping connections alive and reuse them but you'd also have to change your "web server" code a bit.

See Connection: keep-alive  : https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Keep-Alive

Set a timeout of around 5 seconds, set a max requests of whatever you're comfortable with (let's say 50)  and don't close connection as soon as you're done

Then the browser may request the page and when you're done sending the page the browser may request the favicon on the same open connection... and when you refresh page once a second, your kept alive connection may be reused saving some latency.

You could also consider using Javascript on the page to retrieve new measurements once a second or as often as you want, instead of reloading the whole page... as that could be annoying (page flickering, user not able to copy/paste stuff as the page keeps reloading etc)
For example, once the page is loaded, the javascript on the page could request json encoded data which could be something like  {id:1 , value:12.4}    and next reading you'd have {id:2, value:12.8} and so on... you'd want some unique id or the measurement time to know if it's a new measurement and not the old one, and the javascript code could add the fresh reading to the screen and replace the old one or append the value to a list (maybe you want a history of the last N readings on the page)

you could extend the json api to give you more results or based on some parameter

ex  GET /api/temperature.json?from=20220706125015

and your code replies

{

  count : 2,
  results: [
    { date:"20220706125015", value: "100" },
    { date:"20220706125016", value: "110" },
  ]
  error: false
}

The javascript code requests all values from that date yyyymmddhhmmss because that's the last time it has a reading for, or the date sent through the initial html page, or simply doesn't send that date parameter and gets the latest 1..10-whatever readings.
In the example above, it gets two results and pushes them on the screen, then waits 1s or more and requests data again, with the data parameter updated.

Note there's even more complex stuff, it's now possible to "stream" something, so your server could keep connection alive and push data and Javascript on the page could open a connection and keep it alive for hours and read chunks of data and decode them and put stuff on screen.

See https://developer.mozilla.org/en-US/docs/Web/API/Streams_API#examples
« Last Edit: July 06, 2022, 09:52:00 am by mariush »
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 8349
  • Country: fi
    • My home page and email address
Re: What actual data is used to return a favicon to the browser?
« Reply #54 on: July 06, 2022, 11:04:13 pm »
To add to what mariush wrote, the WebSockets API docs at Mozilla shows how to use WebSockets (both server and client side), if you want to use WebSockets instead of plain HTTP.

The difference to plain HTTP is that with WebSockets, instead of doing a new request once every second, the server/device will just push new data once every second or so.  Here, too, you'll definitely want to use JSON for the data.  On the web page, each such push will generate a MessageEvent on the open WebSocket, and you can set an onmessage handler to handle each new message, for example to add the new data to the display.

Simply put, instead of polling the server, the client page opens the websocket connection, and then the device pushes back the data (say, once a second or so, or however often you like).

In particular, if you design your JSON data packets so that each WebSocket message has at most 125 bytes of data, each TCP packet you send will have less than 136 bytes of data.  This means that you can do with just one HTTP request-response in progress at any point in time (including WebSocket handshake), but both an active WebSocket connection and a HTTP request-response in progress at the same time.  Depending on the available resources, you could actually have more than one active WebSocket connection at a time, allowing more than one concurrent client, while only doing one HTTP request-response at a time.

(For a long time, I've been occasionally wondering how to more efficiently process HTTP request headers.  In theory, for this kind of situations (where only a few of the headers matter, and basically the values do not need to be stored, only flags as to which value was seen for which header), it is possible to do with just two TCP packet payload buffers (so a bit over 3k of buffers), using a stream-type parser.  It would be an interesting exercise to see how compact one could make this kind of HTTP+WebSocket or HTTPS+WebSocket server for embedded devices and MCUs.)
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 6032
  • Country: gb
  • Doing electronics since the 1960s...
Re: What actual data is used to return a favicon to the browser?
« Reply #55 on: July 07, 2022, 06:06:43 pm »
I am on the limit of my HTML expertise here but presumably it is ok for a server to just keep pushing data out to the client, at say 1 sec intervals, without the client requesting it? Isn't this what Ajax is?

Currently, for the refreshed status page, I am using

Code: [Select]
<meta http-equiv=refresh content=1>
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline mariush

  • Super Contributor
  • ***
  • Posts: 5343
  • Country: ro
  • .
Re: What actual data is used to return a favicon to the browser?
« Reply #56 on: July 07, 2022, 06:51:56 pm »
No, that code simply tells the browser to reload the page approximately 1 second after the meta tag is parsed / page is loaded / connection is closed.

AJAX is functionality introduced in browsers...
A is short for Asynchronous , J is for Javascript , X is for XML  but nowadays XML is less used, JSON is more common/ popular.

Basically you can use Javascript to request a document to be loaded in background (while the page is still being loaded on user's computer, or some time after the page is completely loaded - that's the asynchronous bit) and when the transfer is complete, the browser resumes running your javascript code and you can do something with that content (populate the page with the data, using Javascript)

See the examples in the page : https://en.wikipedia.org/wiki/Ajax_(programming)
 

Offline sokoloff

  • Super Contributor
  • ***
  • Posts: 1803
  • Country: us
Re: What actual data is used to return a favicon to the browser?
« Reply #57 on: July 07, 2022, 09:36:38 pm »
I am on the limit of my HTML expertise here but presumably it is ok for a server to just keep pushing data out to the client, at say 1 sec intervals, without the client requesting it? Isn't this what Ajax is?
That technique is called "long-polling" (if you want a term to Google) and is valid, especially if the number of clients will be fairly low. https://ably.com/blog/websockets-vs-long-polling for some discussion on the topic.

AJAX and long-polling are not the same. AJAX can be used to implement client-driven polling, but that's about the only overlap.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf

 

-->