AWS SDK for JavaScript v3 PutObjectCommand error 'A header you provided implies functionality that is not implemented' - amazon-s3

I'm trying to upload a file with node.js from my client app (electron) to an S3 bucket in this manner:
const { S3Client, PutObjectCommand } = require('#aws-sdk/client-s3');
const s3Client = new S3Client({
region: 'eu-central-1',
credentials: {
accessKeyId: 'access',
secretAccessKey: 'secret',
},
});
const uploadFileToS3 = async (f) => {
const bucketParams = {
ACL: 'private',
Bucket: 'bucket',
Key: f.name,
Body: f.data,
ServerSideEncryption: 'AES256',
ContentType: 'image/png',
};
try {
return await s3Client
.send(new PutObjectCommand(bucketParams))
.then((result) => {
return process.send({
type: 'success',
fileName: f.name,
result,
});
});
} catch (erro) {
process.send({
type: 'error',
fileName: f.name,
error: erro,
});
}
};
process.on('message', (file) => {
uploadFileToS3(file);
});
I get the following error, that I'm unable to understand:
error: {
name: 'NotImplemented',
'$fault': 'client',
'$metadata': {
httpStatusCode: 501,
requestId: 'PXEBV6H4MX3',
extendedRequestId: 'yyyyyy',
attempts: 1,
totalRetryDelay: 0
},
Code: 'NotImplemented',
Header: 'Transfer-Encoding',
RequestId: 'PXEBV6H4MX3',
HostId: 'yyyyyy',
message: 'A header you provided implies functionality that is not implemented'
}
The file is a buffer generated with:
fs.readFileSync(pth)
Any idea of what could caused this error ?

Seems like the buffer created with
fs.readFileSync(pth)
was rejected and I could only use a stream:
const readableStream = await createReadStream(Buffer.from(f));
Maybe I'm wrong but it is possible that the actual SDK version is unable to accept a buffer yet, this could be the reason for that "missing functionality" message.

Related

Nextjs API folder call - 500 error in production

I have an api folder in my next.js app for some server side endpoints:
import { NextApiRequest, NextApiResponse } from 'next'
import Cors from 'cors'
// Initializing the cors middleware
const cors = Cors({
methods: ['GET', 'HEAD', 'POST'],
origin: '*',
optionsSuccessStatus: 200,
})
// Helper method to wait for a middleware to execute before continuing
// And to throw an error when an error happens in a middleware
function runMiddleware(req, res, fn) {
return new Promise((resolve, reject) => {
fn(req, res, (result) => {
if (result instanceof Error) {
return reject(result)
}
return resolve(result)
})
})
}
export default async (req, res) => {
await runMiddleware(req, res, cors)
const POSKEY = process.env.POSKEY
const PAYEE = process.env.PAYEE
const { currency, url, locale, price } = req.body
const currentUrl = url
const apiResult = await fetch(
'https://api.test.barion.com/v2/Payment/Start',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': 3495,
},
body: JSON.stringify({
PosKey: POSKEY,
PaymentType: 'Immediate',
GuestCheckout: true,
FundingSources: ['All'],
Currency: currency,
RedirectUrl: currentUrl,
CallbackUrl: currentUrl,
Locale: locale,
Transactions: [
{
Payee: PAYEE,
Total: price,
Items: [
{
Name: 'Teszt',
Description: 'Test item comment',
Quantity: 1,
Unit: 'pc',
UnitPrice: 1,
ItemTotal: 1,
SKU: 'SM-01',
},
],
},
],
}),
}
)
.then((result) => {
return result.json()
})
.catch((error) => {
console.error(error)
})
res.status(200).json({ url: apiResult.GatewayUrl })
}
When I call the endpoint, in development it works perfectly:
But in production I got 500 error. (deployed to vercel)
Error in the console on vercel:
[POST] /apigateway/ 23:30:28:53
2022-06-27T21:30:28.595Z e8c57750-4647-4e7a-b62e-6221abc141ac ERROR Error: Cannot find module '/var/task/node_modules/next/dist/server/next.js'.
Please verify that the package.json has a valid "main" entry
at tryPackage (internal/modules/cjs/loader.js:321:19)
at Function.Module._findPath (internal/modules/cjs/loader.js:534:18)
at Function.Module._resolveFilename (internal/modules/cjs/loader.js:888:27)
at Function.Module._load (internal/modules/cjs/loader.js:746:27)
at Module.require (internal/modules/cjs/loader.js:974:19)
at require (internal/modules/cjs/helpers.js:101:18)
at Object.5199 (/var/task/.next/server/pages/api/gateway.js:20:39)
at webpack_require (/var/task/.next/server/webpack-api-runtime.js:25:42)
at webpack_exec (/var/task/.next/server/pages/api/gateway.js:109:39)
at /var/task/.next/server/pages/api/gateway.js:110:28 { code: 'MODULE_NOT_FOUND', path:
'/var/task/node_modules/next/package.json', requestPath: 'next' }
RequestId: e8c57750-4647-4e7a-b62e-6221abc141ac Error: Runtime exited
with error: exit status 1 Runtime.ExitError
What other configuration should I add to my next.config file to make it work, I am beginner with this api.
UPDATE:
This solved my problem... https://github.com/vercel/next.js/issues/34844
The problem was with the .js file wrong import:
Same issue here: https://github.com/vercel/next.js/issues/34844#issuecomment-1055628706
TLDR:
Remove the import { NextApiRequest, NextApiResponse } from "next";

Cloudinary\Error: Missing required parameter - file - Express and Postman

first time trying to upload images to Cloudinary and I have come across an issue when using Express via Postman.
Using form-data on setting 'file' to upload an image to Cloudinary
As of now, when I try to access the req.body I get an empty object, so I guess that has to do with why cloudinary.uploader.upload cannot read the file passed as its first param, since its req.body.file, as shown in the code below.
cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_KEY,
api_secret: process.env.CLOUDINARY_SECRET
})
exports.upload = async (req, res) => {
try{
console.log(req.body);
const result = await cloudinary.uploader.upload(req.body.file, {
public_id: `${Date.now()}`,
resource_type: "auto"
})
return res.json({
public_id: result.public_id,
url: result.secure_url
})
}catch(err){
console.log(err)
}
}
The error message I get:
{
message: 'Missing required parameter - file',
name: 'Error',
http_code: 400
}
Any suggestions to solve this issue?
I solved it! I was not able to pass the form-data as req.body to the server, so I had to try and access it through req.files, but was not able to with that either, so I searched a bit and found a middleware 'express-fileupload', and that did the trick. I just added it in my app.js and used
const fileupload = require('express-fileupload');
app.use(fileupload({useTempFiles: true}))
So now I can access my req.files.
exports.upload = async (req, res) => {
const file = req.files.image
try{
console.log(file);
const result = await cloudinary.uploader.upload(file.tempFilePath, {
public_id: `${Date.now()}`,
resource_type: "auto"
})
res.json({
public_id: result.public_id,
url: result.secure_url
})
}catch(err){
console.log("Error", err)
return res.status(400).json({error: err})
}
}
The response I get is:
{
name: 'some-image.png',
data: <Buffer >,
size: 99770,
encoding: '7bit',
tempFilePath: ' **C:\\filepath\some-image.png** ',
truncated: false,
mimetype: 'image/png',
md5: 'b5f612a571442bf604952fd12c47c1bf',
mv: [Function: mv]
}
POST /cloudinary/upload-images 200 1617.944 ms - 119
And it is uploaded successfully to my Cloudinary.
const result = await cloudinary.uploader.upload(req.file, {
public_id: ${Date.now()},
resource_type: "auto"
})
and add file from form data and type should be File
Solved!
This is how i am setting the FormData
let myTestForm = new FormData();
myTestForm.set("name", name);
myTestForm.set("email", email);
myTestForm.set("Avatar", Avatar);
myTestForm.set("password", password);
This is how i am using the FormData
const config = {
headers: {
"Content-type": "multipart/form-data",
},
};
const { data } = await axios.post(`/api/v1/register`, userData, { config });
please don't pass it this way { userData} , had struggled for with this :/
This is how i am uploading image
const myCloud = await cloudinary.v2.uploader.upload(req.body.Avatar, {
folder: "Avatars",
width: 150,
crop: "scale",
public_id: `${Date.now()}`,
resource_type: "auto",
});
PS : in my case i had to upload only 1 image. Have not passed any parameter in app.js file
app.use(bodyParser.urlencoded({ extended: true }));
app.use(fileUpload());

Serverless Api Gateway Proxy Lambda with Axios to serve binary files (PDF)?

I am using serverless and axios to "passthrough" a PDF generated by the
stampery api, which needs credentials I do not want to save client-side.
In principle I get the PDF as an arraybuffer from axios, transform the arraybuffer to an buffer, which I then use to create a base64 string, needed by API Gateway binary treatment, which can be "activated" by including the isBase64Encoded: true property in the callback response object.
The following code yields an empty PDF:
const axios = require('axios');
const stamperyClientId = 'xxx';
const stamperySecret = 'xxx';
const stampery = axios.create({
baseURL: 'https://api-prod.stampery.com',
auth: {
username: stamperyClientId,
password: stamperySecret
}
});
module.exports.hello = (event, context, callback) => {
const response = {
statusCode: 200,
headers: {
'Content-Type' : 'application/pdf',
'Content-Disposition': 'inline; filename="certificate.pdf"'
},
isBase64Encoded: true
};
stampery.get(`/stamps/5b2a612680e0190004bcccc8.pdf`, {
responseType: 'arrayBuffer',
})
.then(res => {
const buffer = Buffer.from(res.data)
const base64Str = buffer.toString('base64')
response.body = base64Str
callback(null, response);
})
};
The PDF is retrieved by: `curl https://xxx.execute-api.eu-west-1.amazonaws.com/dev/certificate -o my.pdf -H "Accept: application/pdf"
I tested the setup with fileReadSync, which turned out fine:
module.exports.hello = (event, context, callback) => {
const content = fs.readFileSync("data/sample.pdf");
const response = {
statusCode: 200,
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": "inline; filename=\"sample.pdf\""
},
body: content.toString("base64"),
isBase64Encoded: true
};
return callback(null, response);
};
What am I missing? Is this the right way to transform an axios arraybufferinto a base64 string?
P.S. My serverless.yml is given by:
functions:
hello:
handler: handler.hello
events:
- http:
path: certificate.pdf
method: get
package:
include:
- data/sample.pdf
resources:
Resources:
ApiGatewayRestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: dev-stamper
BinaryMediaTypes:
- "application/pdf" # or whichever ones you need
where I already thought of the necessary binary media types.
Ok... the error was a simple typo. I wrote arraybuffer in camel case arrayBuffer, so Axios returned a string instead of an arraybuffer.
Change this line:
stampery.get(`/stamps/5b2a612680e0190004bcccc8.pdf`, {
responseType: 'arrayBuffer',
})
to
stampery.get(`/stamps/5b2a612680e0190004bcccc8.pdf`, {
responseType: 'arraybuffer',
})
and everything works as a charm...

Wreck: Getting "Error: Invalid request payload JSON format" when posting a mp4

I am trying to post a mp4 using Wreck to an Hapi end point, but I receive Error: Invalid request payload JSON format. Below are the routes. I suspect that I should specify a content-type of video/mp4 from Wreck, but the doc don't tells how to do that, event the tests. Please help me find out what is wrong there:
{
method: 'POST',
path: '/upload/mov',
handler: function (request, reply) {
const source = path.join(__dirname, '../data/mov.mp4');
const stats = fs.statSync(source);
const fileStream = fs.createReadStream(source);
const req = Wreck.request('post', '/api/upload/mov', {
baseUrl: baseUrl,
payload: fileStream,
maxBytes: 7073741824 // 70mb
});
req.on('response', (res) => {
// code removed to try to isolate the problem
reply({ message: 'finished' });
});
}
},
{
method: 'POST',
path: '/api/uplaod/mov',
config: {
payload: {
maxBytes: 7073741824 // 70mb
}
},
handler: function (request, reply) {
// const stream = request.pipe(request.response);
// code removed to try to isolate the problem
reply({ message: 'sent' });
}
}

How to restrict file upload, if it is not valid file format in loopback?

I have added meta data by referring How to store files with meta data in LoopBack?
Now I have to check if the filetype is in csv, before uploading it to the server.
Right now, I delete the uploaded file if it is not valid. Is there a better way to solve this?
let filePath;
File.app.models.container.upload(ctx.req, ctx.result, options, function(err, fileObj) {
if (err) {
callback(err);
}
let fileInfo = fileObj.files.file[0];
filePath = path.join("server/storage", fileInfo.container, fileInfo.name);
if (fileInfo.type === "text/csv") {
File.create({
name: fileInfo.name,
type: fileInfo.type,
container: fileInfo.container,
url: path.join(CONTAINERS_URL, fileInfo.container, "/download/",
fileInfo.name)
}, function(err, obj) {
if (err) {
callback(err);
}
callback(null, filePath);
});
} else {
fs.unlinkSync(filePath); //delete if it is not csv
let error = new Error();
error.message = "Please upload only csv file";
error.name = "InvalidFile";
callback(error);
}
});
Here is what I've done,
in middleware.json
"parse": {
"body-parser#json": {},
"body-parser#urlencoded": {"params": { "extended": true }}
},
in server/server.js
var multer = require('multer');
var boot = require('loopback-boot');
var app = module.exports = loopback();
app.use(bodyParser.json()); // for parsing application/json
app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
app.use(multer()); // for parsing multipart/form-data
and remote method as ,
File.remoteMethod(
'upload', {
description: 'Uploads a file with metadata',
accepts: {arg: 'ctx', type: 'object', http: function(ctx) {
console.log(ctx.req.files);
return ctx;
}
},
returns: {arg: 'fileObject', type: 'object', root: true},
http: {verb: 'post'}
}
);
Now ctx can give the mime type..
Update 1:
There is another easier option,
You could define the restriction in datasources.local.js as below, I tested with a filesystem provider
module.exports = {
container: {
root: "./upload",
acl: 'public-read',
allowedContentTypes: ['image/jpg', 'image/jpeg', 'image/png'],
maxFileSize: 10 * 1024 * 1024
}
}