I am just trying to upload a file from the browser through Node.js/Next.js to S3, and busboy is not emitting the file event.
Here is my FE code essentially:
Upload.prototype.to = function(options, fn){
// TODO: x-browser
var path;
if (typeof options == 'string') {
path = options;
options = {};
} else {
path = options.path;
}
var self = this;
fn = fn || function(){};
var req = this.req = new XMLHttpRequest;
req.open('POST', path);
req.onload = this.onload.bind(this);
req.onerror = this.onerror.bind(this);
req.upload.onprogress = this.onprogress.bind(this);
req.onreadystatechange = function(){
if (4 == req.readyState) {
var type = req.status / 100 | 0;
if (2 == type) return fn(null, req);
var err = new Error(req.statusText + ': ' + req.response);
err.status = req.status;
fn(err);
}
};
var key, headers = options.headers || {};
for (key in headers) {
req.setRequestHeader(key, headers[key]);
}
var body = new FormData;
body.append(options.name || 'file', this.file);
var data = options.data || {};
for (key in data) {
body.append(key, data[key]);
}
req.send(body);
};
All that's doing is making the request to /api/<path> with the file name for the selected jpeg I am trying to upload. This is what I receive in the server request body:
body: '------WebKitFormBoundaryWbaXO8J6c8aI7Q4B\r\n' +
'Content-Disposition: form-data; name="file"; filename="1-profile.jpg"\r\n' +
'Content-Type: image/jpeg\r\n' +
'\r\n' +
'����\x00\x10JFIF...
The headers include these:
connection: 'keep-alive',
'content-length': '41079',
pragma: 'no-cache',
'cache-control': 'no-cache',
'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryWbaXO87Kc9aI2Q4B',
accept: '*/*',
origin: 'http://localhost:3000',
My server code looks like this:
import fetchId from '../../../../utils/get-next-id-from-pg'
import Busboy from 'busboy'
import s3 from 'initializers/s3'
export default async function(req, res) {
if (req.method === 'POST') {
const id = await fetchId('image')
return new Promise((resolve, rej) => {
const busboy = new Busboy({ headers: req.headers });
busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) {
console.log('Field [' + fieldname + ']: value: ' + inspect(val));
})
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
console.log('params')
const params = {
Bucket: 'mybucket123',
Key: id,
Body: file
}
s3.upload(params, (err, data) => {
console.log('s3', err, data)
res.setHeader('Connection', 'close');
res.status(200).json({ records: [ { id } ]})
resolve()
})
})
busboy.on('finish', function() {
console.log('finish')
})
req.pipe(busboy);
})
} else {
res.writeHead(405, { 'content-type': 'text/plain' });
res.end("Method not allowed. Send a POST request.");
return;
}
}
It logs finish and that's it, it doesn't log either file or field. What am I missing here? It doesn't log params inside of the on('file') handler, or any of the s3 stuff. I am starting to dig into the busboy source code but is there any better way? What am I doing wrong, or what is a library that works with Next.js?
I am using Next.js and dotenv, could that be causing weird issues?
Hooray I figured it out, add this to the Next.js route:
export const config = {
api: {
bodyParser: false,
},
}
https://nextjs.org/docs/api-routes/api-middlewares#custom-config
Related
I am struggling from the past 2 days to crack the file/image upload with React Native to MongoDB. I literally read all the related forums but there is no luck. I read couple of forums and they gave a sample example but I wasn't succeeded. Here are the sample codes that I wrote.
Client Side :
const { uri } = await this.camera.takePictureAsync(options);
let formData = new FormData();
formData.append('file', {
uri: uri.replace("file:///", ""),
type:'image/jpg', name:'userProfile.jpg',
});
const rawResponse = await fetch('http://192.168.1.5:9000/api/contrats/upload', {
method: 'POST',
body: formData,
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data; charset=utf-8',
},
});
const content = await rawResponse.json();
console.log(content);
Server Side
var storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, __basedir + '/resources/static/assets/uploads');
},
filename: (req, file1, cb) => {
console.log("file : ", file);
let name = file.originalname || file.name;
let extension = name.substr((~-name.lastIndexOf(".") >>> 0) + 2);
let filename = generateId() +"."+ extension; nsion;
cb(null, filename)
},
});
var upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 5
}
});
Result
Try out the below
let body = new FormData();
let filename = uri.split('/').pop();
body.append('file', {uri:uri, name:filename, type:'image/jpg', });
const header = {
'Accept': 'application/json',
'content-type': 'multipart/form-data',
}
fetch("http://192.168.1.5:9000/api/contrats/upload", {
method: 'POST',
headers: header,
body:body,
}).then(response => response.json())
.then(res => console.log(res))
.catch(err => console.log("err", err)
Getting a dreaded JSON error .
I am using an external API that allegedly takes a POST to add a user to a group.
in my nodeJS express app - I want to pass-thru the data coming from my app to the external API.
my "GET" methods work - but now I am trying to take a form submitted to my app, and pass the data to an external API using "POST".
Here is the code I am testing (assume the api url and credentials are correct - and NOT the issue)
I have tested the external API passing the same JSON object directly to the external API using Postman, and it works without error.
const express = require('express');
const router = express.Router();
const https = require('https');
function callExternalAPI( RequestOptions ) {
return new Promise((resolve, reject) => {
https.request(
RequestOptions,
function(response) {
const { statusCode } = response;
if (statusCode >= 300) {
reject(
new Error( response.statusMessage )
);
}
const chunks = [];
response.on('data', (chunk) => {
chunks.push(chunk);
});
response.on('end', () => {
const result = Buffer.concat(chunks).toString();
resolve( JSON.parse(result) );
});
}
)
.end();
})
}
router.get('/thing', /*auth,*/ ( req, res, next ) => {
callExternalAPI(
{
host: 'api_url',
path: '/list',
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + new Buffer( auth_un + ':' + auth_pw ).toString('base64')
}
}
)
.then(
response => {
console.log(response);
}
)
.catch(
error => {
console.log(error);
}
);
});
router.post('/thing', /*auth,*/ ( req, res, next ) => {
callExternalAPI(
{
host: 'api_url',
path: '/addThing',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + new Buffer( auth_un + ':' + auth_pw ).toString('base64')
},
data: {
'group_id': req.body.group_id,
'person_id': req.body.person_id
}
}
)
.then(
response => {
console.log(response);
}
)
.catch(
error => {
console.log(error);
}
);
});
module.exports = router;
console logging the req.body looks lie this
{ group_id: '45b62b61-62fa-4684-a058-db3ef284f699', person_id: '3b1c915c-3906-42cf-8084-f9a25179d6b2' }
And the error looks like this
undefined:1
<html><title>JSpring Application Exception</title>
<h2>JSpring Exception Stack Trace</h2>
<pre>SafeException: FiberServer.parsers.parseJSONBuf(): JSON parse failed.
^
SyntaxError: Unexpected token < in JSON at position 0
Granted the console.log of the req.body does not have the required double quotes, but I think that is just the log dump format - but it might be munging the JSON. I have tried wrapping this in stringify; meaning something like this: data: JSON.stringify( req.body ) (but the same error occurs).
callExternalAPI(
{
host: 'api_url',
path: '/addThing',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + new Buffer( auth_un + ':' + auth_pw ).toString('base64')
},
**data: JSON.stringify( req.body )**
}
)
I am testing this in postman, having the body be 'raw json' with headers as 'application/json'
the body is this:
{
"group_id": "45b62b61-62fa-4684-a058-db3ef284f699",
"person_id": "3b1c915c-3906-42cf-8084-f9a25179d6b2"
}
You should try to write the POST payload in the request body instead of passing it inside the options object:
function callExternalAPI( RequestOptions ) {
const { data, ...options } = RequestOptions;
return new Promise((resolve, reject) => {
const req = https.request(
options,
function(response) {
const { statusCode } = response;
if (statusCode >= 300) {
reject(
new Error( response.statusMessage )
);
}
const chunks = [];
response.on('data', (chunk) => {
chunks.push(chunk);
});
response.on('end', () => {
const result = Buffer.concat(chunks).toString();
resolve( JSON.parse(result) );
});
}
);
req.write(JSON.stringify(data));
req.end();
})
}
In express you must use bodyParser
At the top of the file when you initializing your express app add this lines
const app = express()
const bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
https://medium.com/#adamzerner/how-bodyparser-works-247897a93b90
my server can't find the api's that i created in api directory. it leads to 500 internal server.
I have checked routes.js but i see that everything is right. i have an error.js file for file handling. Here's my code.
'use strict';
let router = require('express').Router();
// Middleware
let middleware = require('./controllers/middleware');
router.use(middleware.doSomethingInteresting);
// Tasks
let tasks = require('./controllers/tasks');
let createkeypairs = require('./controllers/createkeypairs');
let importaddress = require('./controllers/importaddress');
let getwalletinfo = require('./controllers/getwalletinfo');
router.get('/tasks', tasks.findAll2);
router.get('/createkeypairs', createkeypairs.findAll);
router.get('/importaddress', importaddress.findAll);
router.get('/getwalletinfo', getwalletinfo.findAll);
router.post('/buggyroute', tasks.buggyRoute);
// Error Handling
let errors = require('./controllers/errors');
router.use(errors.errorHandler);
// Request was not picked up by a route, send 404
router.use(errors.nullRoute);
// Export the router
module.exports = router;
now showing you my createkeypairs.js
'use strict';
let errors = require('./errors.js');
var request = require("request");
var options = { method: 'POST',
url: '127.0.0.1:18332',
headers:
{ 'Authorization': 'Basic bXVsdGljaGFpbnJwYzpHTmJ5enJhMnlHRjN4Ymp1cnluRTFucTlnV1ExRXV3OTFpYVBqSkt5TkJxdA==',
'cache-control': 'no-cache',
'Cache-Control': 'no-cache',
'Content-Type': 'application/json' },
body: { method: 'createkeypairs', params: [], chain_name: 'tokenchain' },
json: true };
exports.findAll = (req, res, next) => {
// Simulate task list, normally this would be retrieved from a database
let createkeypairs ;
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log("working here ");
// res.json(body);
});
};
exports.buggyRoute = (req, res, next) => {
// Simulate a custom error
next(errors.newHttpError(400, 'bad request'));
};
I think the problem is in createkeypair file.
Try this code once for your createkeypairs.js:
'use strict';
let errors = require('./errors.js');
var request = require("request");
let config = require('config');
var auth = 'Basic ' + Buffer.from(config.user + ':' + config.pass).toString('base64');
var url = config.url;
var chain = config.chain;
var options = { method: 'POST',
url: url,
headers:
{ 'cache-control': 'no-cache',
Authorization : auth,
'Content-Type': 'application/json' },
body: { method: 'importaddress', params: ["address"], chain_name: chain },
json: true };
exports.findAll = (req, res, next) => {
// Simulate task list, normally this would be retrieved from a database
let createkeypairs ;
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
res.json(body);
});
};
exports.buggyRoute = (req, res, next) => {
// Simulate a custom error
next(errors.newHttpError(400, 'bad request'));
};
Do tell me if it works or not.
I'm trying to post discount codes to a user's shop using the reverse engineering instructions shown here http://ma.rtin.so/reverse-engineering-shopify-private-apis (instructions are in PHP)
The first step is properly logging into the users account so I can grab information from the response. Am I doing this step correctly? I feel like I'm missing something to do with tokens, but its hard for me to understand the PHP code given in the instructions.
I am receiving a response without an error status code from the login function but I still don't know if this means I'm doing it correctly. Thanks for any help.
Node.js Discount Creation Controller (Please look at login function but included the whole thing in case)
use strict';
var request = require('request');
var cookie = require('cookie');
var USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17';
var login = function(req, res, cb) {
req.url = 'https://' + req.body.name + '.myshopify.com';
var post_data = {
'utf8': '✓',
'redirect': '',
'subdomain': req.body.name,
'login': req.body.email,
'password': req.body.pwd
}
var headers = {
'User-Agent': USER_AGENT,
'Content-Type': 'application/x-www-form-urlencoded'
};
var url = req.url + '/admin/auth/login';
request.post({ url: url, form: post_data, headers: headers }, function(err, response, body) {
if (err) 'ERROR LOGGING IN';
else {
if (response.statusCode !== 200) throw 'ERROR LOGGING IN';
console.log('Login response headers:', response.headers);
var shopCookies = response.headers['set-cookie'];
var j = request.jar();
for (var i = 0; i < shopCookies.length; i++) {
var cookie = request.cookie(shopCookies[i]);
j.setCookie(cookie, url);
}
req.cookie_string = j.getCookieString(url);
if (cb !== undefined) cb(req, res);
}
});
};
var setCoupons = function(req, res) {
var url = req.url + '/admin/discounts/new';
var headers = {
'User-Agent': USER_AGENT,
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': req.cookie_string
};
request.get({ url: url, headers: headers }, function(err, response, body) {
if (err) throw 'Problem setting coupons';
var value = response.body.match(/name="authenticity_token" value=".*"/i)[0];
var index = value.indexOf('value="');
value = (value.substring(index + 7, value.length - 1));
var count = parseInt(req.body.amount) + 1;
var checkCount = count;
var codes = [];
for (var i = 1; i < count; i++) {
var post_data = {
utf8: '✓',
authenticity_token: value,
discount: {
code: req.body.code + "_" + i,
discount_type: req.body.discount_type,
value: parseInt(req.body.value),
applies_to_resource: '',
starts_at: '2016-04-10'
},
'unlimited-uses': '',
discount_never_expires: ''
}
codes.push(post_data.discount);
var url = req.url + '/admin/discounts';
request.post({ url: url, form: post_data, headers: headers }, function(err, response, body) {
checkCount--;
if (checkCount < 2) {
res.send(codes);
}
});
}
});
};
export function create(req, res) {
login(req, res, setCoupons);
}
Discount codes are only available to Shopify Plus customers.
The API however is available now: https://docs.shopify.com/api/reference/discount
Also rather than using Shopify Plus it would be possible to create your own Discount code engine as an app and apply discounts to sales orders.
I am trying to write a basic REST Post client to work with node.js and because of the REST API I have to work with I have to get details from the responses including cookies to maintain the state of my REST session with the server. My Question is what is the best way to pull the json objects from the response when res.on triggers with all the data in the PRINTME variable and return it to the test.js console.log().
test.js file
var rest = require('./rest');
rest.request('http','google.com','/upload','data\n');
console.log('PRINTME='JSON.stringify(res.PRINTME));
rest.js module
exports.request = function (protocol, host, path, data, cookie){
var protocalTypes = {
http: {
module: require('http')
, port: '80'
}
, https: {
module: require('https')
, port: '443'
}
};
var protocolModule = protocalTypes[protocol].module;
var options = {
host: host,
port: protocalTypes[protocol].port,
path: path,
method: 'POST',
headers: {
'Content-Type': 'text/xml'
, 'Content-Length': Buffer.byteLength(data)
, 'Cookie': cookie||''
}
};
console.log('cookies sent= '+options.headers.Cookie)
var req = protocolModule.request(options, function(res) {
var PRINTME = res;
console.log('STATUS: ' + res.statusCode);
console.log('HEADERS: ' + JSON.stringify(res.headers));
res.setEncoding('utf8');
res.on('data', function (chunk) {
PRINTME.body = chunk;
console.log('BODY: ' + chunk);
});
res.on('close', function () {res.emit('end')});
});
req.on('error', function(e) {
console.error('Request Failure: ' + e.message);
});
req.write(data);
req.end();
};
Using a package like request will help you simplify your code.
The following would be rest.js
var request = require('request');
module.exports = function(protocol, host, path, data, cookie, done) {
var options = {
host: host,
port: protocalTypes[protocol].port,
path: path,
method: 'POST',
headers: {
'Content-Type': 'text/xml',
'Content-Length': Buffer.byteLength(data)
},
jar: true
};
request(options, function(err, resp, body) {
if (err) return done(err);
// call done, with first value being null to specify no errors occured
return done(null, resp, body);
});
}
Setting jar to true will remember cookies for future use.
See this link for more information on the available options
https://github.com/mikeal/request#requestoptions-callback
To use this function in another file
var rest = require('./rest');
rest(... , function(err, resp, body){
...
});