From 2ec0e1542750b3ee9c7321b0e9142113e8ae28f3 Mon Sep 17 00:00:00 2001 From: Jindra Dolezy Date: Sun, 12 Apr 2015 01:13:47 +0200 Subject: [PATCH] Better handling of gzip compressed files --- Makefile | 11 +++++------ httpd/httpd.c | 46 --------------------------------------------- httpd/httpd.h | 3 --- httpd/httpdespfs.c | 47 ++++++++++++++++++++++++++++++++++++---------- 4 files changed, 42 insertions(+), 65 deletions(-) diff --git a/Makefile b/Makefile index 1b6fd96..d7a5f17 100644 --- a/Makefile +++ b/Makefile @@ -166,18 +166,17 @@ flash: $(TARGET_OUT) $(FW_BASE) $(Q) $(ESPTOOL) --port $(ESPPORT) --baud $(ESPBAUD) write_flash 0x00000 $(FW_BASE)/0x00000.bin 0x40000 $(FW_BASE)/0x40000.bin webpages.espfs: html/ html/wifi/ espfs/mkespfsimage/mkespfsimage -ifeq ($(GZIP_COMPRESSION),"yes") +ifeq ($(COMPRESS_W_YUI),"yes") $(Q) rm -rf html_compressed; $(Q) cp -r html html_compressed; -ifeq ($(COMPRESS_W_YUI),"yes") $(Q) echo "Compression assets with yui-compressor. This may take a while..." $(Q) for file in `find html_compressed -type f -name "*.js"`; do $(YUI-COMPRESSOR) --type js $$file -o $$file; done $(Q) for file in `find html_compressed -type f -name "*.css"`; do $(YUI-COMPRESSOR) --type css $$file -o $$file; done $(Q) awk "BEGIN {printf \"YUI compression ratio was: %.2f%%\\n\", (`du -b -s html_compressed/ | sed 's/\([0-9]*\).*/\1/'`/`du -b -s html/ | sed 's/\([0-9]*\).*/\1/'`)*100}" -endif - $(Q) cd html_compressed; find . -type f -regex ".*/.*\.\(html\|css\|js\)" -exec sh -c "gzip -n {}; mv {}.gz {}" \;; cd ..; + +# mkespfsimage will compress html, css and js files with gzip by default if enabled +# override with -g cmdline parameter $(Q) cd html_compressed; find | ../espfs/mkespfsimage/mkespfsimage > ../webpages.espfs; cd ..; - $(Q) awk "BEGIN {printf \"GZIP compression ratio was: %.2f%%\\n\", (`du -b -s html_compressed/ | sed 's/\([0-9]*\).*/\1/'`/`du -b -s html/ | sed 's/\([0-9]*\).*/\1/'`)*100}" else $(Q) cd html; find | ../espfs/mkespfsimage/mkespfsimage > ../webpages.espfs; cd .. endif @@ -196,7 +195,7 @@ clean: $(Q) make -C espfs/mkespfsimage/ clean $(Q) rm -rf $(FW_BASE) $(Q) rm -f webpages.espfs -ifeq ($(GZIP_COMPRESSION),"yes") +ifeq ($(COMPRESS_W_YUI),"yes") $(Q) rm -rf html_compressed endif diff --git a/httpd/httpd.c b/httpd/httpd.c index 36fcd8a..59c4d40 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -66,19 +66,6 @@ static const MimeMap mimeTypes[]={ {NULL, "text/html"}, //default value }; -// The static files with the following extensions from the HTML folder will be compressed and served with GZIP compression -// Add any other file types you want compressed here (and don't forget to modify the Makefile too) -#ifdef GZIP_COMPRESSION -static const char * gzippedFileTypes[] = { - "js", - "html", - "css", - NULL -}; - -static const char gzipNonSupportedMessage[] = "Your browser does not accept gzip-compressed data."; -#endif - //Returns a static char* to a mime type for a given url to a file. const char ICACHE_FLASH_ATTR *httpdGetMimetype(char *url) { int i=0; @@ -92,39 +79,6 @@ const char ICACHE_FLASH_ATTR *httpdGetMimetype(char *url) { return mimeTypes[i].mimetype; } - -#ifdef GZIP_COMPRESSION -//Sends Content-encoding header if the requested file was GZIP compressed -//If the client does not sent the Accept-encoding, send out a static html message. -const char* sendGZIPEncodingIfNeeded(HttpdConnData *connData) { - int i=0; - char acceptEncodingBuffer[64]; - //Go find the extension - char *ext=connData->url+(strlen(connData->url)-1); - while (ext!=connData->url && *ext!='.') ext--; - if (*ext=='.') ext++; - - //ToDo: os_strcmp is case sensitive; we may want to do case-intensive matching here... - while (gzippedFileTypes[i]!=NULL) { - if (os_strcmp(ext, gzippedFileTypes[i])==0) { - //when serving gzipped files check the browser's "Accept-Encoding" header - //if the client does not advertises that he accepts GZIP send a warning message (telnet users for e.g.) - httpdGetHeader(connData, "Accept-Encoding", acceptEncodingBuffer, 64); - if (os_strstr(acceptEncodingBuffer, "gzip") == NULL) { - //No Accept-Encoding: gzip header present - return gzipNonSupportedMessage; - } else { - httpdHeader(connData, "Content-Encoding", "gzip"); - return NULL; - } - } - i++; - } - return NULL; -} -#endif - - //Looks up the connData info for a specific esp connection static HttpdConnData ICACHE_FLASH_ATTR *httpdFindConnData(void *arg) { int i; diff --git a/httpd/httpd.h b/httpd/httpd.h index 8ef6e72..95611bd 100644 --- a/httpd/httpd.h +++ b/httpd/httpd.h @@ -56,9 +56,6 @@ int httpdUrlDecode(char *val, int valLen, char *ret, int retLen); int ICACHE_FLASH_ATTR httpdFindArg(char *line, char *arg, char *buff, int buffLen); void ICACHE_FLASH_ATTR httpdInit(HttpdBuiltInUrl *fixedUrls, int port); const char *httpdGetMimetype(char *url); -#ifdef GZIP_COMPRESSION -const char* sendGZIPEncodingIfNeeded(HttpdConnData *connData); -#endif void ICACHE_FLASH_ATTR httpdStartResponse(HttpdConnData *conn, int code); void ICACHE_FLASH_ATTR httpdHeader(HttpdConnData *conn, const char *field, const char *val); void ICACHE_FLASH_ATTR httpdEndHeaders(HttpdConnData *conn); diff --git a/httpd/httpdespfs.c b/httpd/httpdespfs.c index 2eeb90c..a182bb8 100644 --- a/httpd/httpdespfs.c +++ b/httpd/httpdespfs.c @@ -14,6 +14,11 @@ Connector to let httpd use the espfs filesystem to serve the files in it. #include #include "httpdespfs.h" #include "espfs.h" +#include "espfsformat.h" + +// The static files marked with FLAG_GZIP are compressed and will be served with GZIP compression. +// If the client does not advertise that he accepts GZIP send following warning message (telnet users for e.g.) +static const char *gzipNonSupportedMessage = "HTTP/1.0 501 Not implemented\r\nServer: esp8266-httpd/"HTTPDVER"\r\nContent-Type: text/plain\r\nContent-Length: 52\r\n\r\nYour browser does not accept gzip-compressed data.\r\n"; //This is a catch-all cgi function. It takes the url passed to it, looks up the corresponding @@ -23,9 +28,8 @@ int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) { EspFsFile *file=connData->cgiData; int len; char buff[1024]; -#ifdef GZIP_COMPRESSION - const char *gzipSendResult = NULL; -#endif + char acceptEncodingBuffer[64]; + int isGzip; if (connData->conn==NULL) { //Connection aborted. Clean up. @@ -39,17 +43,32 @@ int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) { if (file==NULL) { return HTTPD_CGI_NOTFOUND; } + + // The gzip checking code is intentionally without #ifdefs because checking + // for FLAG_GZIP (which indicates gzip compressed file) is very easy, doesn't + // mean additional overhead and is actually safer to be on at all times. + // If there are no gzipped files in the image, the code bellow will not cause any harm. + + // Check if requested file was GZIP compressed + isGzip = espFsFlags(file) & FLAG_GZIP; + if (isGzip) { + // Check the browser's "Accept-Encoding" header. If the client does not + // advertise that he accepts GZIP send a warning message (telnet users for e.g.) + httpdGetHeader(connData, "Accept-Encoding", acceptEncodingBuffer, 64); + if (os_strstr(acceptEncodingBuffer, "gzip") == NULL) { + //No Accept-Encoding: gzip header present + httpdSend(connData, gzipNonSupportedMessage, -1); + espFsClose(file); + return HTTPD_CGI_DONE; + } + } + connData->cgiData=file; httpdStartResponse(connData, 200); httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url)); -#ifdef GZIP_COMPRESSION - gzipSendResult = sendGZIPEncodingIfNeeded(connData); - if (gzipSendResult != NULL) { - httpdEndHeaders(connData); - httpdSend(connData, gzipSendResult, os_strlen(gzipSendResult)); - return HTTPD_CGI_DONE; + if (isGzip) { + httpdHeader(connData, "Content-Encoding", "gzip"); } -#endif httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate"); httpdEndHeaders(connData); return HTTPD_CGI_MORE; @@ -101,6 +120,14 @@ int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) { tpd->tplArg=NULL; tpd->tokenPos=-1; if (tpd->file==NULL) { + espFsClose(tpd->file); + os_free(tpd); + return HTTPD_CGI_NOTFOUND; + } + if (espFsFlags(tpd->file) & FLAG_GZIP) { + os_printf("cgiEspFsTemplate: Trying to use gzip-compressed file %s as template!\n", connData->url); + espFsClose(tpd->file); + os_free(tpd); return HTTPD_CGI_NOTFOUND; } connData->cgiData=tpd;