I have created a React Native App that allows me to download Zip packages to my app. In most cases it has worked, I can download the zip package, delete a zip package, and re-download it again. However, with one of my zip files I am encountering the following issue:
- I download and install the zip package
- I delete the zip package
- I try to download it again, but this time I get a 412 error.
No matter how many times I try to download it again it will not let me. To overcome this issue I then do the following:
- Replace the zip file on the server with one of the other zip files
- It now downloads just fine
- I delete the package from app
- I replace the zip file on the server with the original one
- I can now download the zip package just fine
This works one time, but if I delete it and try to install it again, then again it will not let me.
I have also tried the other way around in which case I replace one of the other zip files with the problem zip file. Again, it lets me download it once, but then if I want to download it subsequent times it does not let me.
Why am I having this problem with this zip file? The zip files were made all the same way and there seems to be no issues with it the first time it is downloaded.
At first I was thinking it was a caching issue, but then why would the issue only be with this one specific file?
The zip file was programmably created through PHP and served with Apache. However, I also manually created it from the command prompt and the issue still exists.
My code:
let response = await RNFetchBlob.config({ path: temp_file, }) .fetch( "POST", "https://example.com/apptools/getZipPack.php", { "Accept": "application/json", "Content-Type": "application/json", }, JSON.stringify({ passcode: passcode, group_id: group_id, file: group.zp, }) ) .progress((received, total) => { console.log("progress", received / total); this.setState({ progress_num: (received / total) * 100, }); });
The response I receive back:
{“array”: [Function anonymous], “base64”: [Function anonymous], “blob”: [Function anonymous], “data”: “/Users/MyUserName/Library/Developer/CoreSimulator/Devices/940349C9-94F7-4EC7-95AC-31034876935D/data/Containers/Data/Application/C79E45D8-4D56-4BF9-9C2B-8846C58DE4F9/Documents/temp/45.zip”, “flush”: [Function anonymous], “info”: [Function anonymous], “json”: [Function anonymous], “path”: [Function anonymous], “readFile”: [Function anonymous], “readStream”: [Function anonymous], “respInfo”: {“headers”: {“Connection”: “Keep-Alive”, “Content-Disposition”: “attachment; filename=”45_1.0.8_105.zip””, “Content-Length”: “0”, “Content-Type”: “application/octet-stream”, “Date”: “Thu, 17 Sep 2020 19:28:09 GMT”, “Etag”: “”75cf8-5af85fff34576″”, “Keep-Alive”: “timeout=3, max=100”, “Last-Modified”: “Thu, 17 Sep 2020 17:49:03 GMT”, “Server”: “Apache/2.4.25 (Debian)”}, “redirects”: [“https://example.com/apptools/getZipPack.php”], “respType”: “blob”, “rnfbEncode”: “path”, “state”: “2”, “status”: 412, “taskId”: “k4zitpcob29hhrzk245n5n”, “timeout”: false}, “session”: [Function anonymous], “taskId”: “k4zitpcob29hhrzk245n5n”, “text”: [Function anonymous], “type”: “path”}
Notice that the Content Length is 0 and the Status is 412.
A zip file is placed in the directory that I created for it, but it has a size of 0 bytes.
The pertinent part in the backend script (PHP) is :
header("X-Sendfile: $full_path"); header("Content-type: application/octet-stream"); header('Content-Disposition: attachment; filename="' . basename($file) . '"');
The PHP script can indeed find the file in the filesystem. However, I am not sure whether it is the server or the app that seems to be at fault. My guess is that it is with the app. My first assumption was that it had something to do with caching, but then why is it only affecting this one file and not any other files?
Why am I experiencing this problem and how can I fix it?
Advertisement
Answer
This is what seems to be happening:
The use of
X-Sendfile
is triggering the generation of anETag
, even thoughPOST
responses are normally not cached.The React Native client caches the response, probably because of the presence of the
ETag
. When it hits that resource again after the cache entry is stale, it sends a conditional request. Because this is aPOST
, it uses theIf-Match
header—used for conditional updates of a resource—which is not at all what you are trying to do.The server sees the
If-Match
header and tries to do a conditional update. That is, it will only execute thePOST
if the oldETag
matches the current one. When that is not the case it returns a412 Precondition Failed
response.
Since you said you don’t want to use caching here, the simplest solution is probably to add Cache-Control: no-store
to your response. That might keep Apache from generating the ETag
entirely, but in any case it will definitely keep the client from storing the response, ensuring that it won’t be able to send any conditional requests.