4

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:

  1. I download and install the zip package
  2. I delete the zip package
  3. 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:

  1. Replace the zip file on the server with one of the other zip files
  2. It now downloads just fine
  3. I delete the package from app
  4. I replace the zip file on the server with the original one
  5. 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?

Kevin Christopher Henry
  • 46,175
  • 7
  • 116
  • 102
kojow7
  • 10,308
  • 17
  • 80
  • 135
  • Try removing the `X-Sendfile` header and using `readfile($path);` instead (after the other headers). – Olivier Sep 20 '20 at 08:33
  • And `'Accept': 'application/json'` makes no sense here because a zip file is not JSON. You should remove it. – Olivier Sep 20 '20 at 08:35
  • 2
    have you debugged your network request? https://stackoverflow.com/questions/33997443/how-can-i-view-network-requests-for-debugging-in-react-native try to replicate the requests with curl or some tool to make sure is not the app (IMHO problem is not on the app side) look out for the headers related to error `If-Unmodified-Since` `If-None-Match` and share your findings – diedu Sep 20 '20 at 23:24
  • @Olivier The reason I am wanting to use X-Sendfile is that it is much more efficient than the `readfile()` function. – kojow7 Sep 22 '20 at 20:55

2 Answers2

3

This is what seems to be happening:

  1. The use of X-Sendfile is triggering the generation of an ETag, even though POST responses are normally not cached.

  2. 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 a POST, it uses the If-Match header—used for conditional updates of a resource—which is not at all what you are trying to do.

  3. The server sees the If-Match header and tries to do a conditional update. That is, it will only execute the POST if the old ETag matches the current one. When that is not the case it returns a 412 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.

Kevin Christopher Henry
  • 46,175
  • 7
  • 116
  • 102
  • Okay, will look into that. The only thing is that I have done nothing with "If-Match" or ETags on either the server side or client side. I had not even known such a thing existed before as it only seems to happen with this one file. Is something setting the If-Match by default such as RNFetchBlob on the client or X-Sendfile on the server? And why only for this one file? Is there a proper way to get more detailed header info? – kojow7 Sep 22 '20 at 15:17
  • I did see your suggestion and thing it sounds like a good idea, but not sure how I would implement it. The Zip file needs to be accessed only with the correct password, so if I can redirect to a URL using GET, then I'm not sure how that new URL is protected with a password. Secondly, I'm not sure I really need caching or conditional validation in the first place. – kojow7 Sep 22 '20 at 17:45
  • @kojow7 My first comment (using `readfile()`) addesses all these points: no `X-Sendfile` header (so no `ETag` sent to the client), and you can still perform authorization. – Olivier Sep 22 '20 at 18:10
  • @KevinChristopherHenry Tried setting the Cache-Control: no-store header and it is still not working. – kojow7 Sep 23 '20 at 19:56
  • Hey, I wiped out the client and tried again and it is working perfectly now. Thank you for your help! – kojow7 Sep 23 '20 at 20:29
  • @kojow7: Happy to help. I rewrote the answer based on your confirmation. – Kevin Christopher Henry Sep 25 '20 at 11:49
  • Great, thank you. Also, do you have any inkling why it was only doing it for one specific zip file, but not any of the others? Or is it just one of those mysteries we'll have to let remain unsolved? – kojow7 Sep 25 '20 at 15:45
  • 1
    @kojow7: I'm really not sure. Apache computes `ETags` from [file system attributes](http://httpd.apache.org/docs/2.2/mod/core.html#fileetag) so it may be something unusual at the filesystem level. – Kevin Christopher Henry Sep 25 '20 at 17:04
  • @kojow7 To solve the mystery, seeing the headers would help a lot. Is that specific zip file smaller than the others? – Olivier Sep 26 '20 at 09:28
  • @Olivier Yes, it actually is the smallest one. – kojow7 Sep 26 '20 at 17:59
  • 1
    @kojow7 Then my guess is that the cache capacity is limited; that specific file is small enough to be cached, while the others are too big and are not cached. – Olivier Sep 27 '20 at 06:45
1

you could try to modify the content-type to:

fetch("https://example.com/apptools/getZipPack.php", {
      method: "POST",
      headers:{  
        'Content-Type': 'multipart/form-data',
      },
    })
Ozone
  • 1,337
  • 9
  • 18