I have an Azure Container App with one container having an NGinx reverse proxy, the other container has a NodeJS API serving JSON and pretty small images (~400kb) using express.static
from a mounted Azure Fileshare. Locally, this container setup works fine.
The JSON requests work fine (up to 200kb) but the images only partly download with a 200 ERR_CONTENT_LENGTH_MISMATCH
in the browser and NGinx reporting the following ...
2023/12/04 17:36:02 [error] 107#107: *126 upstream prematurely closed connection while
reading upstream,
client: 100.100.0.48,
server: ,
request: "GET /tools/evidence/api/content/d8e5a7fd-2330-42ca-949f-b4a5bc5b3dcc.png
HTTP/1.1",
upstream: "http://100.100.154.166:5000/content/d8e5a7fd-2330-42ca-949f-b4a5bc5b3dcc.png",
host: "s175d01-ca-evidence-www.bravesky-af1cf594.westeurope.azurecontainerapps.io:443"
The images partially load, and load to differing amounts (between 50% and 80%) so I don't think it is a configured limit. I have tried increasing the size of the Containers, and set timeouts on both NGinx and NodeJS Express as others have suggested but no luck.
For example ...
server.setTimeout(1000)
server.keepAliveTimeout = 65000;
I'm at a bit of a loss tbh. I'm starting to think it might be the mounted fileshare quitting or something although nothing appears in the NodeJS logs to suggest that.
My NGinx config is as follows ...
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=2g inactive=60m use_temp_path=off;
proxy_cache_valid 200 302 60m;
server {
listen 80 default_server;
# Some security headers
server_tokens off; # Turns of version reporting in error messages and headers
more_clear_headers "Server"; # Clears the Server header containing server info
more_clear_headers "X-Powered-By"; # Clears the X-Powered-By header containing server info
more_set_headers "Referrer-Policy: strict-origin-when-cross-origin"; # Make the default explicit
more_set_headers "X-Content-Type-Options: nosniff"; # Blocks requests from script tags if not Javascript and styles tags if not CSS
# Enable compression
gzip on;
gzip_min_length 20; # Small files offer little benefit from compression
gzip_disable "msie6";
gzip_vary on; # Lets the client know through the Vary header that different encodings will affect cache
gzip_proxied any; # Enable compression for all proxied requests
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml
application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-opentype
application/x-font-truetype application/x-font-ttf font/eot font/opentype font/otf image/svg+xml
image/vnd.microsoft.icon;
add_header X-Cache-Status $upstream_cache_status;
proxy_buffering on;
# A health check endpoint
location = /health {
return 204;
access_log "off"; # Don't log
}
# Needs to be / or /base/path/
location ${BASE_URL} {
auth_basic "Protected area";
auth_basic_user_file /etc/nginx/users.htpasswd;
index index.html;
alias /srv/www/;
# Needs to be / or /base/path/
try_files $uri $uri/ ${BASE_URL}index.html;
}
# Needs to be / or /base/path/
location ^~ ${BASE_URL}api/ {
auth_basic "Protected area";
auth_basic_user_file /etc/nginx/users.htpasswd;
proxy_pass ${PROXY_API_BASE_URL}/;
}
}
Relevant NodeJS
const main = async () => {
await repository.init();
const app = express();
// Support JSON encoded requests
app.use(express.json());
// GZip responses
app.use(compression());
// Enable Cross-site Origin Requests for all domains in development mode. In production we would expose
// the API and the web app on the same domain through the reverse-proxy.
if (config.NODE_ENV === "development") {
app.use(cors());
}
app.use("/content", express.static(config.IMG_LOCAL_PATH));
const resolvers = {
// ... resolver definitions
};
const apollo = new ApolloServer({
typeDefs,
resolvers,
});
// Must await the start of Apollo, before adding as middleware
await apollo.start();
app.use("/graphql", expressMiddleware(apollo));
if (config.LOG_REQUESTS) {
app.use((req, res, next) => {
const timestamp = new Date().toLocaleString();
logger.log(`${timestamp}|${req.method}|${req.url}`);
next();
});
}
const server = app.listen(config.API_PORT, () => {
logger.log(`NODE_ENV is: ${config.NODE_ENV}`);
logger.log(`API listening on port ${config.API_PORT}`);
});
server.setTimeout(1000)
server.keepAliveTimeout = 65000;
};
main();