이메일 양식 검사와 패스워드 양식 검사를 통과한 후 SIGN ON 버튼을 클릭 했을 때, Client는 (웹 브라우저) Server로 HTTPS / POST 방식으로 email과 password를 전송해야 한다.
(물론 그 전에 email 중복 체크를 해야 한다.)
Client에서 Server로 HTTP 형식의 데이터를 전송하기 위해서 사용하는 방법으로는 Fetch API와 Axios가 잇다.
Fetch API는 HTTP 파이프라인을 구성하는 요청과 응답 등의 요소를 JavaScript에서 접근하고 조작할 수 있는 인터페이스를 제공한다. Fetch API가 제공하는 전역 fetch() 메서드로 네트워크의 리소스를 쉽게 비동기적으로 취득할 수도 있습니다.
Axios는 node.js와 브라우저를 위한 Promise 기반 HTTP 클라이언트로 비동기 통신 라이브러리.
대체로 큰 차이는 없으나 Library 설치 유무 및 브라우저 지원 여부 및 사용법에 따라 결정하면 된다.
Fetch API : 라이브러리 미 설치
Axios : 라이브러리 설치, Fetch 보다 다양한 브라우저 지원
https://blog.logrocket.com/axios-vs-fetch-best-http-requests/
기존에 Axios를 사용해 API를 개발했기 때문에 Axios를 사용할 예정이다.
Axios를 이용해서 회원 가입 정보를 Server로 (Spring Boot) 전달한다.
Axios 라이브러리를 설치한다.
npm install axios
Axios를 통한 통신을 관리하기 위해서
1. .env 파일을 통해 서버 URL을 설정한다.
ios를 통해서 서버와 통신하기 위해서는 서버 URL을 사용해야 한다. 매번 URL을 입력하거나, URL이 바뀔 경우 일괄적으로 처리하기 위해서 .env 파일을 생성한다.
2. util 폴더를 생성해 http 통신 관련 모듈을 관리한다.
src 폴더 밑 util 폴더를 생성하고, 폴더 밑에 http-common.js 파일을 생성해 axios 밑 http 통신에 필요한 정보를 관리한다.
3. service 폴더를 생성해 request를 관리한다.
src 폴더 밑 service 폴더를 생성하고, 폴더 밑에 필요에 따라 js 파일을 생성해 axios 요청을 생성한다.
해당 요청이 필요시에는 vue파일에서 js를 import 해서 사용한다.
.env 파일은 프로젝트 폴더 최상위에 생성한다.
파일을 생성한후 서버 URL 주소를 입력한다.
Spring Boot를 실행하게 될 경우 8080(http) 톰캣 서버가 실행되는 걸 알 수 있다.
SSL 인증서가 없어 http로 실행된다. 인증서를 추가해 http ->https로 변경해 보안을 강화해야 한다.
LOCAL_DEVELOP_SERVER_URL = http://localhost:8080/
이후 src 폴더 밑에 util 폴더를 생성하고 http-common.js 파일을 생성한다.
js 파일에 axios를 import 하고 통신할 서버를 설정한다.
import axios from "axios";
export default axios.create({
baseURL: process.env.LOCAL_DEVELOP_SERVER_URL,
headers: {
"Content-Type": "application/json",
},
});
마찬가지로 src 폴더 밑에 service 폴더를 생성하고 UserService.js를 생성한다.
import http from "@/util/http-commons"
export default class UserService {
async postSignUp(params) {
// params
// userEmail : [email Type (string)]
// passowrd : [password Type (string)]
// return : boolean (creation result)
return await http.post("user/signUp", params).then((data) => data.data)
}
}
Axios를 정의한 @/util/http-commons를 호출하고 http.post 방식으로 정보를 전달한다.
parmas를 매개변수로 받아서 호출 시 파라미터를 전달할 수 있도록 설정한다. [URL]/user/signUp 으로 POST 하도록 설정한다.
async와 await를 이용해서 비동기 처리를 하도록 한다.
SginUpPage.vue에서 [SIGN UP] 버튼이 눌렸을 때, Axios를 통해 POST 요청을 보내도록 적용한다.
위에서 UserService.js에 axios를 사용하기 위한 셋팅을 해두었으므로 import한 후 parameter를 적절하게 설정하면 된다.
Script 부분에서 import를 시켜준다. data에 userService를 생성하고 created 될 때, userService에 UserService 클래스를 담을 수 있도록 설정한다.
import UserService from '../service/UserService'
export default {
created() {
this.userService = new UserService();
},
data: () => ({
form: false,
email: null,
password: null,
passwordVerify: null,
loading: false,
userService: null,
[SIGN UP] 버튼이 눌렸을 때, methods의 onSubmit()이 호출 되므로 입력된 정보를 양식에 맞게 설정한 후 전달한다.
methods: {
onSubmit () {
if (!this.form) {
alert('Please follow the input form')
return
}
this.loading = true
let userData = {
userEmail : this.email,
userPassword : this.password
}
this.userService.postSignUp(userData).then(data => {
if(!!data) alert('Creation has been completed.')
else alert('A problem has occurred.')
})
this.loading = false
},
입력 양식을 체크한 후 userData 변수를 생성해서 param 양식을 맞춘다. userService에서 미리 정의해 두었던 postSignUp을 호출한다.
.then을 통해서 return data에 따라 alert 메시지를 다르게 표시한다.
SIGN UP 시도 시 404 에러가 발생한다. Spring Boot를 통한 톰캣 서버를 실행하지 않아 서버를 못찾아서 발생한 에러로 서버를 설정한 후 최종 테스트를 진행한다.
현재 vue는 아무런 설정을 하지 않았기 때문에 localhost:8080으로 실행중이다. 포트를 변경해 포트가 겹치지 않도록 설정한다.
package.json 파일안에 "scripts" 부분을 변경한다. "serve" 부분을 변경해 실행시 포트 3000으로 실행되도록 설정한다.
"scripts": {
"serve": "vue-cli-service serve --port 3000",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
Spring Boot를 본격적으로 진행하기 전에 Axios를 통해 값이 정상적으로 넘어갔는지만 확인하기 위해 간단하게 수신만 하도록 설정한다.
Intellij를 실행해서 전에 실행했던 프로젝트를 띄운다. 임시적으로 사용할 tempController를 만들어준다.
@RestController 어노테이션을 이용해서 REST API에 사용됨을 명시한다. 이후 @RequestMapping 어노테이션을 통해서 "/user"에 해당하는 url을 가져온 후 tempController 클래스에 userSignUp의 ResponseEntity를 생성한다. return으로 Boolean 값을 설정한다.
package com.toyproject.toyproject;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/user")
public class tempController {
@PostMapping("signUp")
public ResponseEntity<?> userSignUp(@RequestBody List<Map> userInfo) {
System.out.println(userInfo);
return new ResponseEntity<Boolean>(Boolean.TRUE, HttpStatus.OK);
//return new ResponseEntity<Boolean>(Boolean.FALSE, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Spring Boot 실행하고 서버가 정상적으로 작동하는 것을 확인하고 다시 테스트 진행.
테스트 결과... POST 요청 URL이 .env 설정한데로 지정되지 않는 현상으로 인해 Error가 발생했다.
console.log("url : ", process.env.LOCAL_DEVELOP_SERVER_URL)
userService.js에 console.log 추가한 후 콘솔에 출력시 undefined 발생... 구글링 결과 .env 파일에서 사용할려면 VUE_APP을 PREFIX로 지정해야 한다.
.env 파일
VUE_APP_LOCAL_DEVELOP_SERVER_URL = http://localhost:8080/
http-commons.js 파일
import axios from "axios";
export default axios.create({
baseURL: process.env.VUE_APP_LOCAL_DEVELOP_SERVER_URL,
headers: {
"Content-Type": "application/json",
},
});
UserService.js 파일
import http from "@/util/http-commons"
export default class UserService {
async postSignUp(params) {
// params
// userEmail : [email Type (string)]
// passowrd : [password Type (string)]
// return : boolean (creation result)
console.log("url : ", process.env.VUE_APP_LOCAL_DEVELOP_SERVER_URL)
return await http.post("user/signUp", params).then((data) => data.data)
}
}
파일 수정 후 실행 시 동일한 현상으로 차단되는 경우 서버를 재실행
https://stackoverflow.com/questions/55510326/vue-cli-3-environment-variables-all-undefined
실행 결과 다른 에러가 발생했다.
Network Error 발생... CORS로 인해 발생한 에러로 CORS를 설정한다.
tempController 어노테이션 위에 @CrossOrigin 어노테이션을 추가한다.
tempController.java
@RestController
@RequestMapping("/user")
@CrossOrigin(origins = { "*" }, methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE} , maxAge = 6000)
public class tempController {
@PostMapping("/signUp")
public ResponseEntity<?> userSignUp(@RequestBody List<Map> userInfo) {
System.out.println(userInfo);
return new ResponseEntity<Boolean>(Boolean.TRUE, HttpStatus.OK);
//return new ResponseEntity<Boolean>(Boolean.FALSE, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@CrossOrigin 어노테이션을 적용해도... 동일한 에러가 발생
찾아보니 Spring Security에서 CORS를 적용하기 위해서는 설정이 추가로 필요하다.
Spring security에서 CORS설정해서 진행할려 했지만... 최근에 업데이트가 된건지 찾았던 코드들은 모두 삭제가 되었다...
Spring Boot와 Spring Security를 공부하고 CORS를 적용한 후에 테스트를 다시 수행한다.
SignUpPage.vue
<template>
<v-sheet class="pa-12" rounded>
<v-card class="mx-auto px-6 py-8" max-width="60%" :elevation="12">
<v-form
v-model="form"
@submit.prevent="onSubmit"
>
<v-text-field
v-model="email"
:readonly="loading"
:rules="[rules.required, rules.email.expertTest, emailDuplicateCheck, rules.email.lengthCheck]"
class="mb-2"
clearable
label="Email"
></v-text-field>
<v-text-field
v-model="password"
:readonly="loading"
:rules="[rules.required, rules.passowrd.expertTest, rules.passowrd.lengthCheck]"
type="password"
clearable
label="Password"
placeholder="Enter your password"
></v-text-field>
<v-text-field
v-model="passwordVerify"
:readonly="loading"
:rules="[rules.required, passwordMatchCheck]"
type="password"
clearable
label="Password"
placeholder="Enter your password"
></v-text-field>
<br>
<v-btn
:disabled="!form"
:loading="loading"
block
color="primary"
size="large"
type="submit"
variant="elevated"
>
Sign Up
</v-btn>
</v-form>
</v-card>
</v-sheet>
</template>
<script>
import UserService from '../service/UserService'
export default {
created() {
this.userService = new UserService();
},
data: () => ({
form: false,
email: null,
password: null,
passwordVerify: null,
loading: false,
userService: null,
rules: {
email: {
expertTest: v => {
const emailExpert = /^[A-Za-z0-9_\\.\\-]+@[A-Za-z0-9\\-]+\.[A-Za-z0-9\\-]+/
return !!emailExpert.test(v) || 'It must be in email format.'
},
lengthCheck: v => {
return v.length < 50 || 'The email you entered is too long.'
}
},
passowrd: {
expertTest: v => {
const passwordExpert = /^(?=(?:[^a-zA-Z]*[a-zA-Z]))(?=(?:\D*\d))(?=(?:[^\W_]*[\W_])).{8,}$/
return !!passwordExpert.test(v) || 'It must be at least 8 characters and a combination of 3 or more letters, numbers, and special characters.'
},
lengthCheck: v => {
return v.length < 50 || 'The password you entered is too long.'
}
},
required: v => !!v || 'Field is required'
},
}),
methods: {
onSubmit () {
if (!this.form) {
alert('Please follow the input form')
return
}
this.loading = true
let userData = {
userEmail : this.email,
userPassword : this.password
}
this.userService.postSignUp(userData).then(data => {
if(data) alert('Creation has been completed.')
else alert('A problem has occurred.')
})
this.loading = false
},
emailDuplicateCheck (email) {
console.log(email)
// duplicateCheck: v => this.emailDuplicateCheck(v) || 'This is a duplicate email. Please use a different email.',
return true;
// true가 아닐 경우 'This is a duplicate email. Please use a different email.',
},
passwordMatchCheck (passwordVerify) {
return (this.password === passwordVerify) || 'It does not match your password.'
},
},
}
</script>
.env
VUE_APP_LOCAL_DEVELOP_SERVER_URL = http://localhost:8080/
http-common.js
import axios from "axios";
export default axios.create({
baseURL: process.env.VUE_APP_LOCAL_DEVELOP_SERVER_URL,
headers: {
"Content-Type": "application/json",
},
});
UserService.js
import http from "@/util/http-commons"
export default class UserService {
async postSignUp(params) {
// params
// userEmail : [email Type (string)]
// passowrd : [password Type (string)]
// return : boolean (creation result)
console.log("url : ", process.env.VUE_APP_LOCAL_DEVELOP_SERVER_URL)
return await http.post("user/signUp", params).then((data) => data.data)
}
}
package.json
{
"name": "toy-project",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve --port 3000",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@mdi/font": "5.9.55",
"axios": "^1.5.1",
"core-js": "^3.8.3",
"roboto-fontface": "*",
"vue": "^3.2.13",
"vue-router": "^4.2.4",
"vuetify": "^3.3.11",
"webfontloader": "^1.0.0"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-router": "^5.0.8",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"vue-cli-plugin-vuetify": "~2.5.8",
"webpack-plugin-vuetify": "^2.0.0-alpha.0"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}
tempController.java
package com.toyproject.toyproject;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@CrossOrigin(originPatterns = "http://localhost:3000")
@RestController
@RequestMapping("/user")
public class tempController {
@PostMapping("/signUp")
public String userSignUp(@RequestBody List<Map> userInfo) {
System.out.println(userInfo);
return "returnValue";
//return new ResponseEntity<Boolean>(Boolean.TRUE, HttpStatus.OK);
//return new ResponseEntity<Boolean>(Boolean.FALSE, HttpStatus.INTERNAL_SERVER_ERROR);
}
@GetMapping("/test")
public String userSignUp() {
return "Test";
}
}