What causes the ESOCKETTIMEDOUT or ECONNREFUSED error under load with Docker/Node? - node.js

Our Node API running on docker gets the ESOCKETTIMEDOUT or ECONNREFUSED error under load. This happens when we call the .NET API from node API.
{ [-]
_anonFuncCount: 3
_body: {"events":[{"serviceCategoryCode":{"codeValue":"core"},"eventNameCode":{"codeValue":"clock.punch","shortName":"Clock Punch"},"data":{"eventContext":{"associateOID":"","workAssignmentId":""},"transform":{"clockEntry":{"deviceDateTime":"2017-12-10T18:21:46-04:00","entryDateTime":"2017-12-10T18:21:46-04:00","actionCode":{"codeValue":"clockout","shortName":"clockout"},"laborAllocations":[]}}}}]}
_cacheURL: /api/offlinePunch
_clen: 424
_consuming: true
_contentType: application/json
_currentHandler: handler-2
_currentRoute: postapiofflinepunch100
_dtraceId: 5276
_dumped: false
_events: { [+]
}
_eventsCount: 4
_matchedVersion: 1.0.0
_negotiator: { [+]
}
_parsedBody: true
_readBody: true
_readableState: { [+]
}
_time: 1512958462607
_timerMap: { [+]
}
_url: { [+]
}
_version: \x7e1
body: { [+]
}
client: ~socket
complete: true
connection: ~socket
context: ~params
domain: null
headers: { [+]
}
httpVersion: 1.1
httpVersionMajor: 1
httpVersionMinor: 1
log: ~socket~_httpMessage~log
method: POST
params: { [+]
}
query: { [+]
}
rawBody: {"events":[{"serviceCategoryCode":{"codeValue":"core"},"eventNameCode":{"codeValue":"clock.punch","shortName":"Clock Punch"},"data":{"eventContext":{"associateOID":"","workAssignmentId":""},"transform":{"clockEntry":{"deviceDateTime":"2017-12-10T18:21:46-04:00","entryDateTime":"2017-12-10T18:21:46-04:00","actionCode":{"codeValue":"clockout","shortName":"clockout"},"laborAllocations":[]}}}}]}
rawHeaders: [ [+]
]
rawTrailers: [ [+]
]
readable: false
route: { [+]
}
serverName: time-clocking-api
socket: { [+]
}
statusCode: null
statusMessage: null
timers: [ [+]
]
trailers: { [+]
}
upgrade: false
url: /api/offlinePunch
uuid: f008be5d-ee11-4e1e-8ae4-afa47dc5e587
}

This is most likely due to the same issue here. Also,here is an excellent explanation of what is going on in node. You may have to set the UV_THREADPOOL_SIZE in your Docker Container.

Related

Getting an error "Error: connect ECONNREFUSED 127.0.0.1:80" while sending an image to Backblaze B2

So I've been trying to implement image upload to Backblaze B2 bucket for about three days now. I struggled with getting the data, but I'm getting an error about connection refusing I guess. It returns error code 504 and I've tried fixing it by sending just the image, without converting it to base64 data. I don't understand a lot about base64 data, so I think the error might be caused by base64Data. I also don't have a lot of experience with backblaze-b2 and found the npm package "backblaze-b2" not too long ago.
Here's the code:
const B2 = require('backblaze-b2');
const fs = require('fs');
export const uploadImage = async (req, res) => {
try {
const b2 = new B2({
accountId: process.env.BACKBLAZE_ACCOUNT_ID,
applicationKey: process.env.BACKBLAZE_APPLICATION_MASTER_KEY,
});
await b2.authorize();
// console.log(req.body);
const { image } = req.body;
if (!image) return res.status(400).send('No image found!');
// prepare the image
const base64Data = new Buffer.from(
image.replace(/^data:image\/\w+;base64,/,""),
'base64'
);
const handleImage = async () => {
try {
let uploadUrl = await b2.getUploadUrl({
bucketId: process.env.BACKBLAZE_BUCKET_ID,
});
// console.log('Is this not working?', uploadUrl);
const data = b2.uploadFile({
uploadUrl: uploadUrl.data.bucketId,
uploadAuthToken: uploadUrl.data.authorizationToken,
fileName: 'Pepe', //<-- TODO: Fix later
data: base64Data, // <-- Figure out what to pass in
onUploadProgress: (e) => null,
});
console.log(data);
res.send(data);
} catch (err) {
console.log('Bucket error or something: ', err);
}
};
handleImage();
} catch (err) {
console.log(err);
}
};
Here's the console:
Server is running on port 8000
DB CONNECTED
GET /api/csrf-token 200 2.732 ms - 52
GET /api/current-instructor 304 176.213 ms - -
Bucket error or something: Error: connect ECONNREFUSED 127.0.0.1:80
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1132:16) {
errno: -4078,
code: 'ECONNREFUSED',
syscall: 'connect',
address: '127.0.0.1',
port: 80,
config: {
url: 'deleted, for safety reasons',
method: 'post',
data: <Buffer ff d8 ff e0 00 10 4a 46 49 46 00 01 01 00 00 01 00 01 00 00 ff e2 02 28 49 43 43 5f 50 52 4f 46
49 4c 45 00 01 01 00 00 02 18 00 00 00 00 02 10 00 00 ... 71126 more bytes>,
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'b2/x-auto',
Authorization: 'deleted, for safety reasons',
'Content-Length': 71176,
'X-Bz-File-Name': 'Pepe',
'X-Bz-Content-Sha1': 'deleted, for safety reasons(possibly)',
'User-Agent': 'axios/0.21.4'
},
transformRequest: [ [Function (anonymous)] ],
transformResponse: [ [Function: transformResponse] ],
timeout: 0,
adapter: [Function: httpAdapter],
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
onUploadProgress: [Function: onUploadProgress],
maxContentLength: -1,
maxBodyLength: -1,
maxRedirects: 0,
validateStatus: [Function: validateStatus],
transitional: {
silentJSONParsing: true,
forcedJSONParsing: true,
clarifyTimeoutError: false
},
'axios-retry': { retryCount: 3, lastRequestTime: 1635347528124 }
},
request: <ref *1> ClientRequest {
_events: [Object: null prototype] {
response: [Function],
error: [Function: handleRequestError]
},
_eventsCount: 2,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: true,
sendDate: false,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
_contentLength: 71176,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
_closed: false,
socket: Socket {
connecting: false,
_hadError: true,
_parent: null,
_host: 'localhost',
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 8,
_maxListeners: undefined,
_writableState: [WritableState],
allowHalfOpen: false,
_sockname: null,
_pendingData: [Array],
_pendingEncoding: '',
server: null,
_server: null,
parser: null,
_httpMessage: [Circular *1],
[Symbol(async_id_symbol)]: 325,
[Symbol(kHandle)]: null,
[Symbol(kSetNoDelay)]: false,
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: null,
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0
},
_header: 'POST deleted, for safety reasons HTTP/1.1\r\n' +
'Accept: application/json, text/plain, */*\r\n' +
'Content-Type: b2/x-auto\r\n' +
'Authorization: deleted, for safety reasons=\r\n' +
'Content-Length: 71176\r\n' +
'X-Bz-File-Name: Pepe\r\n' +
'X-Bz-Content-Sha1: deleted, for safety reasons\r\n' +
'User-Agent: axios/0.21.4\r\n' +
'Host: localhost\r\n' +
'Connection: close\r\n' +
'\r\n',
_keepAliveTimeout: 0,
_onPendingData: [Function: nop],
agent: Agent {
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
defaultPort: 80,
protocol: 'http:',
options: [Object: null prototype],
requests: [Object: null prototype] {},
sockets: [Object: null prototype],
freeSockets: [Object: null prototype] {},
keepAliveMsecs: 1000,
keepAlive: false,
maxSockets: Infinity,
maxFreeSockets: 256,
scheduling: 'lifo',
maxTotalSockets: Infinity,
totalSocketCount: 1,
[Symbol(kCapture)]: false
},
socketPath: undefined,
method: 'POST',
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
path: 'deleted, for safety reasons',
_ended: false,
res: null,
aborted: false,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
reusedSocket: false,
host: 'localhost',
protocol: 'http:',
[Symbol(kCapture)]: false,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype] {
accept: [Array],
'content-type': [Array],
authorization: [Array],
'content-length': [Array],
'x-bz-file-name': [Array],
'x-bz-content-sha1': [Array],
'user-agent': [Array],
host: [Array]
}
},
response: undefined,
isAxiosError: true,
toJSON: [Function: toJSON]
}
Any help would be appreciated.
Thanks.
There are a few changes I made to get this working:
Use bodyParser.raw() to parse the body into the Buffer format that the backblaze-b2 library is expecting. It doesn't want base64.
Correct uploadUrl.data.bucketId in the call to b2.uploadFile to uploadUrl.data.uploadUrl.data.uploadUrl. This is what was causing the 'connection refused' error. Since the uploadUrl wasn't a URL, I'm guessing that b2.uploadFile assumed it was a path and you wanted to connect to localhost.
Await the response from b2.uploadFile.
Use response.data rather than just response to see the API response.
I built out your code into a runnable sample:
// I like to put my env vars in a .env file
const dotenv = require('dotenv');
dotenv.config();
const B2 = require('backblaze-b2');
const fs = require('fs');
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
const port = process.env.PORT || 3000
const uploadImage = async (req, res) => {
try {
const b2 = new B2({
accountId: process.env.BACKBLAZE_ACCOUNT_ID,
applicationKey: process.env.BACKBLAZE_APPLICATION_MASTER_KEY,
});
await b2.authorize();
// console.log("req.body:", req.body);
if (!req.body) return res.status(400).send('No image found!');
const handleImage = async () => {
try {
let uploadUrl = await b2.getUploadUrl({
bucketId: process.env.BACKBLAZE_BUCKET_ID,
});
// Make the JSON more readable
console.log('getUploadUrl:', JSON.stringify(uploadUrl.data, undefined, 2));
// uploadFile returns a promise, so we need to await the response
const response = await b2.uploadFile({
uploadUrl: uploadUrl.data.uploadUrl,
uploadAuthToken: uploadUrl.data.authorizationToken,
fileName: 'Pepe', //<-- TODO: Fix later
data: req.body, // <-- This is the raw data as a buffer
onUploadProgress: (e) => null,
});
const prettyResponse = JSON.stringify(response.data, undefined, 2);
console.log('uploadFile: ', prettyResponse);
res.send(prettyResponse);
} catch (err) {
console.log('Bucket error or something: ', err);
}
};
handleImage();
} catch (err) {
console.log(err);
}
};
app.use(bodyParser.raw({ // Raw mode returns the posted body as a Buffer
type: '*/*' // Parse any mime type
}))
app.post('/', function (req, res) {
uploadImage(req, res)
})
app.listen(port, () => {
console.log(`Listening at http://localhost:${port}`)
})
Send a file with curl:
curl http://localhost:3000/ --data-binary #image.png
Console output (somewhat redacted!):
Listening at http://localhost:3000
getUploadUrl: {
"authorizationToken": "********",
"bucketId": "********",
"uploadUrl": "https://********.backblaze.com/b2api/v2/b2_upload_file/********"
}
uploadFile: {
"accountId": "********",
"action": "upload",
"bucketId": "********",
"contentLength": 3802,
"contentMd5": "d9b8b28f7fda3acfe7838ead41d8df38",
"contentSha1": "f8040f1068715160ef98ab98fde80f9214cb2845",
"contentType": "application/octet-stream",
"fileId": "********",
"fileInfo": {},
"fileName": "Pepe",
"fileRetention": {
"isClientAuthorizedToRead": true,
"value": {
"mode": null,
"retainUntilTimestamp": null
}
},
"legalHold": {
"isClientAuthorizedToRead": true,
"value": null
},
"serverSideEncryption": {
"algorithm": null,
"mode": null
},
"uploadTimestamp": 1641496698000
}

How do I suppress expected Axios error messages when testing error states with react-testing-library?

I'm working on adapting some React code of mine to use #testing-library/react, react-query, and msw to mock network calls that I make using axios.
So far (after some brainbending) I've got it working with this code! (yay!)
The test in question (simplified):
import React from 'react';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { screen, waitFor, waitForElementToBeRemoved } from '#testing-library/react';
import { queryCache } from 'react-query';
import userEvent from '#testing-library/user-event';
import Login from '../Login';
import { render, queryClient } from '~/testhelpers/helpers.integration';
// #endregion imports
const server = setupServer(
// ...default setup for successful request; not relevant here
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe('rendering', () => {
describe('error', () => {
it.only('should render error message when request fails', async () => {
server.use(
rest.post(`${API_BASE_URL}api/auth/token`, (req, res, ctx) => {
return res(ctx.status(400), ctx.json('Invalid username or password.'));
})
);
render(<Login />);
const usernameInput = screen.getByLabelText('Username');
const passwordInput = screen.getByLabelText('Password');
const submitInput = screen.getByRole('button', { name: 'Login' });
userEvent.type(usernameInput, 'testUsername');
userEvent.type(passwordInput, 'testPassword');
userEvent.click(submitInput);
await waitFor(() => {
expect(screen.getByText('Invalid username or password.')).toBeInTheDocument();
});
});
});
});
Again, this test works! Hooray! But the issue is that I am making axios think (via msw) that it is receiving a 400 response for this query, and I get this big chunk of error message in my console when I run this test with Jest:
PASS src/display/views/login/__tests__/Login.spec.js
rendering
error
√ should hide loading indicator and render error message when request fails (522 ms)
console.error
Error: Request failed with status code 400
at createError (C:\Users\me\Documents\_Programming\GitHub\my-project\node_modules\axios\lib\core\createError.js:16:15)
at settle (C:\Users\me\Documents\_Programming\GitHub\my-project\node_modules\axios\lib\core\settle.js:18:12)
at XMLHttpRequestOverride.handleLoad [as onreadystatechange] (C:\Users\me\Documents\_Programming\GitHub\my-project\node_modules\axios\lib\adapters\xhr.js:59:7)
at XMLHttpRequestOverride.triggerReadyStateChange (C:\Users\me\Documents\_Programming\GitHub\my-project\node_modules\node-request-interceptor\src\interceptors\XMLHttpRequest\XMLHttpRequestOverride.ts:133:33)
at XMLHttpRequestOverride.trigger (C:\Users\me\Documents\_Programming\GitHub\my-project\node_modules\node-request-interceptor\src\interceptors\XMLHttpRequest\XMLHttpRequestOverride.ts:145:12)
at C:\Users\me\Documents\_Programming\GitHub\my-project\node_modules\node-request-interceptor\src\interceptors\XMLHttpRequest\XMLHttpRequestOverride.ts:306:18
at runNextTicks (internal/process/task_queues.js:62:5)
at processTimers (internal/timers.js:489:9) {
config: {
adapter: [Function: xhrAdapter],
transformRequest: { '0': [Function: transformRequest] },
transformResponse: { '0': [Function: transformResponse] },
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: [Function: validateStatus],
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json;charset=utf-8'
},
method: 'post',
url: 'http://baseurl/api/auth/token',
data: '{"username":"testUsername","password":"testPassword"}'
},
request: XMLHttpRequestOverride {
requestHeaders: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json;charset=utf-8'
},
responseHeaders: { 'x-powered-by': 'msw', 'content-type': 'application/json' },
_events: [],
UNSENT: 0,
OPENED: 1,
HEADERS_RECEIVED: 2,
LOADING: 3,
DONE: 4,
onreadystatechange: [Function: handleLoad],
onabort: null,
onerror: [Function: handleError],
onload: null,
onloadend: null,
onloadstart: null,
onprogress: null,
ontimeout: [Function: handleTimeout],
url: 'http://baseurl/api/auth/token',
method: 'POST',
readyState: 4,
withCredentials: false,
status: 400,
statusText: 'Bad Request',
data: '{"username":"testUsername","password":"testPassword"}',
response: '"Invalid username or password."',
responseType: 'text',
responseText: '"Invalid username or password."',
responseXML: null,
responseURL: '',
upload: null,
timeout: 0,
async: true,
user: undefined,
password: undefined
},
response: {
data: 'Invalid username or password.',
status: 400,
statusText: 'Bad Request',
headers: { 'x-powered-by': 'msw', 'content-type': 'application/json' },
config: {
adapter: [Function: xhrAdapter],
transformRequest: [Object],
transformResponse: [Object],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: [Function: validateStatus],
headers: [Object],
method: 'post',
url: 'http://baseurl/api/auth/token',
data: '{"username":"testUsername","password":"testPassword"}'
},
request: XMLHttpRequestOverride {
requestHeaders: [Object],
responseHeaders: [Object],
_events: [],
UNSENT: 0,
OPENED: 1,
HEADERS_RECEIVED: 2,
LOADING: 3,
DONE: 4,
onreadystatechange: [Function: handleLoad],
onabort: null,
onerror: [Function: handleError],
onload: null,
onloadend: null,
onloadstart: null,
onprogress: null,
ontimeout: [Function: handleTimeout],
url: 'http://baseurl/api/auth/token',
method: 'POST',
readyState: 4,
withCredentials: false,
status: 400,
statusText: 'Bad Request',
data: '{"username":"testUsername","password":"testPassword"}',
response: '"Invalid username or password."',
responseType: 'text',
responseText: '"Invalid username or password."',
responseXML: null,
responseURL: '',
upload: null,
timeout: 0,
async: true,
user: undefined,
password: undefined
}
}
}
at CustomConsole.console.error (node_modules/#testing-library/react/dist/act-compat.js:52:34)
at node_modules/react-query/lib/core/mutation.js:115:32
I think, to a certain extent, this is expected, but it's REALLY unnecessary spam in my tests given that I am expecting and in fact wanting that request to fail. Is there a best practice way to remove this unnecessary log via config for Jest, RTL, msw, or Axios?
react-query logs to the console per default, you can override this with setLogger:
import { setLogger } from 'react-query'
setLogger({
log: printLog,
warn: printWarn,
error: printError,
})
see also: https://react-query.tanstack.com/reference/setLogger

How to get a token for Rest Client?

When I authorize, it passes without errors, but I can’t get a token for further requests (get, post).
As a result, I should get such a token:
{
"token": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJyb2xlX25hbWUiOiLQkNC00LzQuNC90LjRgdGC0YDQsNGC0L7RgCIsImlhdCI6MTU4NTI5MDY5MSwiZXhwIjoxNTg1Mjk0MjkxfQ.saODDtA27NAf6hThUhvltfv6bpeieWDQ8bYxLhjq6Dc"
}
But I do not see him in the object.
Result:
Request {
maxAttempts: 4,
retryDelay: 5000,
fullResponse: true,
attempts: 1,
options: {
url: 'http://localhost:3000/api/auth/login',
method: 'POST',
json: { username: 'admin', password: '123456' },
maxAttempts: 5,
retryDelay: 5000,
fullResponse: true,
promiseFactory: [Function: defaultPromiseFactory]
},
retryStrategy: [Function: HTTPError],
delayStrategy: [Function],
_timeout: null,
_req: Request {
_events: [Object: null prototype] {
error: [Function: bound ],
complete: [Function: bound ],
pipe: [Function]
},
_eventsCount: 3,
_maxListeners: undefined,
method: 'POST',
maxAttempts: 5,
retryDelay: 5000,
fullResponse: true,
promiseFactory: [Function: defaultPromiseFactory],
callback: [Function],
readable: true,
writable: true,
explicitMethod: true,
_qs: Querystring {
request: [Circular],
lib: [Object],
useQuerystring: undefined,
parseOptions: {},
stringifyOptions: {}
},
_auth: Auth {
request: [Circular],
hasAuth: false,
sentAuth: false,
bearerToken: null,
user: null,
pass: null
},
_oauth: OAuth { request: [Circular], params: null },
_multipart: Multipart {
request: [Circular],
boundary: 'd1a5b314-42ec-44c0-87ab-c4ebf0a28613',
chunked: false,
body: null
},
_redirect: Redirect {
request: [Circular],
followRedirect: true,
followRedirects: true,
followAllRedirects: false,
followOriginalHttpMethod: false,
allowRedirect: [Function],
maxRedirects: 10,
redirects: [],
redirectsFollowed: 0,
removeRefererHeader: false
},
_tunnel: Tunnel {
request: [Circular],
proxyHeaderWhiteList: [Array],
proxyHeaderExclusiveList: []
},
headers: {
host: 'localhost:3000',
accept: 'application/json',
'content-type': 'application/json',
'content-length': 40
},
setHeader: [Function],
hasHeader: [Function],
getHeader: [Function],
removeHeader: [Function],
localAddress: undefined,
pool: {},
dests: [],
__isRequestRequest: true,
_callback: [Function: bound ] AsyncFunction,
uri: Url {
protocol: 'http:',
slashes: true,
auth: null,
host: 'localhost:3000',
port: '3000',
hostname: 'localhost',
hash: null,
search: null,
query: null,
pathname: '/api/auth/login',
path: '/api/auth/login',
href: 'http://localhost:3000/api/auth/login'
},
proxy: null,
tunnel: false,
setHost: true,
originalCookieHeader: undefined,
_disableCookies: true,
_jar: undefined,
port: '3000',
host: 'localhost',
path: '/api/auth/login',
_json: true,
body: '{"username":"admin","password":"123456"}',
httpModule: {
_connectionListener: [Function: connectionListener],
METHODS: [Array],
STATUS_CODES: [Object],
Agent: [Function],
ClientRequest: [Function: ClientRequest],
IncomingMessage: [Function: IncomingMessage],
OutgoingMessage: [Function: OutgoingMessage],
Server: [Function: Server],
ServerResponse: [Function: ServerResponse],
createServer: [Function: createServer],
get: [Function: get],
request: [Function: request],
maxHeaderSize: [Getter],
globalAgent: [Getter/Setter]
},
agentClass: [Function: Agent] { defaultMaxSockets: Infinity },
agent: Agent {
_events: [Object: null prototype],
_eventsCount: 1,
_maxListeners: undefined,
defaultPort: 80,
protocol: 'http:',
options: [Object],
requests: {},
sockets: {},
freeSockets: {},
keepAliveMsecs: 1000,
keepAlive: false,
maxSockets: Infinity,
maxFreeSockets: 256
}
},
_callback: null,
_resolve: [Function: promiseResolve],
_reject: [Function: promiseReject],
_promise: Promise {
_handler: Pending {
consumers: undefined,
receiver: undefined,
handler: undefined,
resolved: false
}
},
reply: [Function: requestRetryReply]
}
request:
const auth = {
username : "admin",
password : "123456"
}
var options = {
url: `${config.url}/api/auth/login`,
method: 'POST',
json: auth
};
try {
var result = request(options);
console.log(result)
} catch {
console.log('[ERROR]:', err);
}
Didn't you forget the callback? I mean:
const auth = {
username : "admin",
password : "123456"
}
var options = {
url: `${config.url}/api/auth/login`,
method: 'POST',
json: auth
};
request(options, function(err, res, body) {
if(err){
console.log('[ERROR]:', err);
}
else{
let result = JSON.parse(body);
console.log(result);
}
});
...

Mautic API cannot create DynamicContent

I am struggling to successfully execute create calls on the mautic REST api. In my node project I am using a standard express installation and a wrapper for the node api https://github.com/sambarnes90/node-mautic
I have authenticated my machine with the api and can PULL data without problem. The problem is POSTing data to it.
The documentation for the targeted post request is here: https://developer.mautic.org/#create-dynamic-content
This is my route. It derives from the example in node-mautic which works fine, but putting something into mautic does not work for me:
router.get('/api', function(req, res, next) {
//check auth and create config object
mautic.auth.checkAuth(function(config) {
// if all worked
if (config.auth_object) {
var testbody = {
"name": "apitest2"
}
//create call
mautic.dynamiccontent.createDynamicContent(config, testbody, function(data) {
console.log(data);
return res.send(data);
});
//request call for id=3
/*mautic.dynamiccontent.getDynamicContent(config, 3, function(data) {
console.log(data);
return res.send(data);
});
});*/
}
});
});
I am getting this response from the API:
[ { code: 400,
message: 'name: A name is required.',
details: { name: [Array] } } ]
This is the GitHub project for this: https://github.com/raphaelurban/mautic-api-test
EDIT:
I also tried:
var testbody = new Array();
testbody['name'] = 'apitest2';
with the same result.
Here is the full POST request:
Request {
domain: null,
_events:
{ error: [Function: bound ],
complete: [Function: bound ],
pipe: [Function] },
_eventsCount: 3,
_maxListeners: undefined,
body: '{"name":"apitest2"}',
callback: [Function],
method: 'POST',
readable: true,
writable: true,
explicitMethod: true,
_qs:
Querystring {
request: [Circular],
lib: { formats: [Object], parse: [Function], stringify: [Function] },
useQuerystring: undefined,
parseOptions: {},
stringifyOptions: {} },
_auth:
Auth {
request: [Circular],
hasAuth: false,
sentAuth: false,
bearerToken: null,
user: null,
pass: null },
_oauth: OAuth { request: [Circular], params: null },
_multipart:
Multipart {
request: [Circular],
boundary: 'afa47f83-3327-4d67-982d-4db6daab8a39',
chunked: false,
body: null },
_redirect:
Redirect {
request: [Circular],
followRedirect: true,
followRedirects: true,
followAllRedirects: false,
followOriginalHttpMethod: false,
allowRedirect: [Function],
maxRedirects: 10,
redirects: [],
redirectsFollowed: 0,
removeRefererHeader: false },
_tunnel:
Tunnel {
request: [Circular],
proxyHeaderWhiteList:
[ 'accept',
'accept-charset',
'accept-encoding',
'accept-language',
'accept-ranges',
'cache-control',
'content-encoding',
'content-language',
'content-location',
'content-md5',
'content-range',
'content-type',
'connection',
'date',
'expect',
'max-forwards',
'pragma',
'referer',
'te',
'user-agent',
'via' ],
proxyHeaderExclusiveList: [] },
headers: { host: 'hrutest.mautic.net', 'content-length': 19 },
setHeader: [Function],
hasHeader: [Function],
getHeader: [Function],
removeHeader: [Function],
localAddress: undefined,
pool: {},
dests: [],
__isRequestRequest: true,
_callback: [Function],
uri:
Url {
protocol: 'https:',
slashes: true,
auth: null,
host: 'hrutest.mautic.net',
port: 443,
hostname: 'hrutest.mautic.net',
hash: null,
search: '?access_token=ZTJlOWY3MzI0YTg4OWViNzU3YjY5YjFiZjRlOTU1OWFiYWM1N2ZmOTQ4OWI4NGI2NzZjZGUyMDg3ZWMxZmU5OA',
query: 'access_token=ZTJlOWY3MzI0YTg4OWViNzU3YjY5YjFiZjRlOTU1OWFiYWM1N2ZmOTQ4OWI4NGI2NzZjZGUyMDg3ZWMxZmU5OA',
pathname: '/api/dynamiccontents/new',
path: '/api/dynamiccontents/new?access_token=ZTJlOWY3MzI0YTg4OWViNzU3YjY5YjFiZjRlOTU1OWFiYWM1N2ZmOTQ4OWI4NGI2NzZjZGUyMDg3ZWMxZmU5OA',
href: 'https://hrutest.mautic.net/api/dynamiccontents/new?access_token=ZTJlOWY3MzI0YTg4OWViNzU3YjY5YjFiZjRlOTU1OWFiYWM1N2ZmOTQ4OWI4NGI2NzZjZGUyMDg3ZWMxZmU5OA' },
proxy: null,
tunnel: true,
setHost: true,
originalCookieHeader: undefined,
_disableCookies: true,
_jar: undefined,
port: 443,
host: 'hrutest.mautic.net',
path: '/api/dynamiccontents/new?access_token=ZTJlOWY3MzI0YTg4OWViNzU3YjY5YjFiZjRlOTU1OWFiYWM1N2ZmOTQ4OWI4NGI2NzZjZGUyMDg3ZWMxZmU5OA',
httpModule:
{ Server: { [Function: Server] super_: [Object] },
createServer: [Function: createServer],
globalAgent:
Agent {
domain: null,
_events: [Object],
_eventsCount: 1,
_maxListeners: undefined,
defaultPort: 443,
protocol: 'https:',
options: [Object],
requests: {},
sockets: [Object],
freeSockets: {},
keepAliveMsecs: 1000,
keepAlive: false,
maxSockets: Infinity,
maxFreeSockets: 256,
maxCachedSessions: 100,
_sessionCache: [Object] },
Agent: { [Function: Agent] super_: [Object] },
request: [Function: request],
get: [Function: get] },
agentClass:
{ [Function: Agent]
super_: { [Function: Agent] super_: [Object], defaultMaxSockets: Infinity } },
agent:
Agent {
domain: null,
_events: { free: [Function] },
_eventsCount: 1,
_maxListeners: undefined,
defaultPort: 443,
protocol: 'https:',
options: { path: null },
requests: {},
sockets: { 'hrutest.mautic.net:443:::::::::': [Array] },
freeSockets: {},
keepAliveMsecs: 1000,
keepAlive: false,
maxSockets: Infinity,
maxFreeSockets: 256,
maxCachedSessions: 100,
_sessionCache: { map: [Object], list: [Array] } } }
I haven't had much chance to test any more of this wrapper in recent months.
I would try sending it as an array though if possible?
Try these two combinations of testbody:
var testbody = [
"name": "apitest2"
]
Or
var testbody = {[
"name": "apitest2"
]}
And see what results you get?
Can you possibly get full logs of the request?
Finally I worked it out. At least it is some solution, I am not sure if this also works with the current function.
I found that all other tutorials use form instead of body when posting data. Additionally, their objects do not get JSON.stringified.
So with my object:
var testbody = {
name: 'apitest99'
};
It works when I change the function in node-mautic:
createDynamicContent: function(config, queryParameters, callback) {
var url = config.api_endpoint + "/dynamiccontents/new?access_token=" + config.auth_object.access_token;
//queryParameters = JSON.stringify(queryParameters);**
request.post({
url: url,
form: queryParameters
}, function(err, res) {
if (err) {
callback(err);
} else {
var asset = JSON.parse(res.body);
callback(asset);
}
})
},
And I am getting the following response from the API:
{ dynamicContent:
{ isPublished: true,
dateAdded: '2018-07-13T09:37:07+00:00',
dateModified: null,
createdBy: 1,
createdByUser: 'Raphael',
modifiedBy: null,
modifiedByUser: null,
id: 10,
name: 'apitest99',
category: null,
publishUp: null,
publishDown: null,
sentCount: 0,
variantParent: null,
variantChildren: [],
content: null,
filters: [],
isCampaignBased: true,
slotName: '' } }

How to connect to moto standalone server?

I just installed moto and tried to connect to the standalone server with the following node.js code:
const AWS = require('aws-sdk')
const ep = new AWS.Endpoint('http://127.0.0.1:5000')
const route53 = new AWS.Route53({endpoint: ep})
const params = {}
console.log(route53.listHostedZones(params))
The code sets AWS service's endpoint, which points to the standalone server of moto. And then sends listHostedZones request to the endpoint. The output of this program is
Request {
domain: null,
service:
Service {
config:
Config {
credentials: [Object],
credentialProvider: [Object],
region: undefined,
logger: null,
apiVersions: {},
apiVersion: null,
endpoint: [Object],
httpOptions: [Object],
maxRetries: undefined,
maxRedirects: 10,
paramValidation: true,
sslEnabled: true,
s3ForcePathStyle: false,
s3BucketEndpoint: false,
s3DisableBodySigning: true,
computeChecksums: true,
convertResponseTypes: true,
correctClockSkew: false,
customUserAgent: null,
dynamoDbCrc32: true,
systemClockOffset: 0,
signatureVersion: null,
signatureCache: true,
retryDelayOptions: {},
useAccelerateEndpoint: false },
endpoint:
Endpoint {
protocol: 'http:',
host: '127.0.0.1:5000',
port: 5000,
hostname: '127.0.0.1',
pathname: '/',
path: '/',
href: 'http://127.0.0.1:5000/',
constructor: [Object] },
_clientId: 1 },
operation: 'listHostedZones',
params: {},
httpRequest:
HttpRequest {
method: 'POST',
path: '/',
headers: { 'User-Agent': 'aws-sdk-nodejs/2.166.0 darwin/v6.2.2' },
body: '',
endpoint:
Endpoint {
protocol: 'http:',
host: '127.0.0.1:5000',
port: 5000,
hostname: '127.0.0.1',
pathname: '/',
path: '/',
href: 'http://127.0.0.1:5000/',
constructor: [Object] },
region: undefined,
_userAgent: 'aws-sdk-nodejs/2.166.0 darwin/v6.2.2' },
startTime: 2017-12-18T13:27:48.148Z,
response:
Response {
request: [Circular],
data: null,
error: null,
retryCount: 0,
redirectCount: 0,
httpResponse:
HttpResponse {
statusCode: undefined,
headers: {},
body: undefined,
streaming: false,
stream: null },
maxRetries: 3,
maxRedirects: 10 },
_asm:
AcceptorStateMachine {
currentState: 'validate',
states:
{ validate: [Object],
build: [Object],
afterBuild: [Object],
sign: [Object],
retry: [Object],
afterRetry: [Object],
send: [Object],
validateResponse: [Object],
extractError: [Object],
extractData: [Object],
restart: [Object],
success: [Object],
error: [Object],
complete: [Object] } },
_haltHandlersOnError: false,
_events:
{ validate:
[ [Object],
[Function: VALIDATE_REGION],
[Function: BUILD_IDEMPOTENCY_TOKENS],
[Function: VALIDATE_PARAMETERS] ],
afterBuild:
[ [Object],
[Function: SET_CONTENT_LENGTH],
[Function: SET_HTTP_HOST] ],
restart: [ [Function: RESTART] ],
sign: [ [Object] ],
validateResponse: [ [Function: VALIDATE_RESPONSE] ],
send: [ [Object] ],
httpHeaders: [ [Function: HTTP_HEADERS] ],
httpData: [ [Function: HTTP_DATA] ],
httpDone: [ [Function: HTTP_DONE] ],
retry:
[ [Function: FINALIZE_ERROR],
[Function: INVALIDATE_CREDENTIALS],
[Function: EXPIRED_SIGNATURE],
[Function: CLOCK_SKEWED],
[Function: REDIRECT],
[Function: RETRY_CHECK] ],
afterRetry: [ [Object] ],
build: [ [Function: buildRequest], [Function: sanitizeUrl] ],
extractData: [ [Function: extractData], [Function: extractRequestId] ],
extractError: [ [Function: extractError], [Function: extractRequestId] ],
httpError: [ [Function: ENOTFOUND_ERROR] ] },
emit: [Function: emit] }
As you can see in the above output, there's an ENOTFOUND_ERROR error in it. And I couldn't find any new connection in the console output of moto.
The way I start moto is moto_server route53:
> moto_server route53
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
What's wrong with my configurations? I think there's something wrong with my node.js code. Do I misinterpret the meaning of endpoint?
const AWS = require('aws-sdk')
const ep = new AWS.Endpoint('http://127.0.0.1:5000')
const initParam = {
endpoint: ep,
region: 'ap-northeast-1'
}
const route53 = new AWS.Route53(initParam)
let params = {
CallerReference: 'unique_string_affh38h98hasd8f76a',
Name: 'www.example.com'
}
route53.createHostedZone(params, (err, data) => {
if (err) {
console.error(`createHostedZone err: ${err}`)
} else {
console.log(`createHostedZone data: ${JSON.stringify(data)}`)
}
})
I forgot to set region parameter. And the way I view the output of listHostedZones() was wrong. After correcting them, the code works correctly.

Resources