Skip to content

The request in Parse.Object.saveAll() does not have a reverse proxy #1945

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
4 tasks done
roger28200901 opened this issue Jun 14, 2023 · 21 comments · Fixed by #2608
Closed
4 tasks done

The request in Parse.Object.saveAll() does not have a reverse proxy #1945

roger28200901 opened this issue Jun 14, 2023 · 21 comments · Fixed by #2608

Comments

@roger28200901
Copy link

roger28200901 commented Jun 14, 2023

New Issue Checklist

Issue Description

I was wondering about the practical method behind Parse.Object.saveAll, If my Parse Server I want to redirect through Proxy
ex: 192.168.1.1/api -> 192.168.1.1:4000/parse then, here is the problem.

on the parse/lib/node/ParseObject.js line:2388

return RESTController.request('POST', 'batch', {
              requests: batch.map(obj => {
                const params = obj._getSaveParams();
                params.path = getServerUrlPath() + params.path;
                return params;
              })
            }, options);

And here is the problem, After the above code, my input data format will be like this:

{
  "requests": [
    {
      "method": "PUT",
      "path": "/api/classes/{{className}}/{{ objectId }}",
      "body": {
         {{ Here is the object body}}
       }
    }
  ]
}

and then, as you can see the RESTController will send a axios POST for url -> 192.168.1.1/api/batch and then proxy server (nginx), will let the request send to the 192.168.1.1:4000/parse/batch, but in my requests body, the path only show it to /api/classes/{{className}}/{{objectId}}
, it wont through the nginx setting to let the redirect change to /parse/classes/{{className}}/{{objectId}}

Steps to reproduce

//config.js
const parseUrl = process.env.PARSE_URL || 'http://192.168.1.1/api'; // the nginx will let the 192.168.1.1/api turn into 192.168.1.1:4000/parse
const appId = 'test';
const masterKey = 'test-key';
const initParse = () => {
  Parse.initialize(appId, null, masterKey);
  Parse.serverURL = parseUrl;
  Parse.User.enableUnsafeCurrentUser();
};
const query = new Parse.Query(Tests);
const tests = await query.findAll()
tests.forEach((test) => {
 test.set('name', 'something');
})
await Parse.Object.saveAll(tests); // And then show the error code 206

Actual Outcome

format : Parse Error: cannot route batch path: /api/classes/{{className}}/{{objectId}} { code: 111 }

real outcome: ParseError: cannot route batch path /api/classes/tests/6ab89f70-addf-11ed-b17e-f98dadf7fe5c at C:\Users.......\node_modules\parse\lib\node\ParseObject.js:3047:34 at processTicksAndRejections (node:internal/process/task_queues:96:5) { code: 111 },
.
.
.

Expected Outcome

Expected it can saveAll and the response won't fail

Environment

Nginx

location /api/ {
    #proxy_redirect off;
    #proxy_set_header Host $host;
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods 'GET,POST,OPTIONS';
    add_header Access-Control-Allow-Headers 'Content-Type, X-DP-Token, X-DP-Application-Id';
    #proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://192.168.1.1:4000/parse/;
    client_max_body_size 16M;
  }

Server

  • Parse Server version: 6.2.0
  • Operating system: linux

Database

  • MongoDB

Client

  • Parse JS SDK version: 4.1.0
@parse-github-assistant
Copy link

parse-github-assistant bot commented Jun 14, 2023

Thanks for opening this issue!

  • 🚀 You can help us to fix this issue faster by opening a pull request with a failing test. See our Contribution Guide for how to make a pull request, or read our New Contributor's Guide if this is your first time contributing.

@mtrezza
Copy link
Member

mtrezza commented Jun 14, 2023

Could you please complete the issue template (I've re-added the headlines) and ensure that you follow the checklist at the top?

@roger28200901
Copy link
Author

Could you please complete the issue template (I've re-added the headlines) and ensure that you follow the checklist at the top?

I already fix it, and very sorry about didn't follow the issue template, cause this is the first time I try to send the issue.

@mtrezza
Copy link
Member

mtrezza commented Jun 14, 2023

No worries, thanks for reporting the issue. The template just helps others to easier understand the issue.

In the steps to reproduce you are first querying tests and then saving all users. How are tests and users related?

@roger28200901
Copy link
Author

sorry, it was tests not users. I already edited it. and also add setting code about the nginx setting. and you can check it.

@mtrezza
Copy link
Member

mtrezza commented Jun 15, 2023

Could you please correct the code and make sure it's complete?

@roger28200901
Copy link
Author

Could you please correct the code and make sure it's complete?

I corrected it. Here are some additions, where my parse server is set on port :4000/parse on docker, so you can see above that I use an nginx as a reverse proxy to /api -> :4000/parse

@mtrezza
Copy link
Member

mtrezza commented Jun 15, 2023

Could you post the line from ParseObject.js:3047:34?

@roger28200901
Copy link
Author

 (0, _promiseUtils.when)(batchReady).then(() => {
            // Kick off the batch request
            return RESTController.request('POST', 'batch', {
              requests: batch.map(obj => {
                const params = obj._getSaveParams();
                params.path = getServerUrlPath() + params.path;
                return params;
              })
            }, options);
          }).then(batchReturned.resolve, error => {
            batchReturned.reject(new _ParseError.default(_ParseError.default.INCORRECT_TYPE, error.message));
          });

The error show up on the batchReturned ... this line
and then I see the function getServerUrlPath() how it works,

function getServerUrlPath() {
  let serverUrl = _CoreManager.default.get('SERVER_URL');
  if (serverUrl[serverUrl.length - 1] !== '/') {
    serverUrl += '/';
  }
  const url = serverUrl.replace(/https?:\/\//, '');
  return url.substr(url.indexOf('/'));
}

so as you can see, if my parseUrl is 'http://192.168.1.1/api' and I use a reverse proxy to make this url to redirect on the Parse Server (this is setup on the docker url: '192.168.1.1:4000/parse'), and I can see the request is sending to the url 'http://192.168.1.1/api' and then turn into the 'https://192.168.1.1:4000/parse', but the getServerUrlPath() didn't know I was trying to use reverse proxy skill, so params.path = getServerUrlPath() + params.path; it will show up like /api/classes/{className}/{objectId} and not like this /parse/classes/{className}/{objectId},

for example:

{
        "requests": [
          {
            "method": "POST",
            "path": "/api/classes/GameScore",
            "body": {
              "score": 1337,
              "playerName": "Sean Plott"
            }
          },
          {
            "method": "POST",
            "path": "/api/classes/GameScore",
            "body": {
              "score": 1338,
              "playerName": "ZeroCool"
            }
          }
        ]
      }' 
  request url : https://192.168.1.1/api/ -> and then proxy will send into https://192.168.1.1:4000/parse
  request method: post
  but the every request object's attribute 'path' won't change to /parse/....

Sorry for my english is not very good so please forgive me :( if i didn't explain clearly

@mtrezza
Copy link
Member

mtrezza commented Jun 16, 2023

Thanks for providing more details, let's see if someone in the community has an idea...

@dplewis
Copy link
Member

dplewis commented May 6, 2025

@mtrezza With #2503 being merged Fetch support automatic redirection following which should solve this issue. Alternatively could set it to redirection following to manual and catch 301, 302 redirect status codes.

@mtrezza
Copy link
Member

mtrezza commented May 6, 2025

Are you suggesting we can close this issue via #2503?

@dplewis
Copy link
Member

dplewis commented May 6, 2025

I think a test case should be written first before we close.

@mtrezza
Copy link
Member

mtrezza commented May 6, 2025

Sounds good, @roger28200901 would you want to write a test to verify that the issue has been fixed?

@mtrezza
Copy link
Member

mtrezza commented May 6, 2025

@coderabbitai analyze issue

Copy link

coderabbitai bot commented May 6, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check PR #2503 details
gh pr view 2503 -R parse-community/Parse-SDK-JS

# Look at the current implementation of getServerUrlPath and related code
rg -A 10 "function getServerUrlPath" --no-ignore

# Check if the issue has been fixed in recent versions
rg -A 10 "batch.map\(obj => {" --no-ignore

# Check if any tests have been added for this scenario
rg "test.*batch.*proxy" --no-ignore
rg "test.*reverse.*proxy" --no-ignore

Length of output: 2159


🏁 Script executed:

#!/bin/bash
# Check batch request implementation in ParseObject.ts
rg -A 10 'batch.map' --no-ignore

# Look at HTTPRequest implementation
find . -type f -name "*Controller*" -o -name "*HTTP*" | xargs rg -l "request|fetch"

# Check the implementation that handles redirects
rg -A 10 "redirect|follow" --no-ignore

# Check the PR #2503 specific changes related to HTTP requests
gh pr view 2503 -R parse-community/Parse-SDK-JS --json files | jq '.files[] | select(.path | contains("HTTP") or contains("Controller"))' | jq -r '.path'

Length of output: 20823


Let me examine the RESTController.ts implementation to understand how HTTP requests and redirects are handled.


🏁 Script executed:

#!/bin/bash
# Get the current implementation of RESTController.ts
cat src/RESTController.ts

# Check the PR #2503 changes specifically for RESTController.ts
gh pr view 2503 -R parse-community/Parse-SDK-JS --patch -- src/RESTController.ts

# Check if there are any tests related to proxies or redirects
rg -l "test.*proxy|redirect" --no-ignore

Length of output: 11391


🏁 Script executed:

#!/bin/bash
# Get the specific details of PR #2503 to understand the changes
gh pr view 2503 -R parse-community/Parse-SDK-JS --json body,mergedAt,files

# Check for any settings related to redirect following in the codebase
rg "redirect|follow" --no-ignore src/RESTController.ts

# Check package.json to see when this version was released
cat package.json | grep "version"

# Look for any changes in the XMLHttpRequest handling to fetch API
rg -l "fetch|XMLHttpRequest" --no-ignore src/

Length of output: 3484


🏁 Script executed:

#!/bin/bash
# Look for more information about PR #2503 changes to RESTController.ts
gh pr view 2503 -R parse-community/Parse-SDK-JS --json files | jq '.files[] | select(.path == "src/RESTController.ts")' | jq -r ".additions, .deletions"

# Check if there are any tests specifically for batch requests and redirection
rg -A 5 "test.*batch.*redirect|test.*redirect.*batch" src/__tests__/
rg -A 5 "describe.*batch" src/__tests__/RESTController-test.js

# Look for recent changes to batch processing in ParseObject.ts
rg -A 10 "batch.map\(" --no-ignore

Length of output: 1655


@mtrezza I've analyzed issue #1945, which involves batch requests failing when using Parse SDK with a reverse proxy setup.

Issue Understanding

The problem occurs in the following configuration:

  • Parse Server running at 192.168.1.1:4000/parse
  • Nginx reverse proxy routing http://192.168.1.1/api to the Parse Server
  • SDK configured with SERVER_URL set to http://192.168.1.1/api

In batch requests, the SDK's getServerUrlPath() function extracts the path from the SERVER_URL and adds it to each request path:

// In ParseObject.ts
const requests = batch.map(obj => {
  const params = obj._getSaveParams();
  params.path = getServerUrlPath() + params.path;
  return params;
});

This results in paths like /api/classes/... in batch requests, but the Parse Server expects /parse/classes/....

Solution in PR #2503

PR #2503, which was merged on April 23, 2025, replaced XMLHttpRequest with the Fetch API. This appears to fix the issue because:

  1. Fetch API automatically follows redirects by default
  2. When the reverse proxy redirects from /api/... to /parse/..., Fetch follows this redirection transparently
  3. The server receives the correct paths without requiring changes to the path construction logic

The fix is already available in version 6.2.0-alpha.3 and later.

Recommendation

As dplewis suggested, a test case would be valuable to verify this fix properly handles reverse proxy redirections with batch requests. This would ensure the solution is robust and works as expected.

For users on older SDK versions, possible workarounds include:

  1. Configure the reverse proxy to pass through paths without modification
  2. Set up the reverse proxy to rewrite paths correctly without redirects

Would you like me to help draft a test case to verify the fix?

@mtrezza
Copy link
Member

mtrezza commented May 6, 2025

@coderabbitai write a test case for this

Copy link

coderabbitai bot commented May 6, 2025

Tip

For best results, initiate chat on the files or code changes.

A summary of the edits CodeRabbit can apply:

We will add a new test in src/tests/RESTController-test.js to verify that RESTController.request follows HTTP redirects for batch requests when using a custom SERVER_URL (e.g., a reverse proxy).

A plan of each step that CodeRabbit will take in order to execute the edits for each file (with inline rough draft snippets):

➕ src/__tests__/RESTController-test.js

Within the existing describe('RESTController', ...) block, just before the closing });, insert a new it test case as follows:

  it('follows HTTP redirects for batch requests when using a custom SERVER_URL', async () => {
    // Configure a reverse-proxy style SERVER_URL
    CoreManager.set('SERVER_URL', 'http://test.host/api');

    // Prepare a minimal batch payload
    const batchData = {
      requests: [{
        method: 'POST',
        path: '/classes/TestObject',
        body: { foo: 'bar' }
      }]
    };

    // First response: 301 redirect to /parse/batch; second: successful response
    mockFetch(
      [
        { status: 301, response: {} },
        { status: 200, response: { success: true } }
      ],
      { location: 'http://test.host/parse/batch' }
    );

    // Issue the batch request
    const result = await RESTController.request('POST', 'batch', batchData);

    // We expect two fetch calls: one to the original URL, then one to the Location header
    expect(fetch.mock.calls.length).toBe(2);
    expect(fetch.mock.calls[0][0]).toEqual('http://test.host/api/batch');
    expect(fetch.mock.calls[1][0]).toEqual('http://test.host/parse/batch');

    // The final result should be the JSON from the second (successful) response
    expect(result).toEqual({ success: true });

    // Clean up the custom SERVER_URL
    CoreManager.set('SERVER_URL', undefined);
  });

This test verifies that when the Parse SDK encounters a 3XX redirect for a batch endpoint under a reverse-proxy‐style SERVER_URL, it reads the Location header and reissues the request to the correct path.


Save the file and run the Jest test suite to ensure the new test passes and confirms correct redirect-following behavior.

A summary of the context that CodeRabbit is considering across the codebase:

We need to understand how mockFetch simulates sequential fetch responses and how to inject custom response headers (like Location) to emulate a redirect.

  • Executed edits - (🔄 Check again to try again)

@dplewis
Copy link
Member

dplewis commented May 8, 2025

I opened a PR for this #2608

@roger28200901
Copy link
Author

@mtrezza lol, it’s been more than a year. I switched the Parse JS architecture to Golang Mongoose about half a year ago. But I’m still happy to see this issue resolved.

Copy link

coderabbitai bot commented May 9, 2025

Creating a PR to execute the edits....

The changes have been created in this pull request: View PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants