I have an express app that I want to use the connect-form module on to handle some file upload forms. I followed the examples in the express GitHub repo and on the connect-form but when I call req.form.complete(function(){}) I get an error that I Cannot call method 'complete' of undefined. Here is the code that I have:
var express = require('express'),
knox = require('knox'),
form = require('connect-form')/*,
controller = require('controller').Controller*/;
var app = module.exports = express.createServer();
// Configuration
var port = process.env.PORT || 3000;
app.configure(function(){
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(__dirname + '/public'));
form({ keepExtensions: true });
});
app.configure('development', function(){
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function(){
app.use(express.errorHandler());
});
// Routes
app.get('/', function(req, res){
res.render('index', {
title: 'Express'
});
});
app.get('/manage/new', function(req, res){
res.render('manage/new', {
title: 'Create a new widget'
});
});
app.post('/manage/new', function(req, res, next){
if(!req.form){
res.send('Form not submitted.', {'Content-Type': 'text/plain'}, 500);
console.log(req);
}
else{
req.form.complete(function(err, fields, files){
if(err) next(err);
else {
console.log('\nuploaded %s to %s', files.download.filename, files.download.path);
res.redirect('back');
}
});
console.log(req.form);
}
});
app.listen(port);
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
And my Jade template:
h1= title
#wrapper
#main
form(method="post", enctype="multipart/form-data", name="create-widget")
.errors
fieldset
legend Band / Album Information
p
label.tooltip(for="txtTitle", data-tooltip="Band name or title to use on widget.") Band Name
br
input#txtTitle.input_full(name="title", placeholder="The Beatles")
p
label.tooltip(for="txtAlbum", data-tooltip="Album title shown on widget") Album
br
input#txtAlbum.input_full(name="album", placeholder="Magical Mystery Tour")
p
label.tooltip(for="fileDownload", data-tooltip="File to be downloaded after submitting form on Widget") Download
br
input#fileDownload.input_full(name="download", type="file")
#actions
input(type="submit", value="Generate Widget")
Is there something I've done wrong with my code? Also when uploading the download field, I actually want to upload the file straight to an S3 bucket. Do I have access to the stream as it is being uploaded so as to have the upload run faster?
Edit
Here is the contents of the req object when I submit the form:
{ socket:
{ bufferSize: 0,
fd: 6,
type: 'tcp4',
allowHalfOpen: true,
_readWatcher:
{ socket: [Circular],
callback: [Function: onReadable] },
destroyed: false,
readable: true,
_writeQueue: [],
_writeQueueEncoding: [],
_writeQueueFD: [],
_writeQueueCallbacks: [],
_writeWatcher:
{ socket: [Circular],
callback: [Function: onWritable] },
writable: true,
_writeImpl: [Function],
_readImpl: [Function],
_shutdownImpl: [Function],
remoteAddress: '127.0.0.1',
remotePort: 51588,
server:
{ stack: [Object],
connections: 1,
allowHalfOpen: true,
watcher: [Object],
_events: [Object],
httpAllowHalfOpen: false,
cache: {},
settings: [Object],
redirects: {},
isCallbacks: {},
_locals: [Object],
dynamicViewHelpers: {},
errorHandlers: [],
route: '/',
routes: [Object],
router: [Getter],
__usedRouter: true,
type: 'tcp4',
fd: 7 },
ondrain: [Function],
_idleTimeout: 120000,
_idleNext:
{ repeat: 120,
_idleNext: [Circular],
_idlePrev: [Circular],
callback: [Function] },
_idlePrev:
{ repeat: 120,
_idleNext: [Circular],
_idlePrev: [Circular],
callback: [Function] },
_idleStart: Fri, 21 Oct 2011 16:08:07 GMT,
_events:
{ timeout: [Function],
error: [Function],
close: [Function] },
ondata: [Function],
onend: [Function],
_httpMessage: null },
connection:
{ bufferSize: 0,
fd: 6,
type: 'tcp4',
allowHalfOpen: true,
_readWatcher:
{ socket: [Circular],
callback: [Function: onReadable] },
destroyed: false,
readable: true,
_writeQueue: [],
_writeQueueEncoding: [],
_writeQueueFD: [],
_writeQueueCallbacks: [],
_writeWatcher:
{ socket: [Circular],
callback: [Function: onWritable] },
writable: true,
_writeImpl: [Function],
_readImpl: [Function],
_shutdownImpl: [Function],
remoteAddress: '127.0.0.1',
remotePort: 51588,
server:
{ stack: [Object],
connections: 1,
allowHalfOpen: true,
watcher: [Object],
_events: [Object],
httpAllowHalfOpen: false,
cache: {},
settings: [Object],
redirects: {},
isCallbacks: {},
_locals: [Object],
dynamicViewHelpers: {},
errorHandlers: [],
route: '/',
routes: [Object],
router: [Getter],
__usedRouter: true,
type: 'tcp4',
fd: 7 },
ondrain: [Function],
_idleTimeout: 120000,
_idleNext:
{ repeat: 120,
_idleNext: [Circular],
_idlePrev: [Circular],
callback: [Function] },
_idlePrev:
{ repeat: 120,
_idleNext: [Circular],
_idlePrev: [Circular],
callback: [Function] },
_idleStart: Fri, 21 Oct 2011 16:08:07 GMT,
_events:
{ timeout: [Function],
error: [Function],
close: [Function] },
ondata: [Function],
onend: [Function],
_httpMessage: null },
httpVersion: '1.1',
complete: false,
headers:
{ host: '127.0.0.1:5000',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:6.0) Gecko/20100101 Firefox/6.0',
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'accept-language': 'en-us,en;q=0.5',
'accept-encoding': 'gzip, deflate',
'accept-charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
connection: 'keep-alive',
referer: 'http://127.0.0.1:5000/manage/new',
'content-type': 'multipart/form-data; boundary=---------------------------20072377098235644401115438165',
'content-length': '247075' },
trailers: {},
readable: true,
url: '/manage/new',
method: 'POST',
statusCode: null,
client:
{ bufferSize: 0,
fd: 6,
type: 'tcp4',
allowHalfOpen: true,
_readWatcher:
{ socket: [Circular],
callback: [Function: onReadable] },
destroyed: false,
readable: true,
_writeQueue: [],
_writeQueueEncoding: [],
_writeQueueFD: [],
_writeQueueCallbacks: [],
_writeWatcher:
{ socket: [Circular],
callback: [Function: onWritable] },
writable: true,
_writeImpl: [Function],
_readImpl: [Function],
_shutdownImpl: [Function],
remoteAddress: '127.0.0.1',
remotePort: 51588,
server:
{ stack: [Object],
connections: 1,
allowHalfOpen: true,
watcher: [Object],
_events: [Object],
httpAllowHalfOpen: false,
cache: {},
settings: [Object],
redirects: {},
isCallbacks: {},
_locals: [Object],
dynamicViewHelpers: {},
errorHandlers: [],
route: '/',
routes: [Object],
router: [Getter],
__usedRouter: true,
type: 'tcp4',
fd: 7 },
ondrain: [Function],
_idleTimeout: 120000,
_idleNext:
{ repeat: 120,
_idleNext: [Circular],
_idlePrev: [Circular],
callback: [Function] },
_idlePrev:
{ repeat: 120,
_idleNext: [Circular],
_idlePrev: [Circular],
callback: [Function] },
_idleStart: Fri, 21 Oct 2011 16:08:07 GMT,
_events:
{ timeout: [Function],
error: [Function],
close: [Function] },
ondata: [Function],
onend: [Function],
_httpMessage: null },
httpVersionMajor: 1,
httpVersionMinor: 1,
upgrade: false,
originalUrl: '/manage/new',
query: {},
app:
{ stack:
[ [Object],
[Object],
[Object],
[Object],
[Object],
[Object] ],
connections: 1,
allowHalfOpen: true,
watcher: { host: [Circular], callback: [Function] },
_events:
{ request: [Function],
connection: [Function: connectionListener],
listening: [Function] },
httpAllowHalfOpen: false,
cache: {},
settings:
{ env: 'development',
hints: true,
views: '/Users/davejlong/Workspace/Node/Widget/views',
'view engine': 'jade' },
redirects: {},
isCallbacks: {},
_locals: { settings: [Object], app: [Circular] },
dynamicViewHelpers: {},
errorHandlers: [],
route: '/',
routes:
{ app: [Circular],
routes: [Object],
params: {},
_params: [],
middleware: [Function] },
router: [Getter],
__usedRouter: true,
type: 'tcp4',
fd: 7 },
res:
{ output: [],
outputEncodings: [],
writable: true,
_last: false,
chunkedEncoding: false,
shouldKeepAlive: true,
useChunkedEncodingByDefault: true,
_hasBody: true,
_trailer: '',
finished: true,
socket: null,
connection: null,
_events: { finish: [Function] },
_headers:
{ 'x-powered-by': 'Express',
'content-type': 'text/plain; charset=utf-8',
'content-length': 19 },
_headerNames:
{ 'x-powered-by': 'X-Powered-By',
'content-type': 'Content-Type',
'content-length': 'Content-Length' },
app:
{ stack: [Object],
connections: 1,
allowHalfOpen: true,
watcher: [Object],
_events: [Object],
httpAllowHalfOpen: false,
cache: {},
settings: [Object],
redirects: {},
isCallbacks: {},
_locals: [Object],
dynamicViewHelpers: {},
errorHandlers: [],
route: '/',
routes: [Object],
router: [Getter],
__usedRouter: true,
type: 'tcp4',
fd: 7 },
req: [Circular],
charset: 'utf-8',
statusCode: 500,
_header: 'HTTP/1.1 500 Internal Server Error\r\nX-Powered-By: Express\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 19\r\nConnection: keep-alive\r\n\r\n',
_headerSent: true },
next: [Function: next],
originalMethod: 'POST',
_route_index: 0,
route:
{ path: '/manage/new',
method: 'post',
callbacks: [ [Function] ],
keys: [],
regexp: /^\/manage\/new\/?$/i,
params: [] },
params: [] }
Guess the form is not submitted from brower side, see example provided on http://visionmedia.github.com/connect-form/
var form = require('connect-form');
var server = connect.createServer(
form({ keepExtensions: true }),
function(req, res, next){
// Form was submitted
if (req.form) {
// Do something when parsing is finished
// and respond, or respond immediately
// and work with the files.
req.form.onComplete = function(err, fields, files){
res.writeHead(200, {});
if (err) res.write(JSON.stringify(err.message));
res.write(JSON.stringify(fields));
res.write(JSON.stringify(files));
res.end();
};
// Regular request, pass to next middleware
} else {
next();
}
}
);
"if (req.form)" in the example makes sure that form is indeed submitted ! You ought to do that as well as you may not be able to control what is getting posted to your APIs !!
I'm not sure why connect-form didn't work, but I found that it was written on top of the Formidable module, so I took that and just used that in my router instead and it properly picked up the form:
var sys = require('sys'),
express = require('express'),
knox = require('knox'),
formidable = require('formidable'),
controller = require('./controller').Controller;
var app = module.exports = express.createServer();
/* ================================================================ */
/* Configuration */
/* ================================================================ */
var port = process.env.PORT || 3000;
var hostName = 'http://download.comeandlive.com';
controller.setup(hostName, 80);
app.configure(function(){
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(__dirname + '/public'));
});
app.configure('development', function(){
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function(){
app.use(express.errorHandler());
});
/* ================================================================ */
/* Routes */
/* ================================================================ */
app.get('/', function(req, res){
res.redirect('http://comeandlive.com', 301);
});
/* ================================ */
/* Administration Routes */
/* ================================ */
app.get('/manage', function(req, res){
res.render('manage/index', {
title: 'Widget Administration'
});
})
app.get('/manage/new', function(req, res){
res.render('manage/new', {
title: 'Create a new widget'
});
});
app.post('/manage/new', function(req, res, next){
var form = new formidable.IncomingForm();
form.parse(req, function(err, fields, files){
res.header('Content-Type', 'text/plain');
res.send('Received upload:\n\n' + sys.inspect({fields:fields, files:files}));
});
});
app.listen(port);
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
Related
I have a nodejs app utilising express using #okta/okta-sdk-nodejs and #okta/oidc-middleware to handle authentication.
I have a number of routes that work fine and are authorised as expected. The following flow generates a 401 status code and I am struggling to work out why.
If I hit the route http://localhost:3000/b/f-e-info i get a response from an external API, this works, I then want to send this to another route /es/ingest/b/ts to get ingested I do this via a function callEs('/es/ingest/b/ts',t.symbols) that uses axios this basically accepts a URL and the response data as parameters and posts the data to the es route router.post('/ingest/b/ts', esParsersController.createTsDocs);. The route utilise the createTsDocs function as a call back which just takes care of ingesting the data into a database.
The error in the nodejs console:
POST /es/ingest/b/t 401 0.520 ms - 12
Error: Request failed with status code 401
at createError (login-portal/node_modules/axios/lib/core/createError.js:16:15)
at settle (login-portal/node_modules/axios/lib/core/settle.js:17:12)
at IncomingMessage.handleStreamEnd (login-portal/node_modules/axios/lib/adapters/http.js:260:11)
at IncomingMessage.emit (events.js:326:22)
at endReadableNT (_stream_readable.js:1252:12)
at processTicksAndRejections (internal/process/task_queues.js:80:21) {
config: {
url: '/es/ingest/b/ts',
method: 'post',
data: '{"data":[{..},{...},{...}]}',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json;charset=utf-8',
'User-Agent': 'axios/0.21.1',
'Content-Length': 113195
},
baseURL: 'http://localhost:3000',
transformRequest: [ [Function: transformRequest] ],
transformResponse: [ [Function: transformResponse] ],
timeout: 3000,
adapter: [Function: httpAdapter],
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
validateStatus: [Function: validateStatus]
},
request: <ref *1> ClientRequest {
_events: [Object: null prototype] {
socket: [Function (anonymous)],
abort: [Function (anonymous)],
aborted: [Function (anonymous)],
connect: [Function (anonymous)],
error: [Function (anonymous)],
timeout: [Function (anonymous)],
prefinish: [Function: requestOnPrefinish]
},
_eventsCount: 7,
_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: null,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket: Socket {
connecting: false,
_hadError: false,
_parent: null,
_host: 'localhost',
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 7,
_maxListeners: undefined,
_writableState: [WritableState],
allowHalfOpen: false,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: null,
_server: null,
parser: null,
_httpMessage: [Circular *1],
[Symbol(async_id_symbol)]: 744,
[Symbol(kHandle)]: [TCP],
[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,
[Symbol(RequestTimeout)]: undefined
},
_header: 'POST /es/ingest/b/ts HTTP/1.1\r\n' +
'Accept: application/json, text/plain, */*\r\n' +
'Content-Type: application/json;charset=utf-8\r\n' +
'User-Agent: axios/0.21.1\r\n' +
'Content-Length: 113195\r\n' +
'Host: localhost:3000\r\n' +
'Connection: close\r\n' +
'\r\n',
_keepAliveTimeout: 0,
_onPendingData: [Function: noopPendingOutput],
agent: Agent {
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
defaultPort: 80,
protocol: 'http:',
options: [Object],
requests: {},
sockets: [Object],
freeSockets: {},
keepAliveMsecs: 1000,
keepAlive: false,
maxSockets: Infinity,
maxFreeSockets: 256,
scheduling: 'fifo',
maxTotalSockets: Infinity,
totalSocketCount: 1,
[Symbol(kCapture)]: false
},
socketPath: undefined,
method: 'POST',
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
path: '/es/ingest/b/ts',
_ended: true,
res: IncomingMessage {
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 3,
_maxListeners: undefined,
socket: [Socket],
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: true,
headers: [Object],
rawHeaders: [Array],
trailers: {},
rawTrailers: [],
aborted: false,
upgrade: false,
url: '',
method: null,
statusCode: 401,
statusMessage: 'Unauthorized',
client: [Socket],
_consuming: true,
_dumped: false,
req: [Circular *1],
responseUrl: 'http://localhost:3000/es/ingest/b/ts',
redirects: [],
[Symbol(kCapture)]: false,
[Symbol(RequestTimeout)]: undefined
},
aborted: false,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
reusedSocket: false,
host: 'localhost',
protocol: 'http:',
_redirectable: Writable {
_writableState: [WritableState],
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
_options: [Object],
_ended: true,
_ending: true,
_redirectCount: 0,
_redirects: [],
_requestBodyLength: 113195,
_requestBodyBuffers: [],
_onNativeResponse: [Function (anonymous)],
_currentRequest: [Circular *1],
_currentUrl: 'http://localhost:3000/es/ingest/b/ts',
_timeout: Timeout {
_idleTimeout: -1,
_idlePrev: null,
_idleNext: null,
_idleStart: 2235827,
_onTimeout: null,
_timerArgs: undefined,
_repeat: null,
_destroyed: true,
[Symbol(refed)]: true,
[Symbol(kHasPrimitive)]: false,
[Symbol(asyncId)]: 750,
[Symbol(triggerId)]: 746
},
[Symbol(kCapture)]: false
},
[Symbol(kCapture)]: false,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype] {
accept: [Array],
'content-type': [Array],
'user-agent': [Array],
'content-length': [Array],
host: [Array]
}
},
response: {
status: 401,
statusText: 'Unauthorized',
headers: {
'x-powered-by': 'Express',
'content-type': 'text/plain; charset=utf-8',
'content-length': '12',
etag: 'W/"c-dAuDFQrdjS3hezqxDTNgW7AOlYk"',
'set-cookie': [Array],
date: 'Wed, 17 Mar 2021 09:32:20 GMT',
connection: 'close'
},
config: {
url: '/es/ingest/b/t',
method: 'post',
data: '{"data":[{...},{...},{...}]}',
headers: [Object],
baseURL: 'http://localhost:3000',
transformRequest: [Array],
transformResponse: [Array],
timeout: 3000,
adapter: [Function: httpAdapter],
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
validateStatus: [Function: validateStatus]
},
request: <ref *1> ClientRequest {
_events: [Object: null prototype],
_eventsCount: 7,
_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: null,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket: [Socket],
_header: 'POST /es/ingest/b/ts HTTP/1.1\r\n' +
'Accept: application/json, text/plain, */*\r\n' +
'Content-Type: application/json;charset=utf-8\r\n' +
'User-Agent: axios/0.21.1\r\n' +
'Content-Length: 113195\r\n' +
'Host: localhost:3000\r\n' +
'Connection: close\r\n' +
'\r\n',
_keepAliveTimeout: 0,
_onPendingData: [Function: noopPendingOutput],
agent: [Agent],
socketPath: undefined,
method: 'POST',
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
path: '/es/ingest/b/ts',
_ended: true,
res: [IncomingMessage],
aborted: false,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
reusedSocket: false,
host: 'localhost',
protocol: 'http:',
_redirectable: [Writable],
[Symbol(kCapture)]: false,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype]
},
data: 'Unauthorized'
},
isAxiosError: true,
toJSON: [Function: toJSON]
}
If I just hit a GET Route in the es file It is authenticated as expected.
app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var session = require('express-session');
var okta = require("#okta/okta-sdk-nodejs");
const { ExpressOIDC } = require('#okta/oidc-middleware');
const keys = require('./config/keys');
var bodyParser = require('body-parser')
var app = express();
app.use( bodyParser.json({limit: "15360mb", type:'application/json'}) );
app.use(bodyParser.urlencoded({limit: '100mb', extended: true}));
// Enabled the routes
const dashboardRouter = require("./routes/dashboard");
const usersRouter = require("./routes/users");
const bRouter = require("./routes/b");
const esRouter = require("./routes/es");
var oktaClient = new okta.Client({
orgUrl: keys.okta_orgUrl,
token: keys.okta_token
});
const oidc = new ExpressOIDC({
issuer: keys.okta_issuer,
client_id: keys.okta_client_id,
client_secret: keys.okta_client_secret,
appBaseUrl: keys.okta_appBaseUrl,
scope: keys.okta_scope,
routes: {
login: {
path: keys.okta_routes_login_path
},
loginCallback: {
path: keys.okta_routes_loginCallback_path,
afterCallback: keys.okta_routes_loginCallback_afterCallback
}
}
});
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({
secret:keys.app_session_secret,
resave: true,
saveUnititialized: false
}));
app.use(oidc.router);
app.use((req, res, next) => {
if (!req.userContext) {
return next();
}
oktaClient.getUser(req.userContext.userinfo.sub)
.then(user => {
req.user = user;
res.locals.user = user;
next();
}).catch(err => {
next(err);
});
});
// redirect our users to the correct route
app.use('/', publicRouter);
app.use('/dashboard', oidc.ensureAuthenticated(), dashboardRouter);
app.use('/users', oidc.ensureAuthenticated(), usersRouter);
app.use('/b', oidc.ensureAuthenticated(), bRouter)
app.use('/es', oidc.ensureAuthenticated(), esRouter)
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
oidc.on('ready', () => {
app.listen(keys.app_web_server_port, () => console.log('app started'));
});
oidc.on('error', err => {
// An error occurred while setting up OIDC, during token revocation, or during post-logout handling
});
module.exports = app;
b.js route
const axios = require('axios');
const express = require("express");
const b = require('../models/b');
const keys = require('../config/keys');
const router = express.Router();
const esapi = axios.create({
baseURL: keys.app_web_server_addr+':'+keys.app_web_server_port,
timeout: 3000,
});
// function to call es
let callEs = (url, data) => {
esapi.post(
url,
{data})
.catch( err => console.log(err))
}
router.get("/f-e-info", (req, res) => {
fapi.get(b.bfapi+'eInfo')
.then((response) => {
// handle success
//console.log(response.data.symbols);
res.render("t",{response});
return response.data;
// send the data to es
}).then((t) => {
console.log("sending to es")
callEs('/es/ingest/b/ts',t.symbols)
}).catch( (error) => console.log(error));
});
module.exports = router;
es Route
const esParsersController = require('../controllers/esParsers');
const express = require("express");
const router = express.Router();
// This works fine!!!
router.get("/", (req, res) => {
res.render("es-test");
});
// This fails with a 401 unauthorised.
router.post('/ingest/b/ts', esParsersController.createTsDocs);
module.exports = router;
/controllers/esParsers
const keys = require('../config/keys');
const crypto = require("crypto");
const { createReadStream } = require('fs')
const split = require('split2')
const { Client } = require('#elastic/elasticsearch');
const { disconnect } = require('process');
require('array.prototype.flatmap').shim();
const createTsDocs = (req,res) => {
var datasource = []
req.body.data.forEach(function(value){
var doc = {}
doc.symbol = value.symbol;
// ... do stuff with data
datasource.push(doc)
});
ingestDocIntoEs(`${keys.esIndexName_prefix}ts`,datasource);
res.send("data entered")
}
module.exports = {
createTickerDocs
}
Sorry new to nodejs and trying to learn, can someone help me to understand why a post to the es route /ingest/b/ts gives me a 401 but a GET request to the es route / is authenticated as expected?
I was able to resolved this, forgot to come back an provide and answer. The issue was that I was making a post request with axios which was a new client instance so it did not have any scope of the current request headers. I had to get the current headers from the request and append them to my axios post request.
let config = {
headers : {
cookie: req.headers.cookie
}
}
esapi.post(url,data,config)
The only important part of the cookie is the connect.sid=COOKIE_GOES_HERE'
Another example using curl:
curl -X POST http://localhost:3000/route \
-H 'Content-Type: application/json' \
-H 'Cookie: connect.sid=COOKIE_GOES_HERE' \
-d '{"text": "hello again!", "toUserId": "USER_ID_COPIED_FROM_OKTA_DASHBOARD_URL"}'
UPDATE 1:
I've got the problem down to this line here:
var rooms = Object.keys(socket.adapter.rooms);
Even though socket.adapter.rooms is not empty this returns an empty value.
The debug shows the following:
console.log('socket:connection/disconnecting socket.rooms', socket.rooms );
console.log('socket:connection/disconnecting socket.adapter.rooms', socket.adapter.rooms );
socket:connection/disconnecting socket.rooms Set { 'LBsXUemtWx9U8uV5AAAB', 'Test' }
socket:connection/disconnecting socket.adapter.rooms Map {
'LBsXUemtWx9U8uV5AAAB' => Set { 'LBsXUemtWx9U8uV5AAAB' },
'Test' => Set { 'LBsXUemtWx9U8uV5AAAB' } }
So the values are present but obviosly Object.keys does not like the JSON. I also tried Object.getOwnPropertyNames but that was the same.
Original question:
I'm cobbling together a basic chat app with rooms, which I will expand for another purpose.
Basic joining and chatting work fine, but I have an issue when the browser is closed or refreshed causing the Node server process to crash because in the disconnect event socket.rooms in empty.
Details: Node.js version v10.10.00 on Ubuntu 20.04, Socket v4.0.1
app.js is the main script the entry point script for the server:
const express = require('express');
const bodyParser = require('body-parser');
const socket = require('socket.io');
const app = express();
app.use(bodyParser.urlencoded({extended: false}));
app.use(express.static('public'));
app.set('view engine', 'ejs');
var port = process.env.PORT || 3000;
//Render Index Class Page
app.get('/', (req, res) => {
console.log(`app.js:get/`);
res.render('index');
})
//Render Start Class Page
app.get('/start', (req, res) => {
console.log(`app.js:get/`);
res.render('host_start');
})
//Render Join Class Page
app.get('/join', (req, res) => {
console.log(`app.js:get/`);
res.render('host_join');
})
//Get username and roomname from form and pass it to room
app.post('/room', (req, res) => {
roomname = req.body.roomname;
username = req.body.username;
console.log(`User ${username} entering room ${roomname}`);
res.redirect(`/room?username=${username}&roomname=${roomname}`);
})
//Rooms
app.get('/room', (req, res)=>{
console.log(`app.js:get/room`);
res.render('room');
})
//Start Server
const server = app.listen(port, () => {
console.log(`Server Running on ${port}`);
})
const io = socket(server);
require('./utils/socket')(io);
The controller is socket.js which throws an error when the browser closes or is refreshed:
/mnt/Workspace/nodejs/nexus/utils/socket.js:62
users[roomname].forEach((user, index) => {
^
TypeError: Cannot read property 'forEach' of undefined
at Socket.socket.on (/mnt/Workspace/nodejs/nexus/utils/socket.js:62:29)
at Socket.emit (events.js:198:13)
at Socket.emitReserved (/mnt/Workspace/nodejs/nexus/node_modules/socket.io/dist/typed-events.js:56:22)
at Socket._onclose (/mnt/Workspace/nodejs/nexus/node_modules/socket.io/dist/socket.js:333:14)
at Client.onclose (/mnt/Workspace/nodejs/nexus/node_modules/socket.io/dist/client.js:245:20)
at Socket.emit (events.js:203:15)
at Socket.onClose (/mnt/Workspace/nodejs/nexus/node_modules/engine.io/lib/socket.js:348:12)
at Object.onceWrapper (events.js:286:20)
at WebSocket.emit (events.js:198:13)
at WebSocket.onClose (/mnt/Workspace/nodejs/nexus/node_modules/engine.io/lib/transport.js:106:10)
root#ubun-nodejs#
socket.js is below. The error occurs in the socket.on('disconnecting' block. But when I print the content of socket to the log I can see the socket.rooms is present:
socket:connection/disconnecting: socket Socket {
_events:
[Object: null prototype] {
'joined-user': [Function],
chat: [Function],
typing: [Function],
disconnecting: [Function] },
_eventsCount: 4,
_maxListeners: undefined,
nsp:
Namespace {
_events: [Object: null prototype] { connection: [Function] },
_eventsCount: 1,
_maxListeners: undefined,
sockets:
Map {
'LxZu0PURyMuqS_DIAAAB' => [Circular],
'hJqrx3Blp2q96KVIAAAD' => [Socket] },
_fns: [],
_ids: 0,
server:
Server {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
_nsps: [Map],
parentNsps: Map {},
_path: '/socket.io',
clientPathRegex:
/^\/socket\.io\/socket\.io(\.min|\.msgpack\.min)?\.js(\.map)?$/,
_connectTimeout: 45000,
_serveClient: true,
_parser: [Object],
encoder: Encoder {},
_adapter: [Function: Adapter],
sockets: [Circular],
opts: {},
eio: [Server],
httpServer: [Server],
engine: [Server] },
name: '/',
adapter:
Adapter {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
nsp: [Circular],
rooms: [Map],
sids: [Map],
encoder: Encoder {} } },
client:
Client {
sockets: Map { 'LxZu0PURyMuqS_DIAAAB' => [Circular] },
nsps: Map { '/' => [Circular] },
server:
Server {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
_nsps: [Map],
parentNsps: Map {},
_path: '/socket.io',
clientPathRegex:
/^\/socket\.io\/socket\.io(\.min|\.msgpack\.min)?\.js(\.map)?$/,
_connectTimeout: 45000,
_serveClient: true,
_parser: [Object],
encoder: Encoder {},
_adapter: [Function: Adapter],
sockets: [Namespace],
opts: {},
eio: [Server],
httpServer: [Server],
engine: [Server] },
conn:
Socket {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
id: '_XlX-xlIKIO_ryBNAAAA',
server: [Server],
upgrading: false,
upgraded: true,
readyState: 'closed',
writeBuffer: [],
packetsFn: [],
sentCallbackFn: [],
cleanupFn: [],
request: [IncomingMessage],
protocol: 4,
remoteAddress: '::ffff:10.0.0.82',
checkIntervalTimer: null,
upgradeTimeoutTimer: null,
pingTimeoutTimer:
Timeout {
_called: false,
_idleTimeout: -1,
_idlePrev: null,
_idleNext: null,
_idleStart: 3731,
_onTimeout: null,
_timerArgs: undefined,
_repeat: null,
_destroyed: false,
[Symbol(unrefed)]: false,
[Symbol(asyncId)]: 104,
[Symbol(triggerId)]: 103 },
pingIntervalTimer:
Timeout {
_called: false,
_idleTimeout: -1,
_idlePrev: null,
_idleNext: null,
_idleStart: 3719,
_onTimeout: null,
_timerArgs: undefined,
_repeat: null,
_destroyed: false,
[Symbol(unrefed)]: false,
[Symbol(asyncId)]: 63,
[Symbol(triggerId)]: 0 },
transport: [WebSocket] },
encoder: Encoder {},
decoder: Decoder { _callbacks: {} },
id: '_XlX-xlIKIO_ryBNAAAA',
onclose: [Function: bound onclose],
ondata: [Function: bound ondata],
onerror: [Function: bound onerror],
ondecoded: [Function: bound ondecoded],
connectTimeout: undefined },
data: {},
acks: Map {},
fns: [],
flags: {},
server:
Server {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
_nsps: Map { '/' => [Namespace] },
parentNsps: Map {},
_path: '/socket.io',
clientPathRegex:
/^\/socket\.io\/socket\.io(\.min|\.msgpack\.min)?\.js(\.map)?$/,
_connectTimeout: 45000,
_serveClient: true,
_parser:
{ protocol: 5,
PacketType: [Object],
Encoder: [Function: Encoder],
Decoder: [Function: Decoder] },
encoder: Encoder {},
_adapter: [Function: Adapter],
sockets:
Namespace {
_events: [Object],
_eventsCount: 1,
_maxListeners: undefined,
sockets: [Map],
_fns: [],
_ids: 0,
server: [Circular],
name: '/',
adapter: [Adapter] },
opts: {},
eio:
Server {
_events: [Object],
_eventsCount: 1,
_maxListeners: undefined,
clients: [Object],
clientsCount: 1,
opts: [Object],
ws: [WebSocketServer] },
httpServer:
Server {
insecureHTTPParser: undefined,
_events: [Object],
_eventsCount: 5,
_maxListeners: undefined,
_connections: 5,
_handle: [TCP],
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
httpAllowHalfOpen: false,
timeout: 120000,
keepAliveTimeout: 5000,
maxHeadersCount: null,
headersTimeout: 40000,
_connectionKey: '6::::3000',
[Symbol(IncomingMessage)]: [Function],
[Symbol(ServerResponse)]: [Function],
[Symbol(asyncId)]: 5 },
engine:
Server {
_events: [Object],
_eventsCount: 1,
_maxListeners: undefined,
clients: [Object],
clientsCount: 1,
opts: [Object],
ws: [WebSocketServer] } },
adapter:
Adapter {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
nsp:
Namespace {
_events: [Object],
_eventsCount: 1,
_maxListeners: undefined,
sockets: [Map],
_fns: [],
_ids: 0,
server: [Server],
name: '/',
adapter: [Circular] },
rooms:
Map {
'LxZu0PURyMuqS_DIAAAB' => [Set],
'Test' => [Set],
'hJqrx3Blp2q96KVIAAAD' => [Set] },
sids:
Map {
'LxZu0PURyMuqS_DIAAAB' => [Set],
'hJqrx3Blp2q96KVIAAAD' => [Set] },
encoder: Encoder {} },
id: 'LxZu0PURyMuqS_DIAAAB',
connected: true,
disconnected: false,
handshake:
{ headers:
{ host: '10.0.0.44:3000',
connection: 'keep-alive',
accept: '*/*',
'user-agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36',
referer:
'http://10.0.0.44:3000/room?username=Mr%20Chipps&roomname=Test',
'accept-encoding': 'gzip, deflate',
'accept-language': 'en-US,en;q=0.9' },
time: 'Sun Apr 25 2021 16:13:57 GMT+0100 (British Summer Time)',
address: '::ffff:10.0.0.82',
xdomain: false,
secure: false,
issued: 1619363637442,
url: '/socket.io/?EIO=4&transport=polling&t=Na9c2rA',
query:
[Object: null prototype] { EIO: '4', transport: 'polling', t: 'Na9c2rA' },
auth: {} } }
socket.js:
const {getUsers, users} = require('./getUsers');
//Socket connection
function socket(io) {
io.on('connection', (socket) => {
socket.on('joined-user', (data) =>{
console.log('socket:connection/joined-user ' + data.username + ' room ' + data.roomname);
//Storing users connected in a room in memory
var user = {};
user[socket.id] = data.username;
if(users[data.roomname]){
users[data.roomname].push(user);
}
else{
users[data.roomname] = [user];
}
//Joining the Socket Room
socket.join(data.roomname);
//Emitting New Username to Clients
io.to(data.roomname).emit('joined-user', {username: data.username});
//Send online users array
io.to(data.roomname).emit('online-users', getUsers(users[data.roomname]));
});
//Emitting messages to Clients
socket.on('chat', (data) =>{
console.log('socket:connection/chat ' + data.username + ' room ' + data.roomname + ': ' + data.message);
io.to(data.roomname).emit('chat', {username: data.username, message: data.message});
});
//Broadcasting the user who is typing
socket.on('typing', (data) => {
console.log('socket:connection/typing ' + data.username );
socket.broadcast.to(data.roomname).emit('typing', data.username);
});
//Remove user from memory when they disconnect
// socket.on('disconnecting', ()=>{
socket.on('disconnecting', (data)=>{
console.log('socket:connection/disconnecting' );
var rooms = Object.keys(socket.adapter.rooms);
// var socketId = rooms[0];
// var roomname = rooms[1];
var socketId = data.socketid;
var roomname = data.roomname;
console.log('socket:connection/disconnecting: socket ', socket );
console.log('socket:connection/disconnecting: rooms ', rooms );
console.log('socket:connection/disconnecting: users ', users );
console.log('socket:connection/disconnecting: data ', data );
//console.log('socket:connection/disconnecting: clients ', clients );
users[roomname].forEach((user, index) => { // !!! Errors here with undefined !!!
if(user[socketId]){
users[roomname].splice(index, 1);
}
});
//Send online users array
io.to(roomname).emit('online-users', getUsers(users[roomname]));
})
})
}
module.exports = socket;
getUsers.js:
//Store connected Users
var users = {};
//Funtion to get users online in a room
function getUsers(arr){
console.log('getUsers ');
onlineUsers = [];
arr.forEach((onlineUser) => {
onlineUsers.push(Object.values(onlineUser)[0])
});
return onlineUsers;
}
module.exports = {getUsers, users};
Answer was to replace this:
var rooms = Object.keys(socket.adapter.rooms);
With:
var rooms = Array.from(socket.rooms);
I have the following code listing the files in the folder. Then trying to send them to the post/upload in the express backend. But the files is always undefined. However it seems like size and other info get through.
Client send
const axios = require('axios');
var FormData = require('form-data');
//requiring path and fs modules
const path = require('path');
const fs = require('fs');
//joining path of directory
const directoryPath = path.join(__dirname, '');
//passsing directoryPath and callback function
fs.readdir(directoryPath, function (err, files) {
//handling error
if (err) {
return console.log('Unable to scan directory: ' + err);
}
//listing all files using forEach
var postInfo = files.forEach(async function (file) {
const form_data = new FormData();
form_data.append('file', fs.createReadStream(file));
const request_config = {
method: "post",
url: 'http://localhost:8080/upload',
port: 8080,
headers: {
"Content-Type": "multipart/form-data; boundary=form_data._boundary"
},
data: form_data
};
try {
var req = await axios(request_config);
// Do whatever you want to do with the file
console.log("Request: ", req);
} catch (e) {
console.error(e);
}
});
console.log(postInfo);
});
receiving code in the backend
const http = require('http')
const port = 8080
const express = require('express');
const app = express();
const multer = require('multer');
const server = http.createServer(app)
let storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './uploads')
},
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now())
}
});
const upload = multer({ storage }).single('file');
app.post('/upload', upload, (req, res) => {
console.log(req.files) // this does log the uploaded image data.
})
// bind the server on port
server.listen(port, (err) => {
if (err) {
return console.log('something bad happened', err)
}
console.log(`server is listening on ${port}`)
})
Log:
node file-receive.js
server is listening on 8080
undefined
undefined
undefined
response: undefined,
isAxiosError: true,
toJSON: [Function] }
{ Error: socket hang up
at createHangUpError (_http_client.js:323:15)
at Socket.socketOnEnd (_http_client.js:426:23)
at Socket.emit (events.js:194:15)
at endReadableNT (_stream_readable.js:1103:12)
at process._tickCallback (internal/process/next_tick.js:63:19)
code: 'ECONNRESET',
config:
{ url: 'http://localhost:8080/upload',
method: 'post',
data:
FormData {
_overheadLength: 169,
_valueLength: 0,
_valuesToMeasure: [Array],
writable: false,
readable: true,
dataSize: 0,
maxDataSize: 2097152,
pauseStreams: true,
_released: true,
_streams: [],
_currentStream: null,
_insideLoop: false,
_pendingNext: false,
_boundary: '--------------------------046964089061111513973474',
_events: [Object],
_eventsCount: 1 },
headers:
{ Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'axios/0.19.2' },
transformRequest: [ [Function: transformRequest] ],
transformResponse: [ [Function: transformResponse] ],
timeout: 0,
adapter: [Function: httpAdapter],
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: [Function: validateStatus],
port: 8080 },
request:
Writable {
_writableState:
WritableState {
objectMode: false,
highWaterMark: 16384,
finalCalled: false,
needDrain: false,
ending: false,
ended: false,
finished: false,
destroyed: false,
decodeStrings: true,
defaultEncoding: 'utf8',
length: 0,
writing: false,
corked: 0,
sync: true,
bufferProcessing: false,
onwrite: [Function: bound onwrite],
writecb: null,
writelen: 0,
bufferedRequest: null,
lastBufferedRequest: null,
pendingcb: 0,
prefinished: false,
errorEmitted: false,
emitClose: true,
bufferedRequestCount: 0,
corkedRequestsFree: [Object] },
writable: true,
_events:
[Object: null prototype] {
response: [Function: handleResponse],
error: [Function: handleRequestError] },
_eventsCount: 2,
_maxListeners: undefined,
_options:
{ protocol: 'http:',
maxRedirects: 21,
maxBodyLength: 10485760,
path: '/upload',
method: 'POST',
headers: [Object],
agent: undefined,
agents: [Object],
auth: undefined,
hostname: 'localhost',
port: '8080',
nativeProtocols: [Object],
pathname: '/upload' },
_redirectCount: 0,
_redirects: [],
_requestBodyLength: 983,
_requestBodyBuffers: [ [Object], [Object], [Object] ],
_onNativeResponse: [Function],
_currentRequest:
ClientRequest {
_events: [Object],
_eventsCount: 6,
_maxListeners: undefined,
output: [],
outputEncodings: [],
outputCallbacks: [],
outputSize: 0,
writable: true,
_last: true,
chunkedEncoding: true,
shouldKeepAlive: false,
useChunkedEncodingByDefault: true,
sendDate: false,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
_contentLength: null,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket: [Socket],
connection: [Socket],
_header:
'POST /upload HTTP/1.1\r\nAccept: application/json, text/plain, */*\r\nContent-Type: application/x-www-form-urlencoded\r\nUser-Agent: axios/0.19.2\r\nHost: localhost:8080\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n',
_onPendingData: [Function: noopPendingOutput],
agent: [Agent],
socketPath: undefined,
timeout: undefined,
method: 'POST',
path: '/upload',
_ended: false,
res: null,
aborted: undefined,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
_redirectable: [Circular],
[Symbol(isCorked)]: false,
[Symbol(outHeadersKey)]: [Object] },
_currentUrl: 'http://localhost:8080/upload' },
response: undefined,
isAxiosError: true,
toJSON: [Function] }
I was able to reproduce the issue and was able to fix the code with the following ajdustments (note that I'm uploading a single file for sake of simplicty).
On the client side you basically just provide the form-data to axios and call getHeaders() to get the already set up header - also you need to adjust the file-name in the form.
// client.js
...
const form_data = new FormData();
form_data.append('file', fs.createReadStream(__dirname + '/file-you-want-to-upload'));
const request_config = {
method: "post",
url: 'http://localhost:8080/upload',
port: 8080,
data: form_data,
headers: form_data.getHeaders()
};
...
On the backend side you need to make sure to access file not files as you're using upload.single and actually send a response in your handler as the resposse would hang otherwise:
//server.js
...
const upload = multer({storage}).single('file');
app.post('/upload', upload, (req, res) => {
console.log(req.file) // this does log the uploaded image data.
res.send("File uploaded");
});
...
I am trying to implement nodejs express passport axios based server-client communication. My goal push files to the server using the terminal. But first I try to implement an authentication service. I have passport auth service which works well when I signing throw postman app.
Also, I have implemented nodejs based client, which try signup to the server but without success.
error:
{ Error: Request failed with status code 400
at createError (/home/gefalko/gepick-devenv/node_modules/axios/lib/core/createError.js:16:15)
at settle (/home/gefalko/gepick-devenv/node_modules/axios/lib/core/settle.js:18:12)
at IncomingMessage.handleStreamEnd (/home/gefalko/gepick-devenv/node_modules/axios/lib/adapters/http.js:201:11)
at emitNone (events.js:111:20)
at IncomingMessage.emit (events.js:208:7)
at endReadableNT (_stream_readable.js:1064:12)
at _combinedTickCallback (internal/process/next_tick.js:139:11)
at process._tickCallback (internal/process/next_tick.js:181:9)
config:
{ adapter: [Function: httpAdapter],
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/x-www-form-urlencoded; charset=UTF-8',
'User-Agent': 'axios/0.18.0',
'Content-Length': 45 },
method: 'post',
url: 'http://localhost:3005/login',
data: '{"username":"gefalko","password":"mypass"}' },
request:
ClientRequest {
domain: null,
_events:
{ socket: [Function],
abort: [Function],
aborted: [Function],
error: [Function],
timeout: [Function],
prefinish: [Function: requestOnPrefinish] },
_eventsCount: 6,
_maxListeners: undefined,
output: [],
outputEncodings: [],
outputCallbacks: [],
outputSize: 0,
writable: true,
_last: true,
upgrading: false,
chunkedEncoding: false,
shouldKeepAlive: false,
useChunkedEncodingByDefault: true,
sendDate: false,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
_contentLength: null,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket:
Socket {
connecting: false,
_hadError: false,
_handle: [Object],
_parent: null,
_host: 'localhost',
_readableState: [Object],
readable: true,
domain: null,
_events: [Object],
_eventsCount: 8,
_maxListeners: undefined,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
_bytesDispatched: 263,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: null,
_server: null,
parser: null,
_httpMessage: [Circular],
[Symbol(asyncId)]: 45,
[Symbol(bytesRead)]: 0 },
connection:
Socket {
connecting: false,
_hadError: false,
_handle: [Object],
_parent: null,
_host: 'localhost',
_readableState: [Object],
readable: true,
domain: null,
_events: [Object],
_eventsCount: 8,
_maxListeners: undefined,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
_bytesDispatched: 263,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: null,
_server: null,
parser: null,
_httpMessage: [Circular],
[Symbol(asyncId)]: 45,
[Symbol(bytesRead)]: 0 },
_header: 'POST /login HTTP/1.1\r\nAccept: application/json, text/plain, */*\r\nContent-Type: application/x-www-form-urlencoded; charset=UTF-8\r\nUser-Agent: axios/0.18.0\r\nContent-Length: 45\r\nHost: localhost:3005\r\nConnection: close\r\n\r\n',
_onPendingData: [Function: noopPendingOutput],
agent:
Agent {
domain: null,
_events: [Object],
_eventsCount: 1,
_maxListeners: undefined,
defaultPort: 80,
protocol: 'http:',
options: [Object],
requests: {},
sockets: [Object],
freeSockets: {},
keepAliveMsecs: 1000,
keepAlive: false,
maxSockets: Infinity,
maxFreeSockets: 256 },
socketPath: undefined,
timeout: undefined,
method: 'POST',
path: '/login',
_ended: true,
res:
IncomingMessage {
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_eventsCount: 3,
_maxListeners: undefined,
socket: [Object],
connection: [Object],
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: true,
headers: [Object],
rawHeaders: [Array],
trailers: {},
rawTrailers: [],
aborted: false,
upgrade: false,
url: '',
method: null,
statusCode: 400,
statusMessage: 'Bad Request',
client: [Object],
_consuming: true,
_dumped: false,
req: [Circular],
responseUrl: 'http://localhost:3005/login',
redirects: [],
read: [Function] },
aborted: undefined,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
_redirectable:
Writable {
_writableState: [Object],
writable: true,
domain: null,
_events: [Object],
_eventsCount: 2,
_maxListeners: undefined,
_options: [Object],
_ended: true,
_ending: true,
_redirectCount: 0,
_redirects: [],
_requestBodyLength: 45,
_requestBodyBuffers: [],
_onNativeResponse: [Function],
_currentRequest: [Circular],
_currentUrl: 'http://localhost:3005/login' },
[Symbol(outHeadersKey)]:
{ accept: [Array],
'content-type': [Array],
'user-agent': [Array],
'content-length': [Array],
host: [Array] } },
response:
{ status: 400,
statusText: 'Bad Request',
headers:
{ 'x-powered-by': 'Express',
date: 'Wed, 20 Mar 2019 11:52:49 GMT',
connection: 'close',
'content-length': '11' },
config:
{ adapter: [Function: httpAdapter],
transformRequest: [Object],
transformResponse: [Object],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: [Function: validateStatus],
headers: [Object],
method: 'post',
url: 'http://localhost:3005/login',
data: '{"username":"gefalko","password":"mypass"}' },
request:
ClientRequest {
domain: null,
_events: [Object],
_eventsCount: 6,
_maxListeners: undefined,
output: [],
outputEncodings: [],
outputCallbacks: [],
outputSize: 0,
writable: true,
_last: true,
upgrading: false,
chunkedEncoding: false,
shouldKeepAlive: false,
useChunkedEncodingByDefault: true,
sendDate: false,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
_contentLength: null,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket: [Object],
connection: [Object],
_header: 'POST /login HTTP/1.1\r\nAccept: application/json, text/plain, */*\r\nContent-Type: application/x-www-form-urlencoded; charset=UTF-8\r\nUser-Agent: axios/0.18.0\r\nContent-Length: 45\r\nHost: localhost:3005\r\nConnection: close\r\n\r\n',
_onPendingData: [Function: noopPendingOutput],
agent: [Object],
socketPath: undefined,
timeout: undefined,
method: 'POST',
path: '/login',
_ended: true,
res: [Object],
aborted: undefined,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
_redirectable: [Object],
[Symbol(outHeadersKey)]: [Object] },
data: 'Bad Request' } }
my client:
const axios = require('axios')
const prompt = require('prompt')
var prompt_attributes = [
{
name: 'username',
validator: /^[a-zA-Z\s\-]+$/,
warning: 'Username is not valid, it can only contains letters, spaces, or dashes'
},
{
name: 'password',
hidden: true
}
]
prompt.start();
prompt.get(prompt_attributes, function (err, result) {
if (err) {
console.log(err);
return 1;
}else {
console.log('Command-line received data:');
const username = result.username
const password = result.password
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
const reqBody = {
username: username,
password: password
}
axios.post('http://localhost:3005/login', reqBody, config).then(response => {
console.log(response.data);
}).catch(error => {
console.log(error);
})
}
});
also tried 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' but it not help.
server:
import * as express from 'express'
const app = express()
const passport = require('passport')
const LocalStrategy = require('passport-local').Strategy
const bcrypt = require('bcryptjs')
app.use(require('cookie-parser')())
app.use(require('body-parser').urlencoded({ extended: true }))
app.use(passport.initialize())
const port = 3005
/* DATABASE */
import { MongoClient, ObjectID } from 'mongodb'
const mongoUrl = "mongodb://localhost:27017/";
/* AUTHENTICATION */
MongoClient.connect(mongoUrl, async (err, db) => {
const myDB = db.db('mydb')
const Users = myDb.collection('users')
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
passport.use(new LocalStrategy(
function(username, password, done) {
Users.findOne({ username: username }, async function(err, user) {
console.log('USER FROM DB:', user)
console.log('PASS from client', password)
if (err) { return done(err); }
if (!user){
return done(null, false, { message: 'Incorrect username.' });
}
const res = await bcrypt.compare(password, user.password)
return res ? done(null, user) : done(null, false, { message: 'Incorrect username.' })
})
}))
app.post('/login',passport.authenticate('local'), (req, res) =>
{
res.send('logined as ' + req.user.username)
}
)
app.listen(port, () => console.log('http server listening on port '+port+'!'))
})
Also, I found that my request body is parsed strange on the server when it`s come from node client and it is parsed correctly when it's coming from postman.
request body from nodejs axios client:
{ '{"username":"gefalko","password":"mypass"}': '' }
request body from postman client:
{"username":"gefalko","password":"mypass"}
Try this format :
var qs = require('qs');
axios.post('/foo', qs.stringify({ 'bar': 123 } , config);
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: '' } }