Upload file using vue.js and spring-boot with axios - api

If have a form with a text field and file upload option. The file is called start.vue(say). From that a module.js(say) file is called. Using that service.js(say) is called. That service.js calls the api in moveController.java(say). I am getting error as: Current request is not a multipart request
Tried to find the syntax so that multipart header is retained till api is called. module.js is getting the appropriate file value. But no idea about service.js
start.vue is:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
export default {
name: "BatchMove",
computed: {
loading() {
return this.$store.state.loader.loading;
},
msgs() {
return this.$store.state.moveBatchHistory.msg;
}
},
components: {},
data() {
return {
file: '',
emailId: ''
};
},
methods: {
submitFile() {
console.log("logging email... " + this.emailId);
const { emailId, file } = this;
this.$store.dispatch("moveBatchHistory/ans", { file, emailId });
},
handleFileUpload(){
this.file = this.$refs.file.files[0];
}
}
}
</script>
module.js is:
import {
moveBatchHistoryService
} from '../_services';
export const moveBatchHistory = {
namespaced: true,
state: {
msg: null
},
actions: {
ans({commit}, {file, emailId}) {
moveBatchHistoryService.getSomething(file, emailId).then(
response => { commit("ans", response); }
);
}
},
mutations: {
ans(state, response) {
state.msg = response
}
}
}
service.js is:
import config from '../config';
import {authHeader} from '../_helpers';
import axios from 'axios';
import {store} from '../_store';
export const moveBatchHistoryService = {
getSomething
};
function getSomething(file, emailId) {
var headers = authHeader();
const requestOptions = {
method: 'POST',
headers: headers,
params: {
"file": file,
"Email": emailId
}
};
return axios(`${config.apiUrl}/api/moveBatch`, requestOptions)
.then(response => {
store.dispatch("alert/errorTime", "We are here!");
return response.data;
}).catch(() => {
store.dispatch("alert/errorTime", "Unable to process your request this time. Please try again latter.");
});
}
'''
moveController.java is:
#RequestMapping(value = "/moveBatch", method = RequestMethod.POST)
public void singleFileUpload(#RequestParam("file") MultipartFile file, String Email) throws Exception {}
current request is not multipart request in moveController.java

I have done the following way.
import config from '../config';
import {authHeader} from '../_helpers';
import axios from 'axios';
import {store} from '../_store';
export const moveBatchHistoryService = {
getSomething
};
function getSomething(file, emailId) {
let formData = new FormData();
formData.append('file', file);
formData.append('Email',emailId);
return axios.post(`${config.apiUrl}/api/moveBatch`, formData, { headers: { 'Content-Type': 'multipart/form-data' } })
.then(response => {
store.dispatch("alert/successTime", "Successfully completed the request!");
return response.data;
}).catch(() => {
store.dispatch("alert/errorTime", "Unable to process your request this time. Please try again latter.");
});
}

I am not very good at vue.js but for posting the data you could refer this post
As for as backed is concern you have not mentioned whether the email is #RequestParam, #RequestBody. Use this below for that
#RequestMapping(value = "/moveBatch", method = RequestMethod.POST)
public ResponseEntity singleFileUpload(#RequestPart("Email") String Email,
#RequestPart("file") MultipartFile dataFile) throws IOException {
System.out.println(String.format("Data - %s", email));
return ResponseEntity.ok().build();
}
So when you upload File along with any body or Param the content-type will be multipart/form-data so make sure in client side you add the type
so why #RequestPart, if you see the source code they have said the below
Annotation that can be used to associate the part of a "multipart/form-data" request with a method argument.
Look into it.

Related

Connect.sid not in cookie even with withCredentials:true in axios (rtk-query / react-native)

I am using rtk-query and an axiosbasequery set up like this:
export const apiSlice = createApi({
reducerPath: "apiSlice",
baseQuery: baseQueryWithErrorHandling,
endpoints: (builder) => ({
//endpoints
})
})
baseQueryWithErrorHandling just logs out the app in the event of a 401 error.
export const baseQueryWithErrorHandling = async (
args: IBaseQuery,
api: BaseQueryApi,
extraOptions: {}
) => {
const result = await rawBaseQuery(args, api, extraOptions);
if (result.error) {
if (result.error.status == 401) {
api.dispatch(logOut());
return result;
}
}
return result;
};
rawbaseQuery:
export const rawBaseQuery: BaseQueryFn<
IBaseQuery,
unknown,
FetchBaseQueryError
> = async ({ url, method, data, params }, api, extraOptions) => {
const query = axiosBaseQuery({
baseUrl: Globals.URLS.BASE_URL,
headers: (headers) => {
headers["Accept"] = "application/json";
headers["Content-Type"] = "application/json";
return headers;
},
});
return query({ url, method, data, params }, api, extraOptions);
};
And finally, the actual axios query. I put withCredentials in two spots.
import type { BaseQueryFn, FetchBaseQueryError } from "#reduxjs/toolkit/query";
import axios from "axios";
import type { AxiosRequestConfig, AxiosError } from "axios";
import Globals from "../globals/Globals";
axios.defaults.withCredentials = true; // Here (Spot #1)
export interface IAxiosBaseQuery {
baseUrl?: string;
headers?: (headers: { [key: string]: string }) => { [key: string]: string };
}
export interface IBaseQuery {
url: string;
params?: { [key: string]: string | number | Boolean };
method: AxiosRequestConfig["method"];
data?: AxiosRequestConfig["data"];
error?: {
status: number;
data: unknown;
};
}
export const axiosBaseQuery = ({
baseUrl = "",
headers,
}: IAxiosBaseQuery): BaseQueryFn<
IBaseQuery,
unknown,
FetchBaseQueryError
> => async ({ url, method, data, params }, api, extraOptions) => {
try {
const result = await axios({
url: params?.skipBaseURL ? url : baseUrl + url,
method,
...(params && { params: params }),
...(headers && { headers: headers({}) }),
...(data && { data: data }),
responseType: "json",
withCredentials: true, // And here (Spot #2)
});
return { data: result.data };
} catch (axiosError) {
let err = axiosError as AxiosError;
// error logic
}
};
Yet, it seems sometimes the cookie is passed, and sometimes not.
Here's my flipper log of a successful request with a connect.sid in the cookie.
Yet, the next request results in an error because the session id doesn't exist.
Any suggestions? I've been having this problem for a week.

Vue Apollo Upload file crashes Node Maximum call stack size exceeded at _openReadFs

I am trying to setup front end for graphQl file upload with Apollo-boost-upload. The backend code is based on this link
https://dev.to/dnature/handling-file-uploads-with-apollo-server-2-0-14n7.
It's now reaching the resolver breakpoint after adding the following line in the server.js file
const { apolloUploadExpress } = require("apollo-upload-server");
app.use(apolloUploadExpress({ maxFileSize: 1000000000, maxFiles: 10 }));
And after modifying the schema for the upload type
scalar Upload
Here is the Vue component
<input
type="file"
style="display:none"
ref="fileInput"
accept="image/*"
#change="upload"
>
//Upload method
upload({ target: { files = [] } }) {
if (!files.length) {
return;
}
this.logoImage = files[0];
},
//Dispatching action from vue component
this.$store.dispatch("uploadLogo", { image: this.logoImage });
//Vuex action
const uploadLogo = async (context, payload) => {
context.commit("setLoading", true);
try {
const { data } = await apolloClient.mutate({
mutation: UPLOAD_LOGO,
variables: {file: payload.image},
context: {
hasUpload: true,
},
});
context.commit("setLoading", false);
console.log("Logo:", data.uploadLogo);
} catch (error) {
context.commit("setLoading", false);
console.log(error);
}
};
//Mutation
export const UPLOAD_LOGO = gql`
mutation uploadLogo($file: Upload!) {
uploadLogo(file: $file) {
_id
path
filename
mimetype
user {
_id
}
}
}
`;
// Apolloclient config on main.js
import ApolloClient from "apollo-boost-upload";
import { InMemoryCache } from "apollo-boost";
import VueApollo from "vue-apollo";
// Set up Apollo Client
export const defaultClient = new ApolloClient({
uri: "http://localhost:4000/graphql",
cache: new InMemoryCache({
addTypename: false,
}),
fetchOptions: {
credentials: "include",
},
request: (operation) => {
// if no token in local storage, add it
if (!localStorage.someToken) {
localStorage.setItem("someToken", "");
}
// operation adds the token to authorizatrion header, which is sent o backend
operation.setContext({
headers: {
authorization: "Bearer " + localStorage.getItem("someToken"),
},
});
},
onError: ({ graphQLErrors, networkError }) => {
if (networkError) {
console.log("[networkError]", networkError);
}
if (graphQLErrors) {
for (const error of graphQLErrors) {
console.dir(error);
if (error.name === "AuthenticationError") {
// set auth errir in state
store.commit("setError", error);
// signout user to clear error
store.dispatch("signUserOut");
}
}
}
},
});
Here is the updated typedef (old code commented out) from backend if that helps to identify the issue
const logoUploadTypeDefs = gql`
type File {
_id: ID!
path: String!
filename: String!
mimetype: String!
encoding: String!
user: User
}
# input Upload {
# name: String!
# type: String!
# size: Int!
# path: String!
# }
scalar Upload
type Mutation {
uploadLogo(file: Upload!): File
}
type Query {
info: String
logo: File!
}
`;
Now, the Node app crashes with the following log
I had to change "apollo-upload-server" to "graphql-upload"
change 1:
Commented out "apollo-upload-server" and used "graphql-upload"
// const { apolloUploadExpress } = require("apollo-upload-server");]
const {
graphqlUploadExpress, // A Koa implementation is also exported.
} = require("graphql-upload");
And in the middleware, used this
change 2:
app.use(graphqlUploadExpress());
await apolloServer.start();
instead of old code
app.use(apolloUploadExpress());// Not to be used
await apolloServer.start();
Also, in the resolver, I added this
change 3:
Import Upload from graphql-upload in the resolver file
const { GraphQLUpload } = require("graphql-upload");
....
....
const resolvers = {
// This maps the `Upload` scalar to the implementation provided
// by the `graphql-upload` package.
Upload: GraphQLUpload,
Query: {
....
}
Mutations: {
....
}
}
Refer to Apollo Docs for more details. This fixed the issue of Node crashing with error "Maximum call stack size exceeded at _openReadFs..."

how to intercept 401 in App.vue on mounted hook Vuejs (Quasar)

I have a serviceRoot file that construct the axios instance. I'm using different services also to trigger the API depending of the data. (I will not speak about these services here).
My problem is, I want to kick people when they come back on the website after their tkn is expired.
So I would like to put a code in the App.vue file on the mounted hook, because it's for me the best moment to check if the user gets a 401 error when he tries to load the website directly on the /profil url.
import VueCookies from 'vue-cookies'
import Conf from '../../conf.js'
export default class HttpClient {
headers = {
Authorization: '',
'Content-Type': '',
Accept: ''
}
service = {}
method = ''
userId = ''
constructor() {
const tkn = VueCookies.get('tkn')
const token = `Bearer ${tkn}`
// eslint-disable-next-line prettier/prettier
this.headers.Authorization = token
this.headers['Content-Type'] = 'application/json;charset=utf-8'
this.headers.Accept = 'application/json'
this.service = Axios.create({
baseURL: Conf.api_url,
headers: this.headers,
data: this.data
})
this.service.interceptors.response.use(this.handleSuccess, this.handleError)
console.log(this.handleSuccess, this.handleError)
}
handleSuccess(response) {
return response
}
handleError(error) {
return Promise.reject(error)
}
request = async (axios, config) => {
if (
config.method === 'POST' ||
config.method === 'PUT' ||
config.method === 'DELETE' ||
config.method === 'GET'
) {
console.log('trigger api')
}
const request = {
method: config.method || this.method,
url: config.url,
data: config.data,
headers: config.headers ? config.headers : this.headers,
params: config.params,
responseType: config.responseType
}
if (config.headers) {
request.headers = {
...request.headers,
...config.headers
}
}
axios.defaults.headers.post['Content-Type'] = 'multipart/form-data'
try {
const response = await axios(request)
return response.data
} catch (error) {
throw error
}
}
}
But with that kind of serviceRoot I really don't know how to do that in the app.vue, I tried this:
<script>
import { defineComponent } from '#vue/composition-api'
import Axios from 'axios'
export default defineComponent({
name: 'App',
data() {
return {
layout: 'div'
}
},
created() {
console.log('CREATED')
Axios.interceptors.response.use(undefined, function(err) {
return new Promise(function(resolve, reject) {
if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
console.log('DISPATCH')
// this.$store.dispatch(logout)
}
throw err
})
})
}
})
</script>
But in that case, the Axios.interceptors doen't work with the first file so I wont work.
I tried to import HttpClient from './services/serviceRoot' but the browser says:
Uncaught TypeError: Super expression must either be null or a function.
I can kick directly on serviceRoot after the throw error, but I can't diferency 401's errors (because there are diffents kind of 401s and I wanna only kick people with wront token on the load of the app.
Thanks
Interceptor should go in main.js file
axios.interceptors.response.use(response => {
return response;
}, error => {
if (error && error.message === 'Request failed with status code 401') {
localStorage.removeItem('user');
router.push({name: loginPageName});
}

How to set authorization header coorectly?

Problem:
In my react native app in order to remove repeated calls I have developed a general POST GET methods in httpClient file. It code is look likes this.
import axios from 'axios';
import AsyncStorage from '#react-native-community/async-storage';
axios.defaults.headers.post['Content-Type'] = 'application/json';
var instance = null;
const setAuthorisationHeder = async () => {
const token = JSON.parse(await AsyncStorage.getItem('auth_data'));
if (token) {
console.log('>>>>>> instance', instance);
Object.assign(instance.headers, {
Authorization: 'Bearer' + token.accessToken,
});
} else {
console.log('>>>>>> instance', instance);
Object.assign(instance.headers, {
Authorization: '',
});
}
};
export const setHeader = () => {
console.log('>>>>>>>> HIIII');
instance = axios.create({
baseURL: '',
timeout: 150000,
headers: {
'Content-Type': 'application/json',
},
});
instance.interceptors.response.use(
function (response) {
return response;
},
async function (error) {
if (error.response.status) {
if (error.response.status === 401) {
AsyncStorage.removeItem('auth_data');
} else {
throw error;
}
} else {
console.log(error);
}
},
);
};
export const Get = (route, data) => {
function getData() {
return instance.get(
route,
data == null ? {data: {}} : {data: JSON.stringify(data)},
);
}
if (instance) {
console.log('>>>>>> HIIIIii');
// setAuthorisationHeder();
return getData();
}
return setHeader().then(getData);
};
export const Post = (route, data) => {
console.log('>>>>>> route', route);
function postData() {
return instance.post(route, JSON.stringify(data));
}
if (instance) {
console.log('>>>>>> HIIIIii');
// setAuthorisationHeder();
// setAuthorisationHeder();
return postData();
}
return setHeader().then(postData);
};
Can some tell me a way to add an authorization header to this instance? My token is storing the Asyncstorage in the middle of some actions so at the beginning called I don't have the token. As my code setHeader is running only one time so I created a method call setAuthorisationHeder() function. But it is giving me can not find property .then error when I am putting a request. Can someone help me to solve this issue? Thank you?
you can define global headers once and use it in every network call.
https://github.com/axios/axios#global-axios-defaults
Create a global auth variable where you'll store the auth data from storage. Before making a request get the auth data and use interceptor to set the bearer token.
let authToken = '';
const getAuthToken = async () => {
// asumming auth token was saved as string
authToken = await AsyncStorage.getItem('auth_data');
};
Interceptor
// request interceptor
axiosInstance.interceptors.request.use(
function (config) {
// Do something before request is sent
config.headers.Authorization = `Bearer ${authToken}`;
return config;
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);
complete code
import axios from 'axios';
import AsyncStorage from '#react-native-community/async-storage';
let authToken = '';
const axiosInstance = axios.create({
baseURL: '',
timeout: 150000,
headers: {
'Content-Type': 'application/json',
},
});
// request interceptor
axiosInstance.interceptors.request.use(
function (config) {
// Do something before request is sent
config.headers.Authorization = `Bearer ${authToken}`;
return config;
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);
const getAuthToken = async () => {
// asumming auth token was saved as string
authToken = await AsyncStorage.getItem('auth_data');
};
export const Get = async (route, data = {}) => {
// get and set auth token
await getAuthToken();
// route = /user?id=787878 or /user/787878
return await axiosInstance.get(route);
};
export const Post = async (route, data = {}) => {
await getAuthToken();
return await axiosInstance.post(route, data);
};

Apollo Server & 4xx status codes

Currently, my Apollo Server(running on HapiJS) returns HTTP 200 for every request, including failed ones.
I would like the GraphQL server to return HTTP 4xx for unsuccessful requests. The primary reason for it is that I want to set up monitoring for my ELB.
I know that Apollo Server has an engine platform, but I want to implement it using my current infrastructure.
Any ideas of how I could accomplish that? I tried to capture 'onPreResponse' event for my HapiJS server but I couldn't modify status code there.
After reading this answer. Here is a solution by modifying the hapijs plugin graphqlHapi of hapiApollo.ts file.
server.ts:
import { makeExecutableSchema } from 'apollo-server';
import { ApolloServer, gql } from 'apollo-server-hapi';
import Hapi from 'hapi';
import { graphqlHapi } from './hapiApollo';
const typeDefs = gql`
type Query {
_: String
}
`;
const resolvers = {
Query: {
_: () => {
throw new Error('some error');
},
},
};
const schema = makeExecutableSchema({ typeDefs, resolvers });
const port = 3000;
async function StartServer() {
const app = new Hapi.Server({ port });
graphqlHapi.register(app, { path: '/graphql', graphqlOptions: { schema } });
app.ext('onPreResponse', (request: any, h: any) => {
const response = request.response;
if (!response.isBoom) {
return h.continue;
}
return h.response({ message: response.message }).code(400);
});
await app.start();
}
StartServer()
.then(() => {
console.log(`apollo server is listening on http://localhost:${port}/graphql`);
})
.catch((error) => console.log(error));
hapiApollo.ts:
import Boom from 'boom';
import { Server, Request, RouteOptions } from 'hapi';
import { GraphQLOptions, runHttpQuery, convertNodeHttpToRequest } from 'apollo-server-core';
import { ValueOrPromise } from 'apollo-server-types';
export interface IRegister {
(server: Server, options: any, next?: Function): void;
}
export interface IPlugin {
name: string;
version?: string;
register: IRegister;
}
export interface HapiOptionsFunction {
(request?: Request): ValueOrPromise<GraphQLOptions>;
}
export interface HapiPluginOptions {
path: string;
vhost?: string;
route?: RouteOptions;
graphqlOptions: GraphQLOptions | HapiOptionsFunction;
}
const graphqlHapi: IPlugin = {
name: 'graphql',
register: (server: Server, options: HapiPluginOptions, next?: Function) => {
if (!options || !options.graphqlOptions) {
throw new Error('Apollo Server requires options.');
}
server.route({
method: ['GET', 'POST'],
path: options.path || '/graphql',
vhost: options.vhost || undefined,
options: options.route || {},
handler: async (request, h) => {
try {
const { graphqlResponse, responseInit } = await runHttpQuery([request, h], {
method: request.method.toUpperCase(),
options: options.graphqlOptions,
query:
request.method === 'post'
? // TODO type payload as string or Record
(request.payload as any)
: request.query,
request: convertNodeHttpToRequest(request.raw.req),
});
// add our custom error handle logic
const graphqlResponseObj = JSON.parse(graphqlResponse);
if (graphqlResponseObj.errors && graphqlResponseObj.errors.length) {
throw new Error(graphqlResponseObj.errors[0].message);
}
const response = h.response(graphqlResponse);
Object.keys(responseInit.headers as any).forEach((key) =>
response.header(key, (responseInit.headers as any)[key]),
);
return response;
} catch (error) {
// handle our custom error
if (!error.name) {
throw Boom.badRequest(error.message);
}
if ('HttpQueryError' !== error.name) {
throw Boom.boomify(error);
}
if (true === error.isGraphQLError) {
const response = h.response(error.message);
response.code(error.statusCode);
response.type('application/json');
return response;
}
const err = new Boom(error.message, { statusCode: error.statusCode });
if (error.headers) {
Object.keys(error.headers).forEach((header) => {
err.output.headers[header] = error.headers[header];
});
}
// Boom hides the error when status code is 500
err.output.payload.message = error.message;
throw err;
}
},
});
if (next) {
next();
}
},
};
export { graphqlHapi };
Now, when the GraphQL resolver throws an error, the client-side will receive our custom response with Http status code 400 instead of 200 status code with GraphQL errors response.
General from the browser:
Request URL: http://localhost:3000/graphql
Request Method: POST
Status Code: 400 Bad Request
Remote Address: 127.0.0.1:3000
Referrer Policy: no-referrer-when-downgrade
The response body is: {"message":"some error"}