loadImage() fails to load image served on reverse proxy (Safari/iOS)

  • p5.js version: 0.10.2
  • Web browser and version: Safari: Version 13.1 (15609.1.20.111.8)
  • Operating System: macOS 10.15.4
  • Steps to reproduce this:

I’ve built an application that creates simulations of chemical systems including atoms and molecules using a p5 sketch. When a new sketch is created, a Particle is created and its init function loads the relevant image asset. To handle the async behavior in the Particle class and other classes, I wrote a wrapper function that returns a promise:

`
loadParticleImage() {

    // Included for debug
    var getUrl = window.location;
    var baseUrl = getUrl.protocol + "//" + getUrl.host + "/" + getUrl.pathname.split('/')[1];

    return new Promise((resolve, reject) => {
        loadImage(this.imageUrl, (result) => {

            // Sets the image object property to the result for other methods to access
            this.imageObject = result;

            // Sets the dimensions of the 
            this.width = result.width;
            this.height = result.height;

            // Resolves the Promise with the result
            resolve(result);

        }, (reason) => {

            reject(new Error("Particle did not load for " + this.name + ". Provided with the path " + this.imageUrl + " and the current base is " + baseUrl + ". This yields a path of " + (baseUrl + "/" + this.imageUrl)));
            console.log(reason);
        });
    })
}

`

I have an Apache server hosting my site and the p5 application is served up through a Node/Express server. I set up a reverse proxy on Apache so that all requests to the p5 app at https://subdomain.myurl.com/ are proxied to https://localhost:8443/.

Here’s what I don’t understand:
My code works perfectly fine on Chrome & Firefox running in macOS. But, when I try to use Safari (macOS/iOS) or Chrome (iOS), I get an error that the Promise does not resolve. The path baseUrl + "/" + this.imageUrl returns an absolute image path that loads fine in Safari.

I printed p5 Error Event, but I can’t figure out where to go next:

bubbles: false
cancelBubble: false
cancelable: false
composed: false
currentTarget: null
defaultPrevented: false
eventPhase: 0
isTrusted: true
returnValue: true
srcElement: <img>
target: <img>
timeStamp: 5561
type: "error"
Event Prototype

All I can guess is that there’s some implicit CORS issue that Safari/iOS is blocking or that loadImage() does not handle specifically with these browsers.


Edit: More debugging info

When I manually run loadImage() in the console, it seems the image is being found. When I put in the wrong path intentionally, I get a 404:

Purposefully wrong URL

> loadImage('sims/img/svg/Water.svg', (result) => console.log(result.width), (error) => console.log(error))

{width: 1, height: 1, canvas: <canvas>, drawingContext: CanvasRenderingContext2D, _pixelsState: Object, …}
[Error] Failed to load resource: the server responded with a status of 404 (Not Found) (Water.svg, line 0)
[Log] Event {isTrusted: true, type: "error", target: <img>, currentTarget: <img>, eventPhase: 2, …}

Correct URL

> loadImage('img/svg/Water.svg', (result) => console.log(result.width), (error) => console.log(error))

{width: 1, height: 1, canvas: <canvas>, drawingContext: CanvasRenderingContext2D, _pixelsState: Object, …}
[Log] Event {isTrusted: true, type: "error", target: <img>, currentTarget: <img>, eventPhase: 2, …}

Hello @dfd0226,

I would love to help, but it’s a challenge when there’s no project to debug. Can you reproduce the error in a simple runnable example and share it with us?

If not: open up the JavaScript Console in Safari ( ⌥ ⌘ C) and share the output from there. The actual image request (and response) could also be helpful. (From the Network tab in the Web Inspector in Safari.)

Hi Sven,

My source code base is pretty large/obfuscated and accessed via an Apache reverse proxy, so I probably can’t easily duplicate it in a simple way.

When I look at the Network tab, it fails to load Water.svg (which it says is an HTML request? other SVGs load fine). The request and response headers for Water.svg appear empty and it says “Unknown error.”

When I serve up my code without being behind the reverse proxy, this doesn’t happen in Safari. This problem doesn’t happen on Chrome (proxied or not). If it were just a server config problem, I’d think it would affect all browsers.

Yeah, I will have a hard time helping out unless you stop obfuscating the code. :grin:

I did manage to find something out, though. This is the request and response for the Water.svg:

GET https://sims.connchem.org/sims/img/svg/Water.svg HTTP/1.1
Host: sims.connchem.org
Origin: https://sims.connchem.org
Connection: keep-alive
If-None-Match: W/"5e6-171c689d240"
Accept: image/png,image/svg+xml,image/*;q=0.8,video/*;q=0.8,*/*;q=0.5
If-Modified-Since: Wed, 29 Apr 2020 15:24:13 GMT
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15
Referer: https://sims.connchem.org/sims/
Accept-Language: en-us
Accept-Encoding: gzip, deflate, br
HTTP/1.1 302 Found
Date: Fri, 01 May 2020 06:50:07 GMT
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.3.17
X-Powered-By: Express
Location: /
Vary: Accept
Content-Type: text/plain; charset=utf-8
Content-Length: 23
Access-Control-Allow-Origin: *
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive

Found. Redirecting to /

Whats interesting is that the server responds with a redirect. And that probably because the request does not contain the cookie header. When manually entering the URL in the address bar of Safari, the request looks like this:

GET https://sims.connchem.org/sims/img/svg/Water.svg HTTP/1.1
Host: sims.connchem.org
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Cookie: connect.sid=s%3A2frOCv41gcVRf1J4t5LlTcWkdZTdc8NT.8pD5eEHo6JBCHcpgqOgszKraD7AakvPsMK7w2bIHlr4
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15
Accept-Language: en-us
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

Notice that the Cookie header field is present. So there’s your problem. Now you have to figure out why the cookie field is not present in the request initiated by your code. The obfuscation makes it to time-consuming for me to assist with that.

This is the first concrete direction I’ve gotten in a week, you are amazing. I’ve had issues because I can’t release the code (I put up a login page because people threatened last week to steal my work and sell it).

The session/cookie is being handled by Express middleware server side and not in my main codebase, so that seems like a possible culprit. (Adapted ideas from this login system https://codeshack.io/basic-login-system-nodejs-express-mysql/)

It’s just weird that the SVG embedded in the HTML DOM loads fine (logo top right), but images requested by loadImage() have this cookie header issue.

Apple and the WebKit team is known for having a big focus on privacy-related features. Their tweaking sometimes involves cookies, like the full third-party cookie blocking. I wouldn’t be surprised if a privacy related feature causes your issue. That’s just me speculating, however.

A query like safari not sending cookies in your favorite search engine should yield some interesting hits to follow up.

Thanks again to Sven for finding a critical missing step in my debug efforts: Safari was not appending cookies to the HTTP request for static assets.

Wanted to follow up for anyone else who encounters this problem. There are two ways to fix this:

  1. Option 1: Set any image/static assets to be served up by Express (or your middleware) as static files so that a valid session is not required.

  2. Option 2: Revise the source code of loadImage() method in p5.js to include the credentials: include property in the fetch request. This tells Safari to explicitly send the cookie with the session id to the server for authentication when requesting the image assets.

1 Like