I am having a bit of trouble forwarding multipart/form-data using NextJS api.
async function proxy(req: NextApiRequest, res: NextApiResponse): Promise<void> {
const { body, headers, method, url } = req;
// Doing some work to handle authentication
const rewriteUrl = url?.replace('/api/proxy', '');
const config: AxiosRequestConfig = {
url: rewriteUrl,
method: method as Method,
headers: {
cookie: '',
Authorization: token,
},
data: body,
};
try {
const { data: originalData, status } = await axiosInstance(config);
return res.status(status).json(originalData);
} catch (err) {
return res.status(err.status).json(err);
}
}
export default proxy;
When calling the API directly or using non multipart/form-data network calls, no problem whatsoever. However, when proxying a multipart/form-data, I am receiving 413 Payload Too Large
I am have been trying to use
export const config = {
api: {
bodyParser: false,
},
};
but in that case the backend API is responding with 400 telling that the file doesn't exist.
As am I adding an authentication header before forwarding the request, can this be the culprit as the content size change?
Related
I am using expressJS on the back end to make a very simple API since I am a beginner. I am sending a request to the back end from the front end and I expect the front end to receive a response. This works fine until I change the nodejs for it to make a second request before sending the original response back to the client. The process looks something like:
Front end sends a POST request
back end receives request, then:
makes its own POST request to a source
waits for this data to come back, then:
sends back a response to the original request from the front end including the data gotten from the second request.
This process works fine when I remove the few lines of code which send the second request, but when the NodeJs back end makes this second request, I get a 404 error returned to the front end - and this error does not come from the second request.
Here is the code:
front end:
function post() {
return new Promise(() => {
$.ajax("URL of my nodejs backend", {
method: "POST",
cache: false,
data: {
action: "test-https"
},
}).then(response => {
console.log(response);
});
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script>
nodejs backend (only the bits needed for this question)
const express = require("express");
//const $ = require("./djax.js");
const https = require('https');
const app = express();
app.post("/", (req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
const body = [];
req.on("data", (chunk) => {
body.push(chunk);
});
req.on("end", () => {
const parsedBody = Buffer.concat(body).toString();
//res.status(200).send("bod" + parsedBody);
// Now parsedBody will be like a query string: key1=val1&key2=val2
const queryObject = new URLSearchParams(parsedBody);
parseRequest(queryObject, res);
//console.log(parsedBody);
});
//console.log(body);
next();
});
function parseRequest(queryParameters, response) {
// Here, queryParameters is a QueryParams object holding the body of the request
// sendResponseFunc is the function which sends back the response for this
// current request.
// Now, we have access to the body of the request and we can use this
// to call the neccessary functions and logic, after which
// send a response back to the front-end via the second
// parameter
const action = queryParameters.get("action");
switch(action.toLowerCase()) {
// ... other cases ...
case "test-https":
sendHttpsRequest(response);
break;
default:
response.status(200).send("Error: unknown action:'" + action.toLowerCase() + "'");
break;
}
}
function sendHttpsRequest(response) {
const postData = JSON.stringify({
works: true
});
const postOpts = {
host: "httpbin.org", // This is a test-server. Not mine.
path: "/post",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(postData)
}
};
const newReq = https.request(postOpts, result => {
result.setEncoding("utf8");
res.on("data", chunk => {
console.log("Response" + chunk);
response.status(200).send("Request made from NodeJS end came back " + chunk);
});
});
newReq.write(postData);
newReq.end();
}
When I change the sendHttpsRequest function so that it does NOT make a request, like so:
function sendHttpsRequest(response) {
response.status(200).send("Hi");
}
... then the front-end receives the response and there is no error. There is a 500 internal server error only when the second request is made - when the function sendHttpsRequest is like in the penultimate snippet. I have tried to fix this for two days but I have no idea why this error is happening.
How can I make this second request from the NodeJS server and send back the contents of that without causing the 500 error?
You need to have error handling. You cannot expect that external requests will succeed all the time, for that reason you have to have res.on("error", ...) to respond the client appropriately.
However, I don't see a special case why you are using a data listener to collect payload chunks, it can be simplified very much.
Here is a very simple working example for you
// Backend
const express = require('express');
const axios = require('axios');
const cors = require('cors');
const app = express();
const port = 3000;
app.use(express.json()); // Accepts JSON as a payload
app.use(cors());
app.post('/', (req, res) => {
axios
.get('EXTERNAL URL')
.then((response) => {
console.log('Received payload', req.body);
// Handle response
res.json({data: response.data}).status(201);
})
.catch((error) => {
// Handle error
res.json({
message: error.message,
code: 422})
.status(422);
});
});
app.listen(port, '0.0.0.0', () => console.log(`Started at //127.0.0.1:${port}`));
Required dependencies are
ExpressJS cors package
Axios HTTP client
Axios can be used in browsers as well
Here is your jQuery Ajax request which sends JSON payload instead of FormData
// FE jQuery
function post() {
return new Promise((resolve, reject) => {
$.ajax('http://127.0.0.1:3000', {
contentType: 'application/json; charset=utf-8',
dataType: 'json',
method: 'POST',
data: JSON.stringify({
action: 'test-https',
}),
success: (data) => resolve(data),
error: (err) => reject(err),
});
});
}
post().then(console.log).catch(console.error);
I use NextJS (combined: SSR and SPA for authorized dashboard) with Django Rest FW on the backend. For auth I use JWT token, which is stored in cookies. For that reason, I need a middleware at /pages/api/* for each request to append from cookie access token.
Question:
How to implement a protected request to send file to /pages/api/upload and send it to DRF with an access token?
Sample of small API middleware
export default async (req, res) => {
const { id } = req.query
const cookies = cookie.parse(req.headers.cookie ?? "");
const access = cookies["access"] ?? false;
if (access === false) {
return res.status(401).json({
error: "User unauthorized to make this request"
});
}
if (req.method === "GET") {
try {
const apiRes = await fetch(`${LOCAL_API_URL}/items/${id}`, {
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": `Bearer ${access}`
}
});
const data = await apiRes.json();
if (apiRes.status === 200) {
return res.status(200).json(data);
} else {
return res.status(apiRes.status).json({
error: data.error
});
}
} catch(err) {
console.log(err);
return res.status(500).json({
error: "Something went wrong"
});
}
} else
res.setHeader("Allow", ["GET"]);
return res.status(405).json({
error: `Method ${res.method} is not allowed`
});
}
For sending image you should use FormData.
Firstly create an instance of FormData.
const formData = new FormData()
Then, you can add image into that.
formData.append('fieldName', someFileInput.current.files[0])
Also, if you want to add some more data with the image, you can append it to FormData too, the similar way.
formData.append('fieldName', someMoreData)
Then, you should set Content-Type to 'multipart/form-data', this is to server understand you pass the FormData.
And, finally, send the form data via Fetch.
I was glad to answer you, I hope it helps you!
the solution was raiser simple. Just passed everything I received and appended token to headers/
export default async (req, res) => {
// all extra validation
const apiRes = await fetch(`${LOCAL_API_URL}/upload/`, {
method: "POST",
headers: { ...req.headers, ...{ "Authorization": `Bearer ${access}` } },
body: req.body
});
// all extra validation
}
I'm trying to send a POST method to https://api.getresponse.com/v3/contacts from my Vue using axios. I'm unsure why I kept getting a 400 Bad Request. I've tried checking the Network tab on Mozilla dev tools but there doesn't seem to have any Response message it just returned me.
XHR OPTIONS https://api.getresponse.com/v3/contacts [HTTP/1.1 400 Bad Request 763ms]
I have double confirm from GetResponse documentation to add the Content-Type header to application/json and to set my API key as X-Auth-Token: api-key <API_KEY>.
NOTE: I am also getting CORS header ‘Access-Control-Allow-Origin’ missing but I believe it does not have anything to do with the Error 400.
Here are some code snippets from my Vue file.
axios-config.js
const instance = axios.create({
baseURL: process.env.VUE_APP_GET_RESPONSE_BASE_URL,
headers: {
'Content-Type': 'application/json',
'X-Auth-Token': `api-key ${process.env.VUE_APP_GET_RESPONSE_API_KEY}`,
}
});
Vue
import axios from "#/services/axios-config.js";
user: {
name: "",
campaign: {
campaignId: `${process.env.VUE_APP_GET_RESPONSE_CAMPAIGN_ID}`
},
email: "",
customFieldValue: {
customFieldId: "interest",
value: []
}
}
async callSubscribeAPI() {
try{
const response = await axios.post("/contacts",this.user);
console.log(response);
}catch(error){
console.log("error");
console.log(error);
}
}
This works for me:
(async() => {
const url = 'https://api.getresponse.com/v3/accounts';
const payload = await axios({
method: 'get',
url,
headers: {
"X-Auth-Token": "api-key 12345*****",
"content-type": "application/json"
}
});
console.log("payload", payload.data);
})()
I am using nuxt.js (frontend) and laravel6 (backend) API. Laravel app is running http://172.16.10.86:8000 port and nuxt.js app is running http://localhost:3000. I want to send data in my MySQL database using nuxt.js app. When I send POST request from my nuxt.js app it redirects properly but does not insert any data in database. Inside network tab, I found a 404 error.
methods:{
async register(){
try {
await this.$axios.post('/auth/register',this.form);
} catch(e) {
return;
}
this.$auth.login({data: this.form});
this.$router.push({name:'index'});
}
nuxt.config.js
auth: {
strategies: {
local: {
endpoints:{
login:{
url: '/auth/login',method: 'post', propertyName: 'token'
},
user:{
url:'me', method: 'get', propertyName: 'data'
},
logout:{
url:'logout', method: 'get'
}
}
}
},
axios:{
baseUrl:'http://172.16.10.86:8000/api'
},
I believe you just typo in your axios config.
It's should be baseURL not baseUrl. Please take a look at the docs.
You need to add full URL for the API server.
methods:{
async register() {
try {
await this.$axios.post('http://172.16.10.86:8000/api/auth/register', this.form);
} catch(e) {
return;
}
this.$auth.login({data: this.form});
this.$router.push({name:'index'});
}
I have a contact form on my front end react application that I want to post to an Lambda function that is behind a API Gateway which in turn has a custom domain on top of it.
My front end runs on domain dev.example.com:3000
My API Gateway is on contact.example.com
Further more, I have created my Lambda function with serverless and in my YAML file, have enabled CORS as so:
# serverless.yml
service: contact-form-api
custom:
secrets: ${file(secrets.json)}
provider:
name: aws
runtime: nodejs8.10
stage: ${self:custom.secrets.NODE_ENV}
region: us-east-1
environment:
NODE_ENV: ${self:custom.secrets.NODE_ENV}
EMAIL: ${self:custom.secrets.EMAIL}
DOMAIN: ${self:custom.secrets.DOMAIN}
iamRoleStatements:
- Effect: "Allow"
Action:
- "ses:SendEmail"
Resource: "*"
functions:
send:
handler: handler.send
events:
- http:
path: email/send
method: post
cors: true
I am using AXIOS to make my post request which happens client side:
const data = await axios.post(
"https://contact.example.com/email/send",
formData,
{
"Content-Type": "application/json",
}
)
And the error I get is:
Access to XMLHttpRequest at 'https://contact.example.com/email/send' from origin 'http://dev.example.com:3000' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains the invalid value 'example.com'.
I would have thought having the front end and API on the same domain would get around any cors errors (although I am spoofing dev.example.com) locally in order to test). I would also think the cors setting in my YAML file would get around it.
Anybody know why I might still be getting this CORS error?
Edit: Showing handler code that runs in Lambda function
// handler.js
const aws = require('aws-sdk')
const ses = new aws.SES()
const myEmail = process.env.EMAIL
const myDomain = process.env.DOMAIN
function generateResponse (code, payload) {
return {
statusCode: code,
headers: {
'Access-Control-Allow-Origin': "*",
'Access-Control-Allow-Headers': 'x-requested-with',
'Access-Control-Allow-Credentials': true
},
body: JSON.stringify(payload)
}
}
function generateError (code, err) {
console.log(err)
return {
statusCode: code,
headers: {
'Access-Control-Allow-Origin': "*",
'Access-Control-Allow-Headers': 'x-requested-with',
'Access-Control-Allow-Credentials': true
},
body: JSON.stringify(err.message)
}
}
function generateEmailParams (body) {
const { email, name, content } = JSON.parse(body)
console.log(email, name, content)
if (!(email && name && content)) {
throw new Error('Missing parameters! Make sure to add parameters \'email\', \'name\', \'content\'.')
}
return {
Source: myEmail,
Destination: { ToAddresses: [myEmail] },
ReplyToAddresses: [email],
Message: {
Body: {
Text: {
Charset: 'UTF-8',
Data: `Message sent from email ${email} by ${name} \nContent: ${content}`
}
},
Subject: {
Charset: 'UTF-8',
Data: `You received a message from ${myDomain}!`
}
}
}
}
module.exports.send = async (event) => {
try {
const emailParams = generateEmailParams(event.body)
const data = await ses.sendEmail(emailParams).promise()
return generateResponse(200, data)
} catch (err) {
return generateError(500, err)
}
}
You should add an HTTP request header as 'Content-type':
Now, go to Integration requests, and change the mapping template as follows:
Hope, it helps. and don't forgot to deploy the API before testing.