[WEB] Jenkins Permission denied 에러 해결법

1. 문제 상황 Jenkins에서 다음과 같이 build 전 shell 명령어를 실행시키도록 했는데, 다음과 같이 Permission denied 에러가 발생했다. sudo가 없으면 조작 권한이 없고, sudo가 있으면 관리자 권한이 없는

nato-blog.tistory.com

  • 조금만 더 생각해보면 되는 거였다

 

1. Jenkins 서버에 환경 변수 파일 생성

 

  • Jenkins가 가동되고 있는 서버에 환경 변수 파일을 생성한다.
  • .env파일은 Vue.js 애플리케이션에서 사용할 파일으로, 각종 key와 token이 들어있다

 

 

2. Build시 환경 변수 파일 주입

 

  • Jenkins에서 실제로 build작업이 일어나는건 Invoke Gradle script이다
  • 상단에 Execute shell 작업을 추가, 생성한 .env파일을 필요로 하는 위치로 복사한다

 

+ jenkins 자체에 사용할 환경 변수를 설정 가능하다고 하는데 시간상 찾아보지 못했다...

1. 문제 상황

 

  • A페이지에서 데이터를 Vuex에 저장 후 B페이지로 이동
  • B페이지는 Vuex에서 데이터를 호출해 사용
  • B페이지에서 제대로 데이터를 호출하지 못하는 문제 발생

 

2. 문제 원인

 

  • B페이지에서 Vuex 호출시 await/async 처리를 해봤다
    • 효과가 없었음, Vuex는 알아서 동기적으로 작동한다
    • 정확히는, action은 비동기적이지만 mutation은 동기적으로 작동한다 (링크)
  • 그렇다면 왜 Vuex의 동기가 깨졌는가?
    • A페이지의 Vuex가 비동기적 동작 이후에 호출되기 때문
    recentPage() {
      axios
        .get("/api/recent", {
          params: {
            userId: this.userId,
          },
        })
        .then((res) => {
          this.$store.commit("SET_COURSE", {
            id: res.itemId,
            len: res.len,
            rate: res.rate,
            data: res.data,
          });
        });
        
      router.push("/recent");
    },
  • 위 코드를 보면, axios를 통한 get 호출 후에 Vuex에 commit하게 된다
    • axios를 기다리는동안 router.push로 페이지가 넘어가버리니, 제대로 불러올 수 없음

 

 

3. 해결 방법

 

    async recentPage() {
      await axios
        .get("/api/recent", {
          params: {
            userId: this.userId,
          },
        })
        .then((res) => {
          this.$store.commit("SET_COURSE", {
            id: res.itemId,
            len: res.len,
            rate: res.rate,
            data: res.data,
          });
        });
      router.push("/recent");
    },
  • async, await를 추가하면 axios가 동기적으로 작동, 이후 router.push가 실행되면서 문제가 해결된다

1. 문제 상황

 

  • Jenkins에서 다음과 같이 build 전 shell 명령어를 실행시키도록 했는데,

  • 다음과 같이 Permission denied 에러가 발생했다.
    • sudo가 없으면 조작 권한이 없고, sudo가 있으면 관리자 권한이 없는 상태이다

 

 

 

 

2. 해결 방법

 

  • 수정할 파일이 조금이면 각 파일의 권한을 변경하면 된다
chmod 755 file.txt
  • 수정할 파일이 많다면, Jenkins가 사용하는 계정을 수정할 필요가 있다
    • ubuntu의 모든 계정 확인하기
sudo cat /etc/passwd

누가 봐도 jenkins

  • jenkins 계정 설정 변경
    • 계정 설정 파일 열기
sudo visudo
  • jenkins ALL=(ALL) NOPASSWD: ALL 을 입력 후 ctrl+O로 저장, ctrl+X로 종료한다

  • 이제 jenkins 계정에 대해서는 비밀번호를 묻지 않으므로, sudo 명령어가 정상 작동하게 된다.

작업 환경 : Gitlab, Jenkins, gradle

 

 

1. jenkins, java 설치 후 구동

 

  • Java 설치
// 자바 설치, 필요한 버전으로 수정
sudo apt-get install openjdk-11-jdk
// 버전확인 
java --version

버전 확인

  • Jenkins 설치
//키 다운로드
wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -
echo deb http://pkg.jenkins.io/debian-stable binary/ | sudo tee /etc/apt/sources.list.d/jenkins.list
//키 확인
apt-key list
//jenkins 설치
sudo apt-get install jenkins
  • Jenkins 실행 port 변경
    • 기본 port인 8080은 tomcat 등 다른 서비스들도 많이 사용하는 포트라, 변경해주는게 편하다
    • /etc/sysconfig/jenkins 또는 /etc/default/jenkins 파일의 HTTP_PORT를 변경

  • Jenkins 구동
systemctl start jenkins // 구동
systemctl status jenkins // 상태확인
systemctl restart jenkins // 재시작
systemctl stop jenkins // 종료

status 확인

 

 

2. Jenkins 설정

 

  • 초기 비밀번호 확인
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
  • 최초 접속 후 설정

초기 비밀번호 입력
Install suggested plugins
관리자 계정 생성

  • 플러그인 설치
    • Jenkins관리 - 플러그인 관리
    • Gitlab, Publish over SSH, Gradle(보통 위에서 설치됨)

  • Global Tool Configuration 설정
    • JDK 및 Gradle을 버전에 맞춰 설정
//java 위치 확인
which javac
readlink -f [which javac의 결과]

 

 

 

3. Item 생성 및 빌드 설정

 

  • 새로운 Item - Freestyle Item 생성

  • Item - 구성 - 소스 코드 관리

  • Repository URL : 관리할 gitlab url 입력

  • Gitlab 계정정보 추가(Credentials Add)
    • Username에 로그인 ID, Password에 비밀번호 입력
    • ID와 Description은 원하는대로 기입 가능하다

  • 빌드할 branch명 지정

 

  • Gradle 빌드 설정 추가
    • Invoke Gradle script

 

  • 빌드 설정
    • 생성한 Gradle 지정
    • clean(사용할 workspace 청소), build(빌드) 입력
    • 우측 하단 [고급...] 버튼으로 메뉴 확장
    • Build할 File(backend/build.gradle) 지정

 

빌드 파일 지정

  • Item 화면에서 Build Now를 누르면 테스트 가능하다

성공

 

4. Push/Merge시 자동 빌드 설정

 

  • Item 구성 - 빌드 유발 설정
    • Push, Accepted Merge, Closed Merge(차이가 뭐지?) 설정
    • GitLab webhook URL 확인
    • 우측 하단 [고급...] 버튼으로 메뉴 확장
    • Secret token 발급받기 (재확인 불가이니 저장해 둘 것)

이벤트 설정
Secret Token 발급

  • Gitlab 프로젝트에 webhook 설정
    • Settings - Webhooks 진입 (maintainer 권한 필요)
    • URL : Jenkins의 GitLab webhook URL
    • Secret token : Jenkins의 Secret token
    • Trigger : push, merge 선택
    • 하단의 Add webhook으로 추가

 

  • webhook 작동 확인
    • Test - Push events를 선택해 상단에 HTTP 200이 응답되면 정상 작동
    • push 이벤트를 보냈으므로 Jenkins에서도 자동 

webhook push 테스트
테스트에 자동 빌드하는 Jenkins

 

5. 자동 배포 설정

 

  • 이번 프로젝트에선 Nginx가 :80, :443 요청을 localhost:xxxx로 proxy하도록 설정
    • 즉, Nginx는 가만히 두고 localhost만 재시작해주면 자동 배포로 동작한다
  • SSH 전송을 위한 서버 설정
    • Jenkins 홈 - Jenkins 관리 - 시스템 설정 - Publish over SSH
    • key 경로 지정 혹은 직접 입력

 

 

  • 접속할 서버 설정
    • 이름, IP, 계정명, base 디렉토리 설정 (이 경우는 home 디렉토리)

 

  • Item 구성 - 빌드 후 조치 - Send build artifacts over SSH 추가

 

  • 파일 전송 설정값 지정
    • Source files : 옮길 파일, 이 경우 방금 build한 jar파일의 경로 지정
    • Remove prefix : Source files 입력 내용 중 prefix, 폴더경로에 해당하는 것들
    • Remote directory : 전송받을 폴더
      • 위에서 /home/account를 base 디렉토리로 설정했으므로, /home/account/test 폴더에 jar파일이 저장된다
    • Exec command : 전송 후 실행할 명령어
      • 기존 xxxx port를 쓰던 프로세스를 죽이고
      • 지금 전송한 jar를 xxxx port로 사용하겠다
fuser -k [xxxx]/tcp
// 주의. 절대경로로만 입력할 것
nohup java -jar /home/account/test/*.jar --server.port=[xxxx] >> server.log 2>&1&

 

  • 이제 develop branch에 push하거나 merge하면 자동으로 빌드 - 배포까지 해준다

Vue 프론트에서 S3에 파일 업로드하고, 파일 이용을 위해 S3 경로만 백엔드로 전달 / DB에 저장한 후 사용하였다

 

 

1. 초기화

import AWS from "aws-sdk"
import JSZip from 'jszip'

var albumBucketName = process.env.VUE_APP_BUCKET_NAME;
var bucketRegion = process.env.VUE_APP_BUCKET_REGION;
var IdentityPoolId = process.env.VUE_APP_IDENTITY_POOLID;

AWS.config.update({
  region: bucketRegion,
  credentials: new AWS.CognitoIdentityCredentials({
    IdentityPoolId: IdentityPoolId
  })
});

var s3 = new AWS.S3({
  apiVersion: '2006-03-01',
  params: {Bucket: albumBucketName}
});

AWS를 import한다

파일 압축이 필요해 JSZip도 import했는데, 필요 없다면 제거해도 무방하다

.env파일에서 버켓 이름, 지역, ID를 가져오고, AWS를 설정한다

 

 

2. 이미지(들) 업로드

	uploadPhoto: async (albumName, elId) => {
		var files = document.getElementById(elId).files;
		if (!files.length)return;

		var photoKeyList = [];
		var promises = [];
		
		files.forEach(async file => {
			var fileName = file.name;
			var albumPhotosKey = albumName + '/';
			var slice = fileName.split(".");
			var photoKey = albumPhotosKey + slice[0] + "_" + new Date().getTime() + "." + slice[1];
			
			const result = s3.upload({
				Key: photoKey,
				Body: file,
				ACL: 'public-read'
			}).promise().then(function () {
				photoKeyList.push(photoKey);
			})
			await promises.push(result);
		})
		
		await Promise.all(promises);
		return photoKeyList;
	},

사진이 업로드될 앨범의 이름과 사진을 받는 elements의 id  두 가지를 인자로 받는다

여러장의 사진을 한번에 올릴수 있도록, forEach를 반복하며 s3에 업로드한다

이때, 업로드는 비동기적으로 진행되므로, 빈 list를 반환하지 않도록 반복문에서는 promises에 업로드 정보를 저장하고, Promise.all로 한번에 처리한다

 

사진 이름의 중복을 고려해, 사진의 원본 이름에 현재 시간을 추가해 업로드한다

(이 경우, 서로 다른 두 사용자가 같은 이름의 사진을 milli? nano?초 단위로 동시에 업로드하면 하나가 삭제된다는 문제가 발생하긴 하는데.... 토이프로젝트 수준이라 일단 넘어갔다) 

 

완료 후에는 사진 경로들이 저장된 photoKeyList가 반환된다

 

사용 예시

<v-file-input id="imgfile" filled dense></v-file-input>
<button @click="upload"></button>

...

<script>
	method:{
    	async upload() {
			let ImgUrl = await awss3.uploadPhoto(
				"img",
				"imgfile"
			);
		}
	}
</script>

 

 

3. 압축파일 업로드

uploadZip: () => {
	var files = document.getElementById('photoupload').files;
	// make zip file
	const zip = new JSZip;
	
	files.forEach( file => {
		zip.file(file.name, file, { base64: true })	
	})
	var albumPhotosKey = 'zip/';
	var zipName = new Date().getTime() + ".zip";
	var photoKey = albumPhotosKey + zipName;
	zip.generateAsync({ type: 'blob' })
		.then(function (content) {
			s3.upload({
				Key: photoKey,
				Body: content,
				ACL: 'public-read'
			},
			function (err, data) {
				if (err) {
					return;
				}
				else {
					console.log(data);
				}
			});
		})
	return photoKey;
}

압축할 file들을 찾고, key와 name을 설정하는것까진 위와 큰 차이가 없다

 

별도의 이름이 없어 현재 시간만으로 사용하였다

(사용에는 별 문제가 없지만, 사람이 작업할 때 좋지 못하다는걸 느꼈다.. 하지만 코스트 문제로 인해 넘어갔다)

 

JSZip lib을 이용해 압축파일을 생성하였고, 생성 완료와 동시에 s3에 업로드했다

generateAsync가 이름값대로 async하게 작동하는지, 반환값이 있음에도 문제가 생기는 일은 없었다

 

 

4. 이미지, 압축파일 삭제하기

deletePhoto(photoKeys, zipKey) {
	photoKeys.forEach(photoKey => {		
		s3.deleteObject({ Key: photoKey }, function (err) {
			if (err) { return err; }
		});
	});
	if (zipKey) {
		s3.deleteObject({ Key: zipKey }, function (err) {
			if (err) { return err; }
		});
	}		
}

DB에서만 삭제하면 서비스 이용에는 문제가 없겠지만.. S3 용량에 문제가 생길것 같았다

따라서 삭제 함수를 작성해, 글이 삭제되거나 할때 같이 호출해 삭제되도록 했다

 

photoKey는 DB에서 가져온 img 이름들의 url List이며, s3에 접근해 삭제한다

zipKey는 역시 zip파일을 위한 삭제과정으로, zip이 없으면 역시 제거해도 무방하다

이번엔 반환값이 없으므로 별도의 동기 처리는 하지 않았다

 

 

5. 이미지 수정하기

updatePhoto: async (albumName, photoKey, elId) => {
	var file = document.getElementById(elId).files;
	if (file.length == 0) {
		return;
	}
	if (photoKey && photoKey != 'profileImg/noImg_1628231352109.png') {
		awss3.deletePhoto([photoKey], "");
	}
	return await awss3.uploadPhoto(albumName, elId);
},

글 수정 과정에서 기존 이미지가 다른 이미지로 바뀌는 일이 있다

이 경우 기존 이미지를 삭제하고, 새 이미지를 업로드한 후 DB저장을 위해 새로운 경로를 반환하였다

 

 

6. 이미지, 압축파일 다운로드하기

<a
	:href=" 'https://' + BUCKET_NAME + '.s3.' + BUCKET_REGION + '.amazonaws.com/' + downloadUrl"
	download
>
</a>

a태그의 href에 S3 파일 URL을 넣어주면 알아서 다운로드 된다

이미지를 띄우고 싶을때도 동일하게 img태그의 src에 넣으면 된다

 

 

7. 전체 코드

import AWS from "aws-sdk"
import JSZip from 'jszip'

var albumBucketName = process.env.VUE_APP_BUCKET_NAME;
var bucketRegion = process.env.VUE_APP_BUCKET_REGION;
var IdentityPoolId = process.env.VUE_APP_IDENTITY_POOLID;

AWS.config.update({
  region: bucketRegion,
  credentials: new AWS.CognitoIdentityCredentials({
    IdentityPoolId: IdentityPoolId
  })
});

var s3 = new AWS.S3({
  apiVersion: '2006-03-01',
  params: {Bucket: albumBucketName}
});

export const awss3 = {
	updatePhoto: async (albumName, photoKey, elId) => {
	
		var file = document.getElementById(elId).files;
		if (file.length == 0) {
			return;
		}
		if (photoKey && photoKey != 'defaultImg.png') {
			awss3.deletePhoto([photoKey], "");
		}
		return await awss3.uploadPhoto(albumName, elId);

	},

	uploadPhoto: async (albumName, elId) => {
		var files = document.getElementById(elId).files;

		if (!files.length) {
			return;
		} 

		var photoKeyList = [];

		var promises = [];
		
		files.forEach(async file => {
			var fileName = file.name;
			var albumPhotosKey = albumName + '/';

			var slice = fileName.split(".");

			var photoKey = albumPhotosKey + slice[0] + "_" + new Date().getTime() + "." + slice[1];
			
			const result = s3.upload({
				Key: photoKey,
				Body: file,
				ACL: 'public-read'
			}).promise().then(function () {
				photoKeyList.push(photoKey);
			})

			await promises.push(result);

		})
		
		await Promise.all(promises);
		
		return photoKeyList;
	},

	uploadZip: () => {
		var files = document.getElementById('photoupload').files;

		// make zip file
		const zip = new JSZip;
		
		files.forEach( file => {
			zip.file(file.name, file, { base64: true })	
		})

		var albumPhotosKey = 'album/';
		var zipName = new Date().getTime() + ".zip";
		var photoKey = albumPhotosKey + zipName;
		zip.generateAsync({ type: 'blob' })
			.then(function (content) {
				
				s3.upload({
					Key: photoKey,
					Body: content,
					ACL: 'public-read'
				},
				function (err, data) {
					if (err) {
						return;
					}
					else {
						console.log(data);
					}
				});
			})
		return photoKey;
	},
	
	deletePhoto(photoKeys, zipKey) {
		photoKeys.forEach(photoKey => {		
			s3.deleteObject({ Key: photoKey }, function (err) {
				if (err) { return err; }
			});
		});

		if (zipKey) {
			s3.deleteObject({ Key: zipKey }, function (err) {
				if (err) { return err; }
			});
		}		
	}
}

export default awss3;

 

'WEB 공부' 카테고리의 다른 글

[WEB] Vuex 동기화 문제 해결  (0) 2021.10.06
[WEB] Jenkins Permission denied 에러 해결법  (0) 2021.10.06
[WEB] Jenkins로 CI/CD 구축하기  (0) 2021.10.05
[WEB] Spring - DI  (0) 2021.05.18
[WEB] Cookie & Session  (0) 2021.05.16
[WEB] Backend - EL, JSTL  (0) 2021.05.16
[WEB] Backend - Servlet, JSP  (0) 2021.05.16

<한줄요약>

Spring : 자바 객체를 담고 있는 컨테이너, 객체의 생성/사용/제거 등 관리

 

1. Spring이란?

 

-  JEE에서 사용하는 대부분의 기능 + @를 제공하는 경량화 Framework

-  Low level을 자동으로 처리해주어 개발자가 상위 logic에 집중할 수 있도록 한다

-  외부 lib를 쉽게 가져다 쓸 수 있다

 

 

2. Spring 구조

 

-  1. POJO

-  Plain Old Java Object

-  특정 시스템/기술에 종속적이지 않은 자바 객체. 

-  종속성이 없어 생산/이식이 용이하다

 

-  2. PSA

-  Portable Service Abstraction

-  기술에 일관된 방식을 사용해 접근 ( 추상화 )

-  Service Abstraction이란? 어떤 기능을 사용자에게 드러내지 않고 시스템이 자동으로 처리해주는 것

-  Portable Service Abstraction은? 비슷한 여러 기능을 하나로 통합해 사용자에게 제공하는 것

-  ex) DB에 접근하는 법은 JDBC를 비롯해 여러가지가 있지만, @Trancaction으로 트랜잭션 일괄 처리

 

-  3. IoC/DI

-  Inversion of Control (제어 반전)

-  객체의 생성, 의존 등 생명주기 관리를 개발자가 하는게 아닌, 컨테이너가 하게 된다

-  Depedency Injection

-  객체를 생성해두고, 외부에서 의존성을 설정하면 객체가 주입되도록 한다

-  직접 생성자를 호출하거나, 함수를 통해 받을 필요가 없다

 

-  4. AOP

-  Aspect Oriented Programming

-  관점 지향 프로그래밍

-  관점을 기준으로 모듈화, 핵심 로직과 공통(부가) 로직으로 분리한다

-  공통 사용 부분은 공통 모듈로 분리하고 필요할 때 호출해서 사용한다

-  ex) 로그를 남기고 싶을때, 로그 기록 모듈을 별도로 생성 후 로깅이 필요한 부분에서 호출

-  재사용성의 증가

 

 

3. IoC

 

-  Object간의 연결 관계를 런타임에 결정한다

-  DI : IoC의 여러 방법 중 하나

-  객체 간 결합도를 떨어트려 유지보수 용이성을 높인다

 

IoC의 종류

-  Dependency Lookup

-  직접 lookup해서 필요한 자원을 얻는 방식

-  type casting 필요

 

-  Dependency Injection

-  클래스 간 의존관계를 표기해두면(xml, @ annotaion 등) 자동으로 연결해주는 것

-  lookup 불필요

 

Container란?

-  객체의 생성, 소멸 등 생애주기를 관리

-  객체 사용에 필요한 기능들 제공

-  Dependency 제공

-  Thread 처리 등

 

IoC Container

-  코드 대신 컨테이너가 object의 생성, 사용, 제거를 담당

-  컨테이너가 obj를 제어하므로 IoC Container라 한다

 

Bean

-  spring이 생성,관리하는 객체

 

Spring DI Container (BeanFactory)

-  Bean의 생명주기를 관리, BeanFactory라고도 호칭한다

-  Bean을 등록, 생성, 조회

-  getBean()으로 조회 가능

 

ApplicationContext

-  BeanFactory의 기능을 확장한 것

-  spring의 여러 서비스 추가 제공

 

IoC 장점

-  객체 간 결합도 감소

-  결합도가 높으면? 한 class의 코드만 변경되도 다른 코드들까지 다 변경이 일어날 가능성이 높다

 

-  결합도의 감소 예시

 

-  1. class가 아닌 interface를 사용

-  interface만 동일하면 class는 얼마든 수정되도 상관 없다

-  interface가 수정될 때는 수정 필요

 

-  2. Factory 이용

-  팩토리가 클래스를 생성, interface가 변경되도 팩토리만 수정한다

-  class는 팩토리만 호출

-  팩토리에 의존하는 형태

 

-  3. Assembler 이용

-  runtime에 클래스 관계를 설정, 객체를 주입해준다

-  Spring Container에서 Bean으로 객체를 생성하면, 이를 getBean으로 가져다 쓰는 식

 

 

4. DI

 

Bean 생성 방법

-  1. XML 파일

-  자기 정보를 기술

-  기본적으로 singleton이다.

-  singleton이 아니게 하려면 scope="prototype"설정

-  <bean>태그 사용

<bean id="world" class="com.hello.world" />
//not singleton
<bean id="world" class="com.hello.world" scope="prototype" />

 

-  2. annotation

-  자동으로 bean 등록, 주입

-  xml에 component-scan으로 검색할 패키지를 설정

//context 추가 필요
<context:component-scan base-package="com.hello.*" />

-  @annotation을 설정해두면 자동으로 검색, bean으로 생성한다

표기 의미, 종류
@Repository DAO와 같은, 데이터베이스 접근 클래스에 사용
@Service service 클래스에 사용
@Controller servlet같은 controller에 사용
@Component 그 외 나머지에 사용

 

Dependency Injection (DI)

-  객체 간 의존관계를 외부에서 대신 수행한다

-  IoC의 구현방법, 결합도가 느슨하다

 

DI 종류

-  외부 assembler에서 의존성 주입

 

-  1. xml 이용

-  주입할 객체를 xml파일에 설정

<bean id="world" class="com.test.hello.world" />
//not singleton
<bean id="world" class="com.test.hello.world" scope="prototype" />

-  class가 ~hello.world인 클래스를 주입받는 bean 객체 생성

-  world라는 id로 호출 가능하다

 

-  객체의 획득은 Container의 함수로 제공받는다

ApplicationContext context = new ClassPathXmlApplicationContext("com/test/hello/applicationContext.xml");
CommonService world = context.getBean("world", world.class);

 

-  bean 생성시 생성자에 인자가 필요할 경우, constructor-arg태그를 사용한다

-  다른 bean을 인자로 받을 경우 value 대신 ref 사용, 인자는 해당하는 id값이다

<bean id="world" class="com.test.hello.world">
	<constructor-arg value="50" />
</bean>
//위에서 생성한 world bean을 생성자의 인자로 받는 새 객체
<bean id="world2" class="com.test.hello.world2">
	<constructor-arg ref="world" />
</bean>

 

-  생성한 객체에 값을 주입할 경우, property 태그를 사용한다

-  단, 해당 이름의 setter가 설정되 있어야한다. setArg1이 있을 경우 name="arg1"로 사용 가능

-  동일하게 value, ref 사용

//50을 arg1로, world객체를 arg2로 받는 객체
//world2 클래스 내부에 setArg1, setArg2 함수 필수
<bean id="world2" class="com.test.hello.world2">
	<property name="arg1" value="50" />
	<property name="arg2" ref="world" />
</bean>

 

-  주입할 값에 따라 <list> <map> 등 태그 존재

//인자로 list를 받는 객체
<bean id="world2" class="com.test.hello.world2">
	<property name="argList">
		<list>
			<value type="java.lang.Integer">50</value> // int 50 저장
			<ref bean="world" />
		</list>
    </property>
</bean>

//인자로 map를 받는 객체
<bean id="world2" class="com.test.hello.world2">
	<property name="argMap">
		<map>
			<entry key="key1" value="val1" />
			<entry key="key2" value-ref="world" />
		</map>
    </property>
</bean>

 

-  2. annotation 사용

-  @Autowired

-  타입에 맞춰 주입해준다

-  동일한 type이 여러개일 경우 @Qualifier("name") Annotation을 추가해 구분

 

-  @Resource

-  타입에 맞춰 주입해준다

-  동일한 type이 여러개일 경우 @Resource(name="name1") 으로 구분

 

-  @Inject

-  이름으로 주입해준다(Spring 3 지원)

-  표준 Annotation으로, Spring 종속적이지 않다

-  javax.inject-x.x.x.jar 필요

 

<한줄요약>

Cookie : client측에 저장되는 연결 관련 정보

Session : server측에 저장되는 연결 관련 정보

 

1. Cookie

 

Cookie의 필요성

-  client에 정보를 유지해야 하는 경우가 있다 (미 로그인 상태의 장바구니/최근 본 목록 등은 서버에서 저장 불가)

-  client - server 간 연결 상태를 유지해야 하는 경우가 있다 (로그인 상태를 계속 유지하는 것)

-  요청 - 응답만 수행하고 연결을 해제하는 HTTP로는 해결 불가능

 

-  해결 : Cookie

-  서버에서 client측에 저장하는 정보, key:value 쌍으로 이루어진다

-  브라우저는 request시 header에 쿠키를 넣어 서버로 전송

-  Browser마다 각각 관리된다. ( 크롬에서 naver 로그인을 해도, firefox에선 다시 로그인해야한다)

 

-  Cookie의 사용

-  세션관리, 트래킹(광고), 개인별 맞춤 서비스 등에 이용

-  ID저장, 자동로그인, N일간 다시 보지 않기 등등 다 쿠키를 이용한 것이다

 

-  Cookie 구성요소

요소 설명
name 각 쿠키별 이름
value 해당 쿠키의 값
domain 쿠키의 전송 domain
ex) mypage.com
path 쿠키를 전송 세부 경로
ex) /login
maxAge 쿠키의 유효기간

 

-  Cookie 동작

-  client의 page 요청

-  server의 Cookie 생성

-  Cookie를 포함하여 응답

-  Cookie를 PC에 저장. 다음 요청시 Cookie를 함께 전송 (유효기간이 남았다면)

 

-  Cookie 생성/설정하기

//생성하기
Cookie ck = new Cookie(name, value);

//Value, Path, Domain, MaxAge에 대한 getter/setter 존재
//ex) value 획득, 변경하기
String val = ck.getValue();
ck.setValue(val);

//cookie client로 전송
response.addCookie(ck);

//client의 cookie얻기
Cookie[] cks=request.getCookies();

 

 

2. Session

 

-  client가 server에 접속해있는 하나의 상태 : session

-  session이 만료되었습니다 : 제한 시간이 지나 server에 더이상 해당 session이 존재하지 않는다는 뜻

-  server의 메모리에 저장된다

 

-  session동작

-  client가 page 요청

-  client가 session-id를 보냈다면 해당 id로 session 탐색, 진행

-  보내지 않았다면, 서버에서 신규 session-id 생성 후 client에 반환

-  재접속 시 client는 기존의/새로 받은 session-id를 서버로 보내 즉시 인증

-  session-id를 식별자로, 서버 메모리에 저장되는 data

 

-  cookie에 비해 보안이 좋다

-  cookie와는 달리 용량제한이 없다

 

 

'WEB 공부' 카테고리의 다른 글

[WEB] Jenkins로 CI/CD 구축하기  (0) 2021.10.05
[Frontend] 프론트에서 AWS S3에 이미지 압축 / 업로드 / 다운로드 / 삭제하기  (0) 2021.08.22
[WEB] Spring - DI  (0) 2021.05.18
[WEB] Backend - EL, JSTL  (0) 2021.05.16
[WEB] Backend - Servlet, JSP  (0) 2021.05.16
[JAVA] JDBC  (0) 2021.05.16
[WEB] Frontend - Bootstrap  (0) 2021.05.15

<한줄요약>

EL : JSP의 표현식을 대체해 쉽게 사용하도록 한다

JSTL : JSP 자바 코드의 대체 방법을 제공하는 Library

 

1. EL이란?

 

-  Expression Language

-  JSP에서 표현식, <%= %>을 대신하는 언어

 

기능

-  JSP 4가지 scope 내부 속성 쉽게 사용

-  Java class의 method 호출

-  연산자 지원

 

 

2. EL 사용

 

-  scope의 자바 객체로부터 key/속성값을 꺼내 사용한다

-  객체는 java의 map / bean이어야한다

-  dot 방식 : 객체.key

-  [] 방식 : 객체[key]

-  EL은 null값을 공백으로 표현한다

 

EL 내장객체

객체명 설명
pageContext 현재 page의 instance
pageScope page scope의 객체들
requestScope request scope의 객체들
sessionScope session scope의 객체들
applicationScope application scope의 객체들
param
paramValues
전달된 parameter(들)
cookie 쿠기 정보

 

 

3. EL을 이용한 객체 접근

 

-  JSP 사용시

-  <%= request.getAttribute("emp").getId() %>

 

-  EL 사용시

-  ${ requestScope.emp.id }

-  ${ emp.id } ( 별도 scope 지정 없을시, page -> request -> session -> application 순서로 탐색 )

 

-  저장된 객체의 key에 . 문자가 포함되있을 경우, []만 사용 가능하다

-  ex) setAttribute("hello.world", test); 로 저장했을 경우

-  ${ hello.world } - hello객체는 존재하지 않음

-  ${ requestScope[hello.world] } 으로 사용해야한다

 

-  method 접근

-  ${ requestScope.emp.function() }  == ${ emp.function() }

-  ${emp.function}은 emp 객체의 getFunction()함수를 뜻한다. 주의

 

-  Cookie 접근

-  기본 객체인 cookie 사용

-  ${ cookie.id.val }

-  존재하지 않으면 null 반환

 

 

4. EL 연산자

 

-  산술 연산자 : +, -, *, /, %

-  관계 연산자 : ==, !=, <, >, <=, >=

-  3항 연산자 : con? val1 : val2;

-  논리 연산자 : &&, ||, !

-  유효값 검증 : empty ( null, 빈 문자열, 배열, MAP에 대해 true를 반환)

 

 

5. JSTL이란?

 

-  JSP Standard Tag Library

-  JSP를 확장하는 태그 라이브러리

-  Java 코드를 직접 사용하지 않고 Tag로 제공한다

 

JSTL의 종류

library 기능 URL
core 변수 지원, 제어,반복문 지원 http://java.sun.com/jsp/jstl/core
XML XML 변환, 제어 http://java.sun.com/jsp/jstl/xml
국제화 포매팅 지원, 숫자/날짜양식 지원 http://java.sun.com/jsp/jstl/fmt
database DB SQL http://java.sun.com/jsp/jstl/sql
함수 Collection, String 처리 http://java.sun.com/jsp/jstl/function

 

6. JSTL core 사용하기

 

JSTL core tag 종류

tag 설명
set 변수 설정
remove 변수 제거
if 조건에 따라 실행
choose, when, otherwise 다중조건, if / else 구문
forEach array/collection의 각 항목별 처리
forTokens 특정 구분자로 구분된 항목별 처리
import 해당 URL 자원 삽입
redirect 해당 경로로 이동
url URL 작성
catch 예외처리
out 출력

 

JSTL core 사용 선언

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

 

JSTL로 변수 선언

//name 이름으로 value값을 가지는 변수 선언
//scope는 별도 지정 없으면 page
<c:set value="value" var="name" scope="request"/>

<c:set var="name">
value
</c:set>

 

JSTL 조건문

<c:if test="${ 조건식 }">
	//true일 경우 실행할 Body
</c:if>

<c:choose>
	<c:when test="${ 조건식 }">
		//실행할 Body
	</c:when>
	<c:otherwise>
		//위 조건이 아닐경우 실행할 Body
	</c:otherwise>
</c:choose>

 

JSTL 반복문

//list의 각 항목마다 id를 출력하는 반복문
<c:foreach var="item" items="${ list }">
	${item.id}<br>
</c:foreach>

//몇번째 항목인지 같이 출력
<c:foreach var="item" items="${ list }" varStatus="idx">
	${ idx.count }. ${item.id}<br>
</c:foreach>

//0, 2, 4, 6, 8, 10번째 항목만 출력하기
<c:foreach var="item" items="${ list }" begin="0" end="10" step="2">
	${item.id}<br>
</c:foreach>

'WEB 공부' 카테고리의 다른 글

[Frontend] 프론트에서 AWS S3에 이미지 압축 / 업로드 / 다운로드 / 삭제하기  (0) 2021.08.22
[WEB] Spring - DI  (0) 2021.05.18
[WEB] Cookie & Session  (0) 2021.05.16
[WEB] Backend - Servlet, JSP  (0) 2021.05.16
[JAVA] JDBC  (0) 2021.05.16
[WEB] Frontend - Bootstrap  (0) 2021.05.15
[WEB] Frontend - AJAX  (0) 2021.05.15

+ Recent posts