gingee
Version:
Gingee, a secure, batteries included, feature rich, multi-database, enterprise ready application server, co-authored by a human architect and a Generative AI partner.
212 lines (196 loc) • 8.95 kB
JavaScript
const axios = require('axios');
const querystring = require('querystring');
const FormData = require('form-data');
/**
* @module httpclient
* @description A module for making HTTP requests in Gingee applications.
* This module provides functions to perform GET and POST requests, supporting various content types.
* It abstracts the complexities of making HTTP requests, providing a simple interface for developers to interact with web services.
* It supports both text and binary responses, automatically determining the response type based on the content-type header.
* It is particularly useful for applications that need to fetch resources from external APIs or web services, and for sending data to web services in different formats.
* It allows for flexible data submission, making it suitable for APIs that require different content types.
* It provides constants for common POST data types, ensuring that the correct headers are set for the request.
* <b>IMPORTANT:</b> Requires explicit permission to use the module. See docs/permissions-guide for more details.
*/
// --- Constants for POST data types ---
const POST_TYPES = {
JSON: 'application/json',
FORM: 'application/x-www-form-urlencoded',
TEXT: 'text/plain',
XML: 'application/xml',
MULTIPART: 'multipart/form-data',
};
/**
* Checks the response content-type header to see if it's likely binary.
* @private
*/
function isBinaryResponse(headers) {
const contentType = headers['content-type'] || '';
// Added 'application/json' and 'text/' to the list of non-binary types.
if (/^text\/|application\/(json|javascript|xml)/.test(contentType)) {
return false;
}
// A more general check for common binary types.
return /^image\/|audio\/|video\/|application\/(octet-stream|pdf|zip|msword)/.test(contentType);
}
/**
* A helper to process the raw arraybuffer from axios based on response headers.
* @private
*/
function processBody(data, headers) {
const bodyBuffer = Buffer.from(data);
if (isBinaryResponse(headers)) {
// If it's binary, return the raw Buffer.
return bodyBuffer;
}
// Otherwise, convert the Buffer to a string.
return bodyBuffer.toString('utf8');
}
/**
* @function get
* @memberof module:httpclient
* @description Performs an HTTP GET request.
* This function retrieves data from a specified URL and returns the response status, headers, and body.
* It supports both text and binary responses, automatically determining the response type based on the content-type header.
* It abstracts the complexities of making HTTP requests, providing a simple interface for developers to fetch data from the web.
* It can handle various content types, including JSON, text, and binary data, making it versatile for different use cases.
* It is particularly useful for applications that need to fetch resources from external APIs or web services.
* @param {string} url The URL to request.
* @param {object} [options] Axios request configuration options (e.g., headers).
* @returns {Promise<{status: number, headers: object, body: string|Buffer}>}
* @throws {Error} If the request fails or if the response body cannot be processed.
* @example
* const response = await httpclient.get('https://api.example.com/data');
* console.log(response.body);
*/
async function get(url, options = {}) {
// --- KEY CHANGE ---
// Always request the raw arraybuffer so we can decide how to handle it later.
const config = { ...options, responseType: 'arraybuffer' };
try{
const response = await axios.get(url, config);
// Process the body based on the ACTUAL response headers.
const body = processBody(response.data, response.headers);
return {
status: response.status,
headers: response.headers,
body: body,
};
}catch(axiosErr){
if(axiosErr.response){
// If we got a response, process it similarly.
const body = processBody(axiosErr.response.data, axiosErr.response.headers);
return {
status: axiosErr.response.status,
headers: axiosErr.response.headers,
body: body,
};
}else{
return {
status: 500,
headers: {},
body: 'Unexpected error occurred: ' + (axiosErr.message || 'No message provided'),
};
}
}
}
/**
* @function post
* @memberof module:httpclient
* @description Performs an HTTP POST request.
* This function sends data to a specified URL and returns the response status, headers, and body.
* It supports various content types, including JSON, form-urlencoded, plain text, XML, and multipart/form-data.
* It abstracts the complexities of making HTTP POST requests, providing a simple interface for developers to send data to web services.
* It allows for flexible data submission, making it suitable for APIs that require different content types.
* @param {string} url The URL to post to.
* @param {any} body The data to send in the request body.
* @param {object} [options] Axios request configuration options.
* @param {string} [options.postType=httpclient.JSON] The type of data being posted.
* @returns {Promise<{status: number, headers: object, body: string|Buffer}>}
* @throws {Error} If the request fails or if the body cannot be processed.
* @example
* const response = await httpclient.post('https://api.example.com/data', { key: 'value' });
* console.log(response.body);
*/
async function post(url, body, options = {}) {
const postType = options.postType || POST_TYPES.JSON;
const config = {
headers: { 'Content-Type': postType, ...options.headers },
// --- KEY CHANGE ---
// Always request the raw arraybuffer for the response.
responseType: 'arraybuffer',
};
let data = body;
// ... (The logic for preparing the POST data based on postType is unchanged) ...
if (postType === POST_TYPES.JSON) data = JSON.stringify(body);
if (postType === POST_TYPES.FORM) data = querystring.stringify(body);
if (postType === POST_TYPES.MULTIPART) {
if (!(body instanceof FormData)) throw new Error("For MULTIPART, body must use object created with formdata module.");
delete config.headers['Content-Type'];
}
try{
const response = await axios.post(url, data, config);
// Process the body based on the ACTUAL response headers.
const responseBody = processBody(response.data, response.headers);
return {
status: response.status,
headers: response.headers,
body: responseBody,
};
}catch(axiosErr){
if(axiosErr.response){
// If we got a response, process it similarly.
const body = processBody(axiosErr.response.data, axiosErr.response.headers);
return {
status: axiosErr.response.status,
headers: axiosErr.response.headers,
body: body,
};
}else{
return {
status: 500,
headers: {},
body: 'Unexpected error occurred: ' + (axiosErr.message || 'No message provided'),
};
}
}
}
module.exports = {
get,
post,
/**
* @constant JSON
* @memberof module:httpclient
* @description Constant for JSON content type in POST requests.
* This constant can be used to specify that the POST request body is in JSON format.
*/
JSON: POST_TYPES.JSON,
/**
* @constant FORM
* @memberof module:httpclient
* @description Constant for form-urlencoded content type in POST requests.
* This constant can be used to specify that the POST request body is in form-urlencoded format.
*/
FORM: POST_TYPES.FORM,
/**
* @constant TEXT
* @memberof module:httpclient
* @description Constant for plain text content type in POST requests.
* This constant can be used to specify that the POST request body is in plain text format.
*/
TEXT: POST_TYPES.TEXT,
/**
* @constant XML
* @memberof module:httpclient
* @description Constant for XML content type in POST requests.
* This constant can be used to specify that the POST request body is in XML format.
*/
XML: POST_TYPES.XML,
/**
* @constant MULTIPART
* @memberof module:httpclient
* @description Constant for multipart/form-data content type in POST requests.
* This constant can be used to specify that the POST request body is in multipart/form-data format.
*/
MULTIPART: POST_TYPES.MULTIPART,
};