feat: Remove deprecation DEPPS4: Remove convenience method for http request Parse.Cloud.httpRequest (#8287)
BREAKING CHANGE: The convenience method for HTTP requests `Parse.Cloud.httpRequest` is removed; use your preferred 3rd party library for making HTTP requests
This commit is contained in:
@@ -7,7 +7,7 @@ The following is a list of deprecations, according to the [Deprecation Policy](h
|
||||
| DEPPS1 | Native MongoDB syntax in aggregation pipeline | [#7338](https://github.com/parse-community/parse-server/issues/7338) | 5.0.0 (2022) | 6.0.0 (2023) | deprecated | - |
|
||||
| DEPPS2 | Config option `directAccess` defaults to `true` | [#6636](https://github.com/parse-community/parse-server/pull/6636) | 5.0.0 (2022) | 6.0.0 (2023) | deprecated | - |
|
||||
| DEPPS3 | Config option `enforcePrivateUsers` defaults to `true` | [#7319](https://github.com/parse-community/parse-server/pull/7319) | 5.0.0 (2022) | 6.0.0 (2023) | deprecated | - |
|
||||
| DEPPS4 | Remove convenience method for http request `Parse.Cloud.httpRequest` | [#7589](https://github.com/parse-community/parse-server/pull/7589) | 5.0.0 (2022) | 6.0.0 (2023) | deprecated | - |
|
||||
| DEPPS4 | Remove convenience method for http request `Parse.Cloud.httpRequest` | [#7589](https://github.com/parse-community/parse-server/pull/7589) | 5.0.0 (2022) | 6.0.0 (2023) | removed | - |
|
||||
| DEPPS5 | Config option `allowClientClassCreation` defaults to `false` | [#7925](https://github.com/parse-community/parse-server/pull/7925) | 5.3.0 (2022) | 7.0.0 (2024) | deprecated | - |
|
||||
| DEPPS6 | Auth providers disabled by default | [#7953](https://github.com/parse-community/parse-server/pull/7953) | 5.3.0 (2022) | 7.0.0 (2024) | deprecated | - |
|
||||
| DEPPS7 | Remove file trigger syntax `Parse.Cloud.beforeSaveFile((request) => {})` | [#7966](https://github.com/parse-community/parse-server/pull/7966) | 5.3.0 (2022) | 7.0.0 (2024) | deprecated | - |
|
||||
|
||||
@@ -1686,25 +1686,6 @@ describe('Cloud Code', () => {
|
||||
obj.save().then(done, done.fail);
|
||||
});
|
||||
|
||||
it('can deprecate Parse.Cloud.httpRequest', async () => {
|
||||
const logger = require('../lib/logger').logger;
|
||||
spyOn(logger, 'warn').and.callFake(() => {});
|
||||
Parse.Cloud.define('hello', () => {
|
||||
return 'Hello world!';
|
||||
});
|
||||
await Parse.Cloud.httpRequest({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:8378/1/functions/hello',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': Parse.applicationId,
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
},
|
||||
});
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
'DeprecationWarning: Parse.Cloud.httpRequest is deprecated and will be removed in a future version. Use a http request library instead.'
|
||||
);
|
||||
});
|
||||
|
||||
describe('cloud jobs', () => {
|
||||
it('should define a job', done => {
|
||||
expect(() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const httpRequest = require('../lib/cloud-code/httpRequest'),
|
||||
HTTPResponse = require('../lib/cloud-code/HTTPResponse').default,
|
||||
const httpRequest = require('../lib/request'),
|
||||
HTTPResponse = require('../lib/request').HTTPResponse,
|
||||
bodyParser = require('body-parser'),
|
||||
express = require('express');
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ describe('Parse.GeoPoint testing', () => {
|
||||
obj.set('location', point);
|
||||
obj.set('name', 'Zhoul');
|
||||
await obj.save();
|
||||
Parse.Cloud.httpRequest({
|
||||
request({
|
||||
url: 'http://localhost:8378/1/classes/TestObject/' + obj.id,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
/**
|
||||
* @typedef Parse.Cloud.HTTPResponse
|
||||
* @property {Buffer} buffer The raw byte representation of the response body. Use this to receive binary data. See Buffer for more details.
|
||||
* @property {Object} cookies The cookies sent by the server. The keys in this object are the names of the cookies. The values are Parse.Cloud.Cookie objects.
|
||||
* @property {Object} data The parsed response body as a JavaScript object. This is only available when the response Content-Type is application/x-www-form-urlencoded or application/json.
|
||||
* @property {Object} headers The headers sent by the server. The keys in this object are the names of the headers. We do not support multiple response headers with the same name. In the common case of Set-Cookie headers, please use the cookies field instead.
|
||||
* @property {Number} status The status code.
|
||||
* @property {String} text The raw text representation of the response body.
|
||||
*/
|
||||
export default class HTTPResponse {
|
||||
constructor(response, body) {
|
||||
let _text, _data;
|
||||
this.status = response.statusCode;
|
||||
this.headers = response.headers || {};
|
||||
this.cookies = this.headers['set-cookie'];
|
||||
|
||||
if (typeof body == 'string') {
|
||||
_text = body;
|
||||
} else if (Buffer.isBuffer(body)) {
|
||||
this.buffer = body;
|
||||
} else if (typeof body == 'object') {
|
||||
_data = body;
|
||||
}
|
||||
|
||||
const getText = () => {
|
||||
if (!_text && this.buffer) {
|
||||
_text = this.buffer.toString('utf-8');
|
||||
} else if (!_text && _data) {
|
||||
_text = JSON.stringify(_data);
|
||||
}
|
||||
return _text;
|
||||
};
|
||||
|
||||
const getData = () => {
|
||||
if (!_data) {
|
||||
try {
|
||||
_data = JSON.parse(getText());
|
||||
} catch (e) {
|
||||
/* */
|
||||
}
|
||||
}
|
||||
return _data;
|
||||
};
|
||||
|
||||
Object.defineProperty(this, 'body', {
|
||||
get: () => {
|
||||
return body;
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'text', {
|
||||
enumerable: true,
|
||||
get: getText,
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'data', {
|
||||
enumerable: true,
|
||||
get: getData,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -713,15 +713,6 @@ ParseCloud.useMasterKey = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const request = require('./httpRequest');
|
||||
ParseCloud.httpRequest = opts => {
|
||||
Deprecator.logRuntimeDeprecation({
|
||||
usage: 'Parse.Cloud.httpRequest',
|
||||
solution: 'Use a http request library instead.',
|
||||
});
|
||||
return request(opts);
|
||||
};
|
||||
|
||||
module.exports = ParseCloud;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
import HTTPResponse from './HTTPResponse';
|
||||
import querystring from 'querystring';
|
||||
import log from '../logger';
|
||||
import { http, https } from 'follow-redirects';
|
||||
import { parse } from 'url';
|
||||
|
||||
const clients = {
|
||||
'http:': http,
|
||||
'https:': https,
|
||||
};
|
||||
|
||||
function makeCallback(resolve, reject) {
|
||||
return function (response) {
|
||||
const chunks = [];
|
||||
response.on('data', chunk => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
response.on('end', () => {
|
||||
const body = Buffer.concat(chunks);
|
||||
const httpResponse = new HTTPResponse(response, body);
|
||||
|
||||
// Consider <200 && >= 400 as errors
|
||||
if (httpResponse.status < 200 || httpResponse.status >= 400) {
|
||||
return reject(httpResponse);
|
||||
} else {
|
||||
return resolve(httpResponse);
|
||||
}
|
||||
});
|
||||
response.on('error', reject);
|
||||
};
|
||||
}
|
||||
|
||||
const encodeBody = function ({ body, headers = {} }) {
|
||||
if (typeof body !== 'object') {
|
||||
return { body, headers };
|
||||
}
|
||||
var contentTypeKeys = Object.keys(headers).filter(key => {
|
||||
return key.match(/content-type/i) != null;
|
||||
});
|
||||
|
||||
if (contentTypeKeys.length == 0) {
|
||||
// no content type
|
||||
// As per https://parse.com/docs/cloudcode/guide#cloud-code-advanced-sending-a-post-request the default encoding is supposedly x-www-form-urlencoded
|
||||
|
||||
body = querystring.stringify(body);
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
} else {
|
||||
/* istanbul ignore next */
|
||||
if (contentTypeKeys.length > 1) {
|
||||
log.error('Parse.Cloud.httpRequest', 'multiple content-type headers are set.');
|
||||
}
|
||||
// There maybe many, we'll just take the 1st one
|
||||
var contentType = contentTypeKeys[0];
|
||||
if (headers[contentType].match(/application\/json/i)) {
|
||||
body = JSON.stringify(body);
|
||||
} else if (headers[contentType].match(/application\/x-www-form-urlencoded/i)) {
|
||||
body = querystring.stringify(body);
|
||||
}
|
||||
}
|
||||
return { body, headers };
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes an HTTP Request.
|
||||
*
|
||||
* **Available in Cloud Code only.**
|
||||
*
|
||||
* By default, Parse.Cloud.httpRequest does not follow redirects caused by HTTP 3xx response codes. You can use the followRedirects option in the {@link Parse.Cloud.HTTPOptions} object to change this behavior.
|
||||
*
|
||||
* Sample request:
|
||||
* ```
|
||||
* Parse.Cloud.httpRequest({
|
||||
* url: 'http://www.parse.com/'
|
||||
* }).then(function(httpResponse) {
|
||||
* // success
|
||||
* console.log(httpResponse.text);
|
||||
* },function(httpResponse) {
|
||||
* // error
|
||||
* console.error('Request failed with response code ' + httpResponse.status);
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @method httpRequest
|
||||
* @name Parse.Cloud.httpRequest
|
||||
* @param {Parse.Cloud.HTTPOptions} options The Parse.Cloud.HTTPOptions object that makes the request.
|
||||
* @return {Promise<Parse.Cloud.HTTPResponse>} A promise that will be resolved with a {@link Parse.Cloud.HTTPResponse} object when the request completes.
|
||||
*/
|
||||
module.exports = function httpRequest(options) {
|
||||
let url;
|
||||
try {
|
||||
url = parse(options.url);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
options = Object.assign(options, encodeBody(options));
|
||||
// support params options
|
||||
if (typeof options.params === 'object') {
|
||||
options.qs = options.params;
|
||||
} else if (typeof options.params === 'string') {
|
||||
options.qs = querystring.parse(options.params);
|
||||
}
|
||||
const client = clients[url.protocol];
|
||||
if (!client) {
|
||||
return Promise.reject(`Unsupported protocol ${url.protocol}`);
|
||||
}
|
||||
const requestOptions = {
|
||||
method: options.method,
|
||||
port: Number(url.port),
|
||||
path: url.pathname,
|
||||
hostname: url.hostname,
|
||||
headers: options.headers,
|
||||
encoding: null,
|
||||
followRedirects: options.followRedirects === true,
|
||||
};
|
||||
if (requestOptions.headers) {
|
||||
Object.keys(requestOptions.headers).forEach(key => {
|
||||
if (typeof requestOptions.headers[key] === 'undefined') {
|
||||
delete requestOptions.headers[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
if (url.search) {
|
||||
options.qs = Object.assign({}, options.qs, querystring.parse(url.query));
|
||||
}
|
||||
if (url.auth) {
|
||||
requestOptions.auth = url.auth;
|
||||
}
|
||||
if (options.qs) {
|
||||
requestOptions.path += `?${querystring.stringify(options.qs)}`;
|
||||
}
|
||||
if (options.agent) {
|
||||
requestOptions.agent = options.agent;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = client.request(requestOptions, makeCallback(resolve, reject, options));
|
||||
if (options.body) {
|
||||
req.write(options.body);
|
||||
}
|
||||
req.on('error', error => {
|
||||
reject(error);
|
||||
});
|
||||
req.end();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef Parse.Cloud.HTTPOptions
|
||||
* @property {String|Object} body The body of the request. If it is a JSON object, then the Content-Type set in the headers must be application/x-www-form-urlencoded or application/json. You can also set this to a {@link Buffer} object to send raw bytes. If you use a Buffer, you should also set the Content-Type header explicitly to describe what these bytes represent.
|
||||
* @property {function} error The function that is called when the request fails. It will be passed a Parse.Cloud.HTTPResponse object.
|
||||
* @property {Boolean} followRedirects Whether to follow redirects caused by HTTP 3xx responses. Defaults to false.
|
||||
* @property {Object} headers The headers for the request.
|
||||
* @property {String} method The method of the request. GET, POST, PUT, DELETE, HEAD, and OPTIONS are supported. Will default to GET if not specified.
|
||||
* @property {String|Object} params The query portion of the url. You can pass a JSON object of key value pairs like params: {q : 'Sean Plott'} or a raw string like params:q=Sean Plott.
|
||||
* @property {function} success The function that is called when the request successfully completes. It will be passed a Parse.Cloud.HTTPResponse object.
|
||||
* @property {string} url The url to send the request to.
|
||||
*/
|
||||
|
||||
module.exports.encodeBody = encodeBody;
|
||||
175
src/request.js
175
src/request.js
@@ -1 +1,174 @@
|
||||
module.exports = require('./cloud-code/httpRequest');
|
||||
import querystring from 'querystring';
|
||||
import log from './logger';
|
||||
import { http, https } from 'follow-redirects';
|
||||
import { parse } from 'url';
|
||||
|
||||
class HTTPResponse {
|
||||
constructor(response, body) {
|
||||
let _text, _data;
|
||||
this.status = response.statusCode;
|
||||
this.headers = response.headers || {};
|
||||
this.cookies = this.headers['set-cookie'];
|
||||
|
||||
if (typeof body == 'string') {
|
||||
_text = body;
|
||||
} else if (Buffer.isBuffer(body)) {
|
||||
this.buffer = body;
|
||||
} else if (typeof body == 'object') {
|
||||
_data = body;
|
||||
}
|
||||
|
||||
const getText = () => {
|
||||
if (!_text && this.buffer) {
|
||||
_text = this.buffer.toString('utf-8');
|
||||
} else if (!_text && _data) {
|
||||
_text = JSON.stringify(_data);
|
||||
}
|
||||
return _text;
|
||||
};
|
||||
|
||||
const getData = () => {
|
||||
if (!_data) {
|
||||
try {
|
||||
_data = JSON.parse(getText());
|
||||
} catch (e) {
|
||||
/* */
|
||||
}
|
||||
}
|
||||
return _data;
|
||||
};
|
||||
|
||||
Object.defineProperty(this, 'body', {
|
||||
get: () => {
|
||||
return body;
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'text', {
|
||||
enumerable: true,
|
||||
get: getText,
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'data', {
|
||||
enumerable: true,
|
||||
get: getData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const clients = {
|
||||
'http:': http,
|
||||
'https:': https,
|
||||
};
|
||||
|
||||
function makeCallback(resolve, reject) {
|
||||
return function (response) {
|
||||
const chunks = [];
|
||||
response.on('data', chunk => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
response.on('end', () => {
|
||||
const body = Buffer.concat(chunks);
|
||||
const httpResponse = new HTTPResponse(response, body);
|
||||
|
||||
// Consider <200 && >= 400 as errors
|
||||
if (httpResponse.status < 200 || httpResponse.status >= 400) {
|
||||
return reject(httpResponse);
|
||||
} else {
|
||||
return resolve(httpResponse);
|
||||
}
|
||||
});
|
||||
response.on('error', reject);
|
||||
};
|
||||
}
|
||||
|
||||
const encodeBody = function ({ body, headers = {} }) {
|
||||
if (typeof body !== 'object') {
|
||||
return { body, headers };
|
||||
}
|
||||
var contentTypeKeys = Object.keys(headers).filter(key => {
|
||||
return key.match(/content-type/i) != null;
|
||||
});
|
||||
|
||||
if (contentTypeKeys.length == 0) {
|
||||
// no content type
|
||||
// As per https://parse.com/docs/cloudcode/guide#cloud-code-advanced-sending-a-post-request the default encoding is supposedly x-www-form-urlencoded
|
||||
|
||||
body = querystring.stringify(body);
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
} else {
|
||||
/* istanbul ignore next */
|
||||
if (contentTypeKeys.length > 1) {
|
||||
log.error('Parse.Cloud.httpRequest', 'multiple content-type headers are set.');
|
||||
}
|
||||
// There maybe many, we'll just take the 1st one
|
||||
var contentType = contentTypeKeys[0];
|
||||
if (headers[contentType].match(/application\/json/i)) {
|
||||
body = JSON.stringify(body);
|
||||
} else if (headers[contentType].match(/application\/x-www-form-urlencoded/i)) {
|
||||
body = querystring.stringify(body);
|
||||
}
|
||||
}
|
||||
return { body, headers };
|
||||
};
|
||||
|
||||
function httpRequest(options) {
|
||||
let url;
|
||||
try {
|
||||
url = parse(options.url);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
options = Object.assign(options, encodeBody(options));
|
||||
// support params options
|
||||
if (typeof options.params === 'object') {
|
||||
options.qs = options.params;
|
||||
} else if (typeof options.params === 'string') {
|
||||
options.qs = querystring.parse(options.params);
|
||||
}
|
||||
const client = clients[url.protocol];
|
||||
if (!client) {
|
||||
return Promise.reject(`Unsupported protocol ${url.protocol}`);
|
||||
}
|
||||
const requestOptions = {
|
||||
method: options.method,
|
||||
port: Number(url.port),
|
||||
path: url.pathname,
|
||||
hostname: url.hostname,
|
||||
headers: options.headers,
|
||||
encoding: null,
|
||||
followRedirects: options.followRedirects === true,
|
||||
};
|
||||
if (requestOptions.headers) {
|
||||
Object.keys(requestOptions.headers).forEach(key => {
|
||||
if (typeof requestOptions.headers[key] === 'undefined') {
|
||||
delete requestOptions.headers[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
if (url.search) {
|
||||
options.qs = Object.assign({}, options.qs, querystring.parse(url.query));
|
||||
}
|
||||
if (url.auth) {
|
||||
requestOptions.auth = url.auth;
|
||||
}
|
||||
if (options.qs) {
|
||||
requestOptions.path += `?${querystring.stringify(options.qs)}`;
|
||||
}
|
||||
if (options.agent) {
|
||||
requestOptions.agent = options.agent;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = client.request(requestOptions, makeCallback(resolve, reject, options));
|
||||
if (options.body) {
|
||||
req.write(options.body);
|
||||
}
|
||||
req.on('error', error => {
|
||||
reject(error);
|
||||
});
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
module.exports = httpRequest;
|
||||
module.exports.encodeBody = encodeBody;
|
||||
module.exports.HTTPResponse = HTTPResponse;
|
||||
|
||||
Reference in New Issue
Block a user