모든 리눅스에는 root 계정이 존재한다.

 

파워셀 명령어 (?)

ssh -i 키파일

ssh id@ip주소 (호스트주소)

 

 

파워셀에서 ssh root@192.168.100.128 입력하면 원격 네트워크로 접속 가능 

 

 

CentOS

우측상단 전원버튼 - wired connected   >> NIC 설정 가능

 

ifconfig : 윈도우에서 ipconfig 의 기능

 

VMWare -  Edit

 

ip 변경 가능

 

 

파워셀에서 ssh root@192.168.100.128 입력하면 원격 네트워크로 접속 가능  ( 신기해서 두 번 적음 )

< ssh 관련 접속 추천 툴 >
Xshell

 

 

 

 

리눅스 커널 - 운영체제 본체 

셀 - 운영체제와 대화를 할 수 있게 해줌 ( 사용자 인터페이스 )

 

리눅스 명령의 구조  

형식  :  명령 [옵션] [인자]    

명령 = date , ls , man, cp, cd 등 수백 가지가 있다.

옵션 = - 나 -- 로 시작하는 경우가 많고 영문 소문자나 대문자로 구성된다.

인자 = 주로 파일명이나 디렉터리 명이 사용된다. 인자는 없을 수도 있다.

 

# 프롬프트 : root 권한을 갖고 있는 계정의 프롬프트 라는 의미.

$ 프롬프트 : 일반 사용자의 프롬프트 이다.

 

 

★  기초 명령어  

 

  man [명령] : 명령어의 사용법을 알려줌 ( 유용함 ★ )

file [인자] : 인자의 파일 정보를 알려줌

pwd : 현재 위치를 확인한다.

cd [인자] : 지정한 디렉터리로 이동한다.   명령)   cd ~centos   =   centos 계정의 홈 디렉터리로 이동  >>  /home/centos

 

ls [옵션] [인자] : 디렉터리 내용 보기 
-a 숨김 파일 포함 모든 파일을 출력  ,  -F 파일의 종류 표시 
-d 디렉터리 자체 의 정보 출력,  -l ( 엘 ) 파일의 상세 정보 표시

 

mkdir [옵션] [인자] : 디렉터리 만들기 , -p 중간 단계의 디렉터리를 자동으로 생성해줌

rmdir [옵션] [인자] : 디렉터리 지우기 , -p 중간 단계의 디렉터리를 자동으로 삭제해줌

 

cat [옵션] [인자]  : 파일의 내용을 출력한다. ,  -n 행 번호를 붙여서 출력한다.

more [옵션] [인자] : 파일의 내용을 화면 단위로 출력한다.   ,  +(행 번호)  +500 = 500번째 행 부터 출력

 

tail [옵션] [인자] : 파일의 뒷부분 몇 행을 출력한다. 기본값은 10  ,  -f  주기적으로 계속 출력한다

 

cp [옵션] [인자] [인자] ...  :   -r  디렉터리를 복사할 때 지정한다

두 인자가 모두 파일인 경우

( 앞 인자 ) 앞 파일을   ( 뒤 인자 )  해당 이름으로 현재 디렉터리에 복사한다.

 

뒤 인자가 디렉터리인 경우

( 앞 인자 ) 앞 파일을  ( 뒤 인자 ) 해당 경로 디렉터리에 복사한다.

 

인자를 여러 개 지정하는 경우

마지막에 지정한 디렉터리로 앞서 지정한 파일들이 모두 복사 됨. 

 

cp /etc/hosts /etc/services /etc/passwd .   =  hosts, services, passwd 파일 3개를   ' . ' ( 현재 디렉터리 ) 에 복사한다. 

 

 

mv [옵션] [인자] [인자] : 파일을 이동하거나 이름을 바꾼다

앞 인자의 파일을 뒤 인자의 파일로 이름을 바꾼다. or 뒤 인자의 경로로 이동한다

 

rm -rf [인자] : 물어보지 않고 강제로 파일, 디렉터리 삭제

 

 

grep [옵션] [패턴] [인자]

 

find [경로 검색] [동작]

 

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

리눅스 파일의 종류와 특징

 

리눅스에서는 디렉터리도 파일로 취급  ★

 

ELF 파일 = 리눅스 실행 파일

심벌릭 링크 = 원본 파일을 대신하여 다른 이름으로 파일명을 지정한 것 파일명 끝에 @가 붙어있음 ( 윈도우의 바로가기 같은거 )

 

주요 디렉터리

dev = 장치 파일이 담긴 디렉터리

 

home = 홈 디렉터리 ( 윈도우로 치면 사용자. (금정산1_pc) )

 

media = cd-rom 이나 usb 같은 외부 장치를 연결하는 디렉터리

 

root = root 계정의 홈 디렉터리 ( ' / ' 디렉터리와 다른 것이므로 혼동하지 않도록 한다. )

root 계정의 home 디렉터리 이기 때문에 디렉터리 이름이 root 이다.

centos 계정의 home 디렉터리 라면 centos .

 

usr

etc = 주로 설정파일이 들어있음

tmp = 자유로운 디렉토리. 단, 재시작 시 파일이 모두 삭제 됨

var = 데이터나 로그, 내용이 자주 바뀌는 파일이 저장된다.

 

 

 

 

 

 

 

절대 경로명 : 항상 ' / ' 디렉터리 부터 시작한다.

 

상대 경로명 : ' / ' 이외의 문자로 시작한다. 현재 디렉터리를 기준으로 작성한다.

예시)  cd 디렉터리명  /  cd ..  

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

리눅스의 일반적인 경우 

파일 = 파일 + inode + 실제 파일 데이터

inode : 파일의 메타 데이터 ( 소유자 , 소유 권한, 수정 날짜 파일의 위치 등등.. )

ls -i  로 inode 확인 가능.

파일 이름이 달라도 inode 번호가 같다면 같은 파일.

 

하드디스크에는 파일의 실제 데이터가 여기저기 분산 되어 저장되어있다. 

 

하드 링크 만들기 명령어 

ln [원본 파일] [링크 파일] : 파일의 링크를 생성한다.   , -s  심벌링 링크를 생성한다.

하드 링크  파일은 inode가 같은 파일이다. ( 아직 뭐가 장점인지는 잘 모르겠다. )

 

하드 링크로 1기가 파일을 100개 만들어도 하드디스크에는 1기가만 소모된다.

1기가 파일을 100개 복사 했다면  100기가를 소모한다.

 


과제 

1. 사용자 홈 디렉터리에 임의의 이름으로 파일 생성    >> touch 로 생성 했음

A.jpg A.txt A.avi A.conf
B.jpg B.txt B.avi B.conf
C.jpg C.txt C.avi C.conf
D.jpg D.txt D.avi D.conf
E.jpg E.txt E.avi E.conf

2. 확장자 별로 디렉터리를 만들고 해당 파일들을 옮겨주세요   >> mkdir 로 디렉터리 만들고 ,   mv로 이동했음  
mkdir txt
mv *.txt txt

jpg txt avi conf

3. 옮긴 파일들 중에서 A가 들어간 파일들을 복사해서 별도의 디렉터리에 모아줍니다.      (/tmp/A)
find 명령어를 활용해보는 문제??  같아서  사용해보았음.
find ./*/A.* -exec mv {} /tmp/A \;          pwd 위치 : /home ( 홈 디렉터리 )
[패턴] = 현재 디렉터리 내에 하위 디렉터리에서 A로 시작하는 파일들.
find [패턴] -exec [명령어] {} [명령어 인자] \;       {} = find 명령으로 검색한 모든 파일     \;   =  exec 옵션 내용의 마지막을 알림

find 를 사용 하는게 아니라 cp를 많이 써보라는 의도 같음 !!


4. 홈 디렉터리에 있는 jpg 디렉터리는 안의 파일만 지우고    >>  
rm -r jpg  ( jpg 디렉터리 삭제 ) >> 디렉터리 내부 파일을 지우겠냐고 물어봄 ' Y ' >> 마지막에 폴더까지 지우겠냐고 물어봄 ' N '

rm -rf jpg/*  << 이게 더 좋네요

 txt 디렉터리는 통째로 디렉터리까지 삭제하세요.
rm -rf txt


5. avi 디렉터리는 통째로 /tmp 디렉터리에 복사
cp -r avi /tmp
-r 옵션으로 디렉터리 이름을 명시


6. conf 디렉터리를 통째로 /tmp/conf 디렉터리로 이동하세요.
mv conf /tmp/conf
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

5일차 금 : OSI 7계층 프로토콜 http ssl ssh dhcp FTP 등

 

 

OSI 7계층 프로토콜  FTP  DHCP   DNS   HTTP   SSL   SNMP   STMP   SMB  ...

 

 

DHCP : 네트워크 설정을 자동으로 해주는 프로토콜

DHCP 원리

네트워크 어딘가에는 DHCP 서버가 있는데, 보내는 패킷 2개 + 받아야 할 패킷 2개가 필요하다

 

1. DHCP Discover : 브로드캐스트로 요청

2. DHCP Offer : 유니캐스트로 응답함

3. DHCP Request

4. DHCP Ack : 2번과 유사

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

DNS

DNS.pcap 분석하기 예제

 

DNS요청을 하는 클라이언트 및 서버의 ip / port 는?   192.168.0.114  /  53

 

DNS는 4계층 프로토콜로 무엇을 사용하는가?     UDP

 

DNS 요청한 주소들이 어떤 것들이 있는지 파악해보자

 

특별한 에러가 없는 경우 DNS 요청과 응답에 각각 몇 개의 패킷이 필요한지 확인하자 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

SSL : Secure Socket Layer

평문 통신을 암호화 해준다.

 

 

TLS :  transport layer security

 

 

암호화의 이해

일방향 암호화   rc4, MD4, MD5, SHA256, SHA512, SHA1024, sha2048

평문  >  암호화  =  암호문

 

해시 : 복호화 할 수 없다. 

패스워드 등 중요한 정보를 암호화 하는데 사용된다.

무결성 체크 등 에도 사용된다.

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

양방향 암호화

폄문 > 암호화 > 암호문 > 복호화 > 평문

 

키 : 암호화 , 복호화 할 때 사용하는 일정 길이의 문자열

 

대칭키 : 비밀키 , 세션키

평문을 키를 이용해서 암호화 하여 상대에게 '암호문, 키'를 보낸다.

상대는 받은 암호문을 키로 복호화하여 평문을 읽게 된다.

 

단점 : 키 전달 문제. ( 키를 어떻게 전달 할 것인가? )

DES , 3DES , AES

 

 

비대칭키 : 개인키 , 공개키

암호화에 사용하는 키와 복호화에 사용하는 키가 다르다.

 

개인키로 암호화 한 것은 공개키로 복호화가 가능하고,

공개키로 암호화 한 것은 개인키로 복호화가 가능하다

개인키는 본인만 알아야 하며, 공개키는 누구든지 가질 수 있다.

 

장점 : 키 전달 문제가 해결이 됨

단점 : 속도가 느림

 

 

RSA

상대방의 공개키로 암호문을 만들어 상대에게 보낸다. 상대는 본인의 개인키로 암호문을 복호화하여 평문을 읽는다.

 

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

SSL의 원리 >> 키 신뢰의 문제를 해결하기 위해 인증기관 이라는 개념을 가져옴.

인증기관이 인증해준 신뢰성있는 해당 사이트의 공개키를 '인증서(SSL)' 라고 한다.

 

G마켓에서 인증기관에게 우리 사이트의 인증서를 만들어 달라고 요청한다.

인증기관 에서는 G마켓의 공개키와 G마켓 사이트 정보를 받고, G마켓의 공개키에 인증기관의 개인키로 암호화하여 인증서를 발급해준다.

 

클라이언트는 G마켓에 접속을하면 G마켓의 인증서를 받게 되고, 클라이언트는 G마켓의 인증서를 인증기관의 공개키로 복호화 되는 것을 확인하고

신뢰할 수 있게 된다.   ( 인증기관의 개인키로 암호화 했기 때문에, 인증기관의 공개키로 해독이 된다. )

 

클러이언트는 임의의 대칭키를 만들고 G마켓의 인증서 (공개키) 로 암호화 하여 G마켓에서도 신뢰를 얻고 사이트를 이용할 수 있다.

( G마켓 에서는 G마켓의 공개키로 암호화된 클라이언트의 대칭키를  G마켓의 개인키로 해독 할 수 있다. )

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

 

FTP : 파일 전송 프로토콜

 

지원되는 인증방식

1. 서버에 등록되어 있는 계정 인증

2. 익명계정

 

 

 

 

Active Mode ( 기본 )

서버와 클라이언트 간의 명령은 21번 포트로,  업로드, 다운로드는 20번 포트로

20번 포트에서 서버에서 클라이언트로 먼저 접속을 하는게 특징.

 

하지만 최근에는 클라이언트가 사설IP ( 공유기 ) 를 사용하는 추세라서 자주 사용되지는 않는다

 

 

 

Passive Mode 

업로드와 다운로드를 할 때 20번 포트를 열지 않고 임의의 포트를 연다.

명령과 업로드 다운로드 모두 클라이언트에서 먼저 접속을 한다.

 

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

FTP.pcap 분석하기

1. SYN을 보내는 쪽이 클라이언트   ( 192.168.0.114 )    /    서버 ( 192.168.0.193 )  3 hand shake 하는중

1번 우클릭 , Follow - TCP Stream    TCP영역의 데이터만 보여준다.  

빨간색 = 클라이언트가 서버로 보내는 값 ( 요청 )    /    파란색 = 서버가 클라이언트로 보내는 값 ( 응답 )

 

PASV = 패시브모드로 변경하겠다

 

 

맨 밑 패킷 우클릭 - Follow - TCP Stream

패시브 모드로 바꾸고 난 뒤 7254번 포트에 접속 중 !!

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

http

인터넷 웹 서비스의 핵심 프로토콜

요청, 응답 형식으로 동작함

http 응답코드

100~199 단순한 정보

200~299 클라이언트의 요청이 정상적으로 처리됨

300~399 다른 url로 재이동

400~499 클라이언트의 요청이 불완전하여 다른 정보가 필요함

500~599 서버의 오류로 클라이언트의 요청 수행 불가

 

 

http. cap 분석하기

4번 패킷( HTTP ) 우클릭 - Follow - TCP Stream

 

 

 

쿠키 - 클라이언트에 저장되어 서버에서 활용하는 상태 정보

 

http 메소드

get

post

option : 서버에서 사용 가능한 메소드 정보 리스트 요청

put : 서버의 파일 업로드

delete : 서버의 파일 삭제

 

일반적인 웹 사이트의 경우

단, api 서버의 경우는 용도가 다름

서브네팅 이어서 조금 더 ..

VLSM ( Variable Length Subnet Mask ) : 서브넷을 가변적으로 알맞게 계속 주소를 만들어주는 방법

60개 , 30개 , 10개 ...

 

 

예제

201.102.1.0/24  네트워크를 1팀 120개,  2팀 60개, 3팀, 4팀은 20개씩 네트워크로 VLSM 서브네팅을 해보세요.

 

각 팀별로 네트워크주소/prefix 방식으로 표기

 

1팀 : 201.102.1.0/25

2팀 : 201.102.1.128/26

3팀 : 201.102.1.192/27

4팀 : 201.102.1.224/27

 

 

예제2

133.200.0.0/16 네트워크를 다음과 같은 개수로 할당해보시오

22   25   230   500   1000   2400   3500   4000

 

4000 : 133.200.0.0/20        4096개  256 * 16 

3500 : 133.200.16.0/20      4096개  256 * 16 

2400 : 133.200.32.0/20     4096개  256 * 16

1000 : 133.200.48.0/22     1024개  256 * 4 

500 : 133.200.52.0/23       512개  256 * 2 

230 : 133.200.54.0/24       256개  256 * 1 

25 : 133.200.55.0/27       32개  256 / 8

22 : 133.200.55.32/27        32개  256 / 8

 

 

사설 네트워크 IP대역

10.0.0.0 ~ 10.255.255.255   사설 네트워크에서 사용 (A클래스)
172.16.0.0 ~ 172.31.255.255 사설 네트워크에서 사용(B클래스)
192.168.0.0 ~ 192.168.255.255 사설 네트워크에서 사용 (C클래스)

 

 

 

▲7월14일 폴더 - 04. 프로토콜.pdf - 20페이지
프로토콜 헤더 구조 분석
ip 프로토콜
ip 헤더는 일반적으로 20바이트 크기
version : 4bit   ipv4, ipv6 등 ip 버전 확인용
IHL : 4bit
IP Header Length     헤더길이/4 저장
            일반적으로 5라는 값이 옴   =

TOS : Type of service : 거의 사용되지 않음. QOS와 관련

Total Length : ip헤더 길이 + 데이터 길이

Identification : 단편화된 조각을 구분하기 위한 구분자

IP FLAGS  : 단편화옵션
                D : do not frag     0       1
                M : More frag      0        1
Fragment Offset : 단편화된 조각 위치
TTL : 네트워크에서의 패킷의 수명, 3계층이상 장비를 지날때마다 1씩 감소
목적지를 못 찾고 무한루프하는 패킷을 자동으로 소멸시켜줌.

protocol : 데이터에 포함된 상위 프로토콜 종류
            01: ICMP
            06: TCP
            17: UDP

Header checksum : 헤더에 에러가 있는지 체크

보내는 사람 주소 : 4바이트
받는 사람주소 : 4바이트

기타옵션 : ip 전달에 걸린 시간값, 라우터에게 특별한 명령을 내린다 등등의 각종 옵션

 

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
TCP 프로토콜
tcp   ,   udp   비교
TCP  :  안정적인 전송이 목표 ,  연결수립  ,  윈도우 사이즈 설정을 통해 흐름제어  ,  긴 데이터 전송에 적합  ,  대부분의 프로그램에서 사용
UDP : 단순하고 빠른 데이터 전송이 목표  ,  연결수립 하지않음  ,  흐름제어 불가  ,  짧은 데이터 전송에 적합  ,  DNS  DHCP  SNMP  TFTP

 

 

 

▲ 7월14일 폴더 - 04. 프로토콜.pdf - 35페이지
출발지 포트 : 2바이트
목적지 포트 : 2바이트

Sequence numbr  : 4바이트
Acknowledgment nmber : 4바이트  > 신뢰성 있는 통신과 관련
TCP FLAGS
U : URG 긴급한 데이터가 있을 경우
A : ACK  데이터 수신 확인
P : PSH 보내는 데이터를 버퍼에 저장하지 않고 전달
R : RST 비정상인 연결 재수립 요청
S : SYN 연결 수립할 때 사용         ( 아래 사진 첫 연결 . )
F : FIN 연결 종료할 때 사용

Windows size : 수신 가능한 버퍼의 크기
Checksum : 오류 체크 ( 헤더 + 데이터 )
Urgent Pointer : 긴급 데이터의 위치를 표시 ( 거의 쓰이는 일은 없음 )
TCP Options : TCP 헤더에 없는 정보를 송신할 때 사용


1. 보내는 쪽은 임의의 숫자를 지정하여 자신의 seq를 생성
2. 데이터 송신 후 자신의 seq값에 보낸 데이터의 양 만큼의 숫자를 증가 시킴
3. 받는 쪽에서는 전송 받은 seq와 데이터 양을 확인하고 ack값을 계산해서 응답으로 보냄
4. ack는 전송 받은 데이터의 양을 표시

 

 

 

 

 


 

 

HTTP.cap  ( WireShark )
3way handshake
1. 클라이언트가 서버에게 연결 시도 : syn flag
2. 서버가 클라이언트에게 연결 허용 알림 : syn + ack flag
3. 클라이언트가 수신 확인 전송 : ack flag

4way handshake
1. 연결을 끊고자 하는 쪽에서 연결 종료 시도 : FIN + ACK Flag
2. 상대방에서 연결 종료 신호 수신 확인 : ACK Flag
3. 상대방도 연결 종료 시도 : Fin + Ack Flag
4. 연결을 끊는 쪽에서도 연결 종료 수신 확인 : ACK Flag
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
ARP 프로토콜
ARP가 어떻게 IP에 해당하는 MAC 주소를 알아오는가?

1. 대상을 찾기 위해 IP주소에 해당하는 MAC ADDRESS를 ARP로 요청 - 브로드캐스트
2. 해당 IP를 가지고 있는 호스트는 ARP요청을 전송한 호스트에게 ARP응답을 전송합니다. - 유니캐스트


 

 

Hardware Type : 2계층 프로토콜 타입, 1 ( Ethernet )
Protocol Type : 3계층 프로토콜 타입, 0x0800 ( IPv4 )

Hardware Address length : MAC address의 길이, 일반적으로 6 ( 바이트 )
Protocol address length : IP address의 길이, 4

Operation Code ( Opcode ) : 
1 = ARP요청 ( 브로드캐스트 )
2 = ARP응답 ( 유니캐스트 )
3 = RARP요청
4 = RARP응답
5~~ = 다른종류 ARP 요청/응답


ARP.pcap 분석 예제
arp 요청을 하는 호스트의 ip/mac은?  192.168.0.114  /  00:16:ce:6e:8b:24   ( 1번 sender )
arp 요청 패킷의 전송 방식 및 목적지는?   브로드캐스트 , 모든 통신장비 ㅇㅇ
arp 프로토콜의 프로토콜 주소 ( IP주소 ) / 하드웨어 주소 ( MAC 주소 )의 타입/길이는 ?   IPv4 ( 4 ) , Ethernet ( 6 ) ,
arp 응답을 하는 호스트의 ip는? MAC주소는?   192.168.0.1   /   00:13:46:0b:22:ba   ( 2번 sender )
arp 응답 패킷의 전송 방식 및 목적지는?   유니캐스트  ,    ( 2번 target )
arp 요청/응답에 따른 operation code는?  요청 1  /  응답 2

 


 

 

 

Type : 대분류     Code : 소분류

ICMP의 기능 : 오류 보고 ( Error Report ) ,  질의 메시지 ( Query )
ICMP의 메시지 유형
0 : echo 응답 ( echo reply ) > ping의 응답 : ip 호스트 진단 ( 라이브 여부 확인 )
8 : echo 요청 ( echo request ) > ping의 요청 : ip 호스트 진단 ( 라이브 여부 확인 )
3 : 수신처 도달 불가능 ( Destination Unreachable ) : 목적지 도달 불가능 알림

 


 

ICMP.pcap 분석하기 예제                                                   192.168.0.114   >   192.168.0.1
icmp 요청을 한 호스트와 대상의 ip는?         통신 2종류임     192.168.0.114   >   72.14.207.99  

icmp 패킷의 ttl값을 통해 os 추측해보자     128 = 윈도우   /  255 = 리눅스 , cisco 장비 ( 라우터 )

icmp 요청/응답의 icmp type 및 code를 보자.   요청 Type : 8   /   응답 Type : 0

ICMP2.pcap 분석하기 예제
icmp 요청한 호스트와 대상 ip?     10.2.10.2   >   10.4.88.88
icmp 요청/응답의 type 및 code?    요청 8    /   수신지 도착불가 3   ,  code 1 : 호스트를 찾지 못함
분석 결과 알 수 있는 것은?     Destination Unreachable

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

네트워크 통신의 전반적인 구조 이해가 되었을까요 ???   >> 음 .. 아니요

유니캐스트 = 일 대 일

브로드캐스트 = 일 대 동일 네트워크 모두

멀티캐스트 = 일 대 그룹 ( 구독자 )

 

 

패킷을 받는 경우

1. 유니캐스트 이면서 목적지가 자기 자신인 경우

2. 브로드캐스트인 경우

3. 멀티캐스트인 경우 자기 자신이 속한 멀티캐스트 그룹의 주소인 경우

 

해당 경우가 아닌데 패킷을 받은 경우, 조금이라도 과부하를 막기 위해 폐기

 

 

2계층 브로드캐스트 에서의 MAC주소 

FF:FF:FF:FF:FF:FF

 

3계층 브로드캐스트 주소

255.255.255.255   또는  서브넷 마스크가 255.255.255.0 일때, 네트워크주소 + .255 (가장 끝 주소)

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

 

 

OUI : 제조사 코드

UAA : 제조사에서 자체적으로 할당. 네트워크에서 각 장비를 구분할 수 있게 해줌

 

 

@@@ 재미로 확인 @@@

bit.ly/ieee_list

 

MAC주소로 제조사 확인하는 사이트

@@@@@@@@@@@@@

 

★ 이번 주 가장 핵심 내용 

             IP 주소

 

대부분의 네트워크가 TCP/IP 로 동작하므로 IP 주소 체계를 이해하는 것이 네트워크 이해에 매우 중요함 !!!!

 

IPv4 주소는 32비트 . 옥텟 이라고 부르는 8비트 단위로  " . " 을 이용하여 구분함

예시 : 192.168.0.1   ( = 이진법으로 변환 할 줄 알아야 함 )

 

 

IP 주소 체계

클래스 : IP주소를 효율적으로 배정하기 위한 개념

 

A 클래스 : 0.0.0.0 ~ 127.255.255.255

00000000.00000000.00000000.00000000 ~ 01111111.11111111.11111111.11111111

0xxxxxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx

A클래스의 서브넷마스크( 넷마스크 )

255.0.0.0

A클래스의 네트워크 개수  0 ~ 127 중   처음과 끝인 0과 127 을 제외하면 총 126개 이다.

A클래스의 호스트 개수 256 * 256 * 256 - 2 = 16,777,216 - 2 = 16,777,214 

맨 처음 호스트 주소 > 네트워크 주소 이므로 제외.

맨 끝 호스트 주소 > 브로드캐스트 주소 이므로 제외.

 

 

 

B 클래스 : 128.0.0.0 ~ 191.255.255.255

10000000.00000000.00000000.00000000 ~ 10111111.11111111.11111111.11111111

10xxxxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx

서브넷마스크 = 255.255.0.0             ( x 개수만큼 제곱 )

네트워크 개수 2^14 = 16,384 개

호스트 개수 2^16 - 2 = 65,534개  ( 네트워크, 브로드캐스트 주소 제외 )

 

 

 

C 클래스 : 192.0.0.0 ~ 223.255.255.255

11000000.00000000.00000000.00000000 ~ 11011111.11111111.11111111.11111111

110xxxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx

서브넷마스크 = 255.255.255.0   ( 강의실이랑 동일 )

네트워크 개수 2^21 = 2,097,152 개   

호스트 개수 256 - 2 = 254 개

 

 

 

D 클래스 : 224.0.0.0 ~ 239.255.255.255   > 멀티캐스트 주소로 사용함

11100000.00000000.00000000.00000000 ~ 11101111.11111111.11111111.11111111

 

 

 

 

E 클래스 : 240.0.0.0 ~ 255.255.255.255  >  예약용

11110000.00000000.00000000.00000000 ~ 11111111.11111111.11111111.11111111

 

서브넷마스크의 값으로 될 수 있는 것들?

255.255.0.0                                    /16
255.255.128.0                                /17
255.255.192.0                                 /18
255.255.224.0                                  /19
255.255.240.0                                  /20
255.255.248.0                                 /21
255.255.252.0                                 /22
255.255.254.0                                /23
255.255.255.0                                /24
255.255.255.128                             /25
255.255.255.192                             /26
255.255.255.224                              /27
255.255.255.240                             /28
255.255.255.248                                /29
255.255.255.252                                 /30

 

 

 

 

 

 

하지만 클래스 기반의 주소 체계인 클래스풀 체계는 인터넷이 상용화 되면서 

필요한 호스트 숫자가 폭발적으로 증가하였고 그 다음의 주소 체계인

 

클래스리스 네트워크가 등장하게 됩니다 

 

첫 번째 단기 대책 : Classless Inter-Domain Routing ( CIDR )

두 번째 중기 대책 : NAT와 사설 IP

세 번째 장기 대책 : IPv6

 

 

NAT 정의

사설 IP에서 공유기를 거치면, 공유기의 공인 IP로 변경이 되어 ISP ( Internet Service Provider ) 에 접속하게 된다.

출발지는 공인 IP, 도착지는 사설 IP 가 된다.

 

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

서브네팅

정의 : 원래 부여된 클래스의 기준을 무시하고 새로운 네트워크-호스트 구분 기준을 

사용자가 정하여 원래 클래스풀 단위의 네트워크보다 더 쪼개 사용하는 것을 말한다.

 

예제

1. 211.100.10.0 ~ 211.100.10.255 의 C클래스 네트워크를 네트워크당 60개의 IP를 사용 가능하도록 서브네팅 하시오. 

 

서브넷팅의 팁 : 네트워크를 반복적으로 1/2로 분할해서 구해보자.

 

1) 서브넷 마스크는?   c클래스 기준 /24 인데, 반띵을 두 번 했으니 +1  ,  +1   해서    /26   ( 255.255.255.192 )

2) 각각의 서브넷은?  211.100.10.0/26   ,   211.100.10.64/26    ,   211.100.10.128/26   ,   211.100.10.192/26

3) 서브넷의 개수는?   4개

4) 네트워크당 IP의 개수는?   62개

5) 첫 번째 서브넷의 브로드캐스트 주소는? ( 맨 마지막 호스트 주소 )   211.100.10.63

6) 두 번째 서브넷의 사용가능한 IP범위는?   211.100.10.65  ~  211.100.10.126  ( 사용가능!!! 한 것.  첫 번째와 마지막 번째 주소는 제외 )

 

 

풀이

1. 반띵을 해봅니다 

마지막 옥텟에서 0~127   /  128~255   로 반띵했습니다.

 

2. 한 번 더 반띵 해봅니다

0~63  /  64~127   ,  128~191  /  192~255   하나의 네트워크당 62개의 주소를 부여할 수 있게 됐습니다.

 

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

 

예제 2번

1. 211.100.10.0 ~ 211.100.10.255 의 C클래스 네트워크를 네트워크당 13개의 IP를 사용 가능하도록 서브네팅 하시오.  

 

128   64  32   16  

/25   /26  /27  /28

 

1) 서브넷 마스크는?   c클래스 기준 /24 ,  반띵을 네 번 했으니 +4  그러므로   ' /28 '  ( 255.255.255.240 )

 

2) 각각의 서브넷은?   총 16개

211.100.10.0/28  ,  16/28   ,   32/28   ,   48/28   ,   

64/28  ,   80/28   ,   96/28    ,   112/28

128/28   ,   144/28   ,   160/28   ,   176/28

192/28   ,   208/28   ,   224/28   ,   240/28

 

3) 서브넷의 개수는?   16개

 

4) 네트워크당 IP의 개수는?   14개

 

5) 첫 번째 서브넷의 브로드캐스트 주소는? ( 맨 마지막 호스트 주소 )   211.100.10.15

 

6) 두 번째 서브넷의 사용가능한 IP범위는?  211.100.10.17 ~  211.100.10.30    ( 사용가능!!! 한 것.  첫 번째와 마지막 번째 주소는 제외 )

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

예제 3번

 

B클래스 크기의 네트워크를 서브네팅 해보자

네트워크 주소가 151.92.0.0 인 B클래스 네트워크 (151.92.0.0/16)를 각 네트워크당 6500개의 ip가 사용가능하도록 서브네팅 해보자
255.255.224.0/19 
151.92.224.0/19

1) 서브넷 마스크는?   B클래스 기준 /16 ,  반띵을 세 번 했으니 +3  그러므로   ' /19 '   ( 255.255.224.0 )

 

2) 각각의 서브넷은?   

151.92.0.0 ~ 151.92.31.255  (  151.92.0.0/19 )

151.92.32.0 ~ 151.92.63.255  (  151.92.32.0/19 ) 

151.92.64.0 

151.92.96.0 

151.92.128.0 

151.92.160.0 

151.92.192.0 

151.92.224.0 

+32 씩 ...

 

3) 서브넷의 개수는?   8개   (  B클래스 한뭉탱이를 3번 반띵 했으니까. )

 

4) 네트워크당 IP의 개수는?   8190개

 

5) 첫 번째 서브넷의 브로드캐스트 주소는? ( 맨 마지막 호스트 주소 )   151.92.31.0

 

6) 두 번째 서브넷의 사용가능한 IP범위는?  151.92.32.1 ~ 151.92.63.254    ( 사용가능!!! 한 것.  첫 번째와 마지막 번째 주소는 제외 )


ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
네트워크 주소 ( Network Address ) : 하나의 네트워크를 통칭하기 위한 주소
브로드캐스트 주소 ( Broadcast Address ) : 특정 네트워크에 속하는 모든 호스트들이 갖게 되는 주소,
네트워크에 있는 모든 클라이언트들에게 데이터를 보내기 위함.

과제) 서브넷팅된 IP주소 구분하기.
보기 IP주소는 서브넷팅된 주소 이다.
 [가] IP주소가 속한 Network Address , 
 [나] IP주소가 속한 Network의 Broadcast Address ,
 [다] 해당 Network의 할당가능한 IP주소 대역 ,
 [라] 보기에 주어진 IP주소가 할당가능한 주소인지 확인하시오
 [마] 만약 할당할 수 없다는 주소라면 그이유를 쓰시오.
       할당할 수 있는 주소인 경우는 '없음' 쓰시오.

예)  130.56.99.120 /30  <- IP address
가. Network Address : 130.56.99.120 /30 
나. Broadcast Address : 130.56.99.123
다. 할당가능한 IP주소대역 : 130.56.99.121~ 130.56.99.122
라. 보기 IP주소는 할당가능한 IP주소예요? -> 할당하지못함.
마. 이유 : Network Address로 예약된 주소이기 때문에.
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

(1) 10.74.69.52 /29
[가] IP주소가 속한 Network Address , 10.74.69.48  /29
 [나] IP주소가 속한 Network의 Broadcast Address , 10.74.69.55
 [다] 해당 Network의 할당가능한 IP주소 대역 ,  10.74.69.49 ~ 10.74.69.54
 [라] 보기에 주어진 IP주소가 할당가능한 주소인지 확인하시오   예
 [마] 만약 할당할 수 없다는 주소라면 그이유를 쓰시오.   없음
       할당할 수 있는 주소인 경우는 '없음' 쓰시오.

(2) 85.98.46.119 /25

[가] IP주소가 속한 Network Address , 85.98.46.0   /25
 [나] IP주소가 속한 Network의 Broadcast Address ,  85.98.46.127
 [다] 해당 Network의 할당가능한 IP주소 대역 ,  85.98.46.1 ~ 85.98.46.126
 [라] 보기에 주어진 IP주소가 할당가능한 주소인지 확인하시오  예
 [마] 만약 할당할 수 없다는 주소라면 그이유를 쓰시오.   없음
       할당할 수 있는 주소인 경우는 '없음' 쓰시오.

(3) 96.13.44.146 /30

[가] IP주소가 속한 Network Address ,  96.13.44.144  /30
 [나] IP주소가 속한 Network의 Broadcast Address ,  96.13.44.147
 [다] 해당 Network의 할당가능한 IP주소 대역 ,  96.13.44.145 ~ 96.13.44.146
 [라] 보기에 주어진 IP주소가 할당가능한 주소인지 확인하시오   예
 [마] 만약 할당할 수 없다는 주소라면 그이유를 쓰시오.   없음
       할당할 수 있는 주소인 경우는 '없음' 쓰시오.

(4) 75.55.86.210 /26

[가] IP주소가 속한 Network Address , 75.55.86.192  /26
 [나] IP주소가 속한 Network의 Broadcast Address ,  75.55.86.255
 [다] 해당 Network의 할당가능한 IP주소 대역 ,   75.55.86.193 ~ 75.55.86.254
 [라] 보기에 주어진 IP주소가 할당가능한 주소인지 확인하시오   예
 [마] 만약 할당할 수 없다는 주소라면 그이유를 쓰시오.   없음
       할당할 수 있는 주소인 경우는 '없음' 쓰시오.

(5) 129.72.16.116 /28

[가] IP주소가 속한 Network Address ,  129.72.16.112  /28
 [나] IP주소가 속한 Network의 Broadcast Address ,  129.72.16.127
 [다] 해당 Network의 할당가능한 IP주소 대역 ,  129.72.16.113 ~ 129.72.16.126
 [라] 보기에 주어진 IP주소가 할당가능한 주소인지 확인하시오    예
 [마] 만약 할당할 수 없다는 주소라면 그이유를 쓰시오.    없음
       할당할 수 있는 주소인 경우는 '없음' 쓰시오.

(6) 222.79.61.159 /27

[가] IP주소가 속한 Network Address , 222.79.61.128  /27
 [나] IP주소가 속한 Network의 Broadcast Address ,  222.79.61.159
 [다] 해당 Network의 할당가능한 IP주소 대역 ,  222.79.61.129 ~ 222.79.61.158
 [라] 보기에 주어진 IP주소가 할당가능한 주소인지 확인하시오  아니오
 [마] 만약 할당할 수 없다는 주소라면 그이유를 쓰시오.   Broadcast Address 로 예약된 주소이기 때문이다.
       할당할 수 있는 주소인 경우는 '없음' 쓰시오.

(7) 192.89.10.255 /23

[가] IP주소가 속한 Network Address , 192.89.10.0  /23
 [나] IP주소가 속한 Network의 Broadcast Address ,   192.89.11.255
 [다] 해당 Network의 할당가능한 IP주소 대역 ,   192.89.10.1 ~ 192.89.11.254
 [라] 보기에 주어진 IP주소가 할당가능한 주소인지 확인하시오    예
 [마] 만약 할당할 수 없다는 주소라면 그이유를 쓰시오.    없음
       할당할 수 있는 주소인 경우는 '없음' 쓰시오.

(8) 156.22.191.0 /22

[가] IP주소가 속한 Network Address , 156.22.188.0  /22
 [나] IP주소가 속한 Network의 Broadcast Address ,  156.22.191.255
 [다] 해당 Network의 할당가능한 IP주소 대역 ,    156.22.188.1 ~ 156.22.191.254
 [라] 보기에 주어진 IP주소가 할당가능한 주소인지 확인하시오    예 
 [마] 만약 할당할 수 없다는 주소라면 그이유를 쓰시오.    없음
       할당할 수 있는 주소인 경우는 '없음' 쓰시오.

(9) 225.77.29.42 /21

[가] IP주소가 속한 Network Address , 225.77.24.0  /21
 [나] IP주소가 속한 Network의 Broadcast Address ,   225.77.31.255
 [다] 해당 Network의 할당가능한 IP주소 대역 ,   225.77.24.1 ~ 225.77.31.254
 [라] 보기에 주어진 IP주소가 할당가능한 주소인지 확인하시오    예
 [마] 만약 할당할 수 없다는 주소라면 그이유를 쓰시오.    없음
       할당할 수 있는 주소인 경우는 '없음' 쓰시오.

 

 

검증  서브넷 계산기

https://www.subnet-calculator.com/subnet.php?net_class=A 

상위 > 하위 = 헤더를 붙인다 = 인캡슐레이션

 

하위 > 상위 = 헤더를 뗀다 = 디캡슐레이션

 

패킷의 데이터 = 페이로드 라고 부르기도 한다.

 

 

 

 

잘 알려진 프로토콜들 의 포트번호 

FTP 20, 21   SSH  22   telnet 23   SMTP  25

DNS 53 도메인 이름으로 IP를 알아내는 프로토콜     

TFTP 69      HTTP, TCP 는 80   POP3은 110

netbios (윈도우 시스템 이름관련 서비스 ) 137~139

IMAP : 메일관련 프로토콜 : 143 HTTPS(SSL) : 443 SMB : 445

 

DBMS

MS-SQL(SQL SERVER) : 1433,1434

MARIADB(MY-SQL): 3306

ORACLE : 1521

원격데스크톱(RDP) : 3389

SNMP : 161,162

DHCP : 67 자동으로 네트워크를 세팅해주는 프로토콜

 

포트 번호 : 동일한 IP 주소를 가진  시스템 내에서 프로세스를 구분하기 위한 주소

0 ~ 1023 포트 : well known port ( = system port ) = 잘 알려진 서비스들이 사용. ( 보통 서버가 사용 )

1024 ~ 49151 포트 : registerd port ( = user port ) = 응용 프로그램들이 사용

49152~65535 포트 : Dynamic port = 접속을 하는 용도로 사용

 

명령어 :  nslookup 도메인이름 
해당 도메인 이름에 대한 ip를 알 수 있음 
nslookup naver.com

명령어 : ipconfig /displaydns                              
DNS 캐시 지우기

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ



Preamble : 이더넷 헤더에 포함되지 않음. 이더넷 신호가 전송 됨을 알림.
FCS : Fream Check Sequence , check sum 기능. 4byte 

이더넷 헤더의 길이 : 14 byte ( 6 + 6 + 2 )
이더넷 데이터의 길이 : 46 ~ 1500 byte
 ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
와이어샤크 화면구성
패킷 리스트 : 캡쳐한 패킷목록 ( 상단부 )
패킷 디테일 : 패킷 정보들 ( 중단부 )


와이어샤크 필터링
프로토콜 필터링 http tcp arp https ...
ip.addr == 8.8.8.8
ip.src 
ip.dst

tcp.port ==
tcp.dstport == 
tcp.srcport ==
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
VPN  ( Virtual Private Network )
물리적으로는 전용선이 아니지만 가상으로 직접 연결한 것 같은 효과가 나도록 만들어주는 기술

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
techblogposts.com

3일차 수 : 기타 네트워크 기본설명 , ip , ip주소 , 서브넷팅
4일차 목 : TCP UDP  ICMP  ARP
5일차 금 : OSI 7계층 프로토콜 http ssl ssh dhcp FTP 등

https://gitabout.com/11

 

인텔리제이(IntelliJ IDEA) 유용한 키보드 단축키 32가지 (PC)

IntelliJ IDEA 올해 초 까지만 해도 저는 Eclipse 만 사용했습니다. IntelliJ 가 생산성 향상에 월등하다는 이야기를 주위에서 많이 듣긴 했지만, 익숙한 툴을 바꾸기도 쉽지가 않았고 뭔가 겉멋(?)이 드

gitabout.com

 

 

master - slave 쓰지말라고 하는데

 

git bash 이놈들은 아직도 브랜치 이름을  master 로 쓰고있다

 

 

그래서 처음 저장소를 만들면 main 으로 바꿔 주는게 좋다

 

 branch -m [old_name]  [new_name]

 branch -m [old_name]  [new_name]

 branch -m [old_name]  [new_name]

 branch -m [old_name]  [new_name]

 branch -m [old_name]  [new_name]

 

 branch -m master main

 

 branch -m master main

 

 branch -m master main

 

 branch -m master main

 

 branch -m master main

 

 branch -m master main

 

'깃 사용방법' 카테고리의 다른 글

vi 편집기 저장 종료 명령어  (0) 2021.01.15

Ctrl + Alt + Insert 사기다

 

 

STS tool 에서는 잘 작동하던 Devtools 의 기능(자동 빌드) 이 인텔리제이(IntelliJ) 에서는 영 작동을 하지않아서 직접 구글링 해보고 해결방법을 정리한 글입니다.

 

 

초기 프로젝트 생성시 (start.spring.io) Dependency에 Devtools 추가를 꼭 해주어야 합니다

 

 

그리고 인텔리제이(IntelliJ) 에서 Ctrl + Shift + A 후 registry... 선택

 

검색창에 뭐든 키워드 검색해서 allow.when.app.running 체크.

해석하면 대충 '앱이 실행중에도 컴파일러가 자동 빌드를 허용한다' 라는 느낌이네요

 

FIle - Setting - Compiler - Build project automatically 체크

 

 

 

 

이렇게만 설정 하면 자동빌드는 해결이 됩니다.

 

 

 

 

파일 내용 수정시 (*) 기호 나오게 하기

 

File - setting - Editor tabs - Mark modified (*) 체크

 

 

작성일자 : 2021 . 08. 15 일요일

개발도구 : STS 4.1.0

 

개인 정리용으로 작성한 글이라 틀린부분이 많을 수 있습니다 ~~

 

 

SpringBoot 프로젝트의 폴더 구조

 

 

 

 

 


1.  src/main/java 

스프링 프레임워크!! 라고 하면 필수적으로 나오는게 MVC 구조인데요 그 중 Model, Controller 를 저장하는 곳입니다.

 

처음 Spring Stater Project를 생성하면 

기본 패키지명 "com.example.demo" 와 하위 java파일 "[프로젝트명]Application.java" 가 있습니다.

( TestController.java 는 제가 임의로 만든파일 )

그리고 패키지명은 보통 자기가 만들 도메인이나 회사명이나 등등을 거꾸로 쓴다고 하네요.

예를들면 com.naver.webtoon     (?)

 

 

자동으로 만들어진 자바파일 입니다~ 메인함수에 run 함수 하나만 달랑 있는 아주 간단한 파일이네요

프로젝트를 SpringBootApp 으로 실행시키면 이 함수가 호출이 되겠군요 

 

만들어진 패키지 하위에서 작업을 하셔야 위와 같은 main함수를 찾아내기 때문에 작업하실 때 번거롭지 않습니다!!

굳이 다른패키지에서 하고싶으시다면 자세한 내용은 @ComponentScan 어노테이션을 찾아보시기 바랍니다 ~

 


2. src/main/resources

서버가 실행될 때 필요한 파일들을 저장하는 공간입니다.

기본적으로 application.properties 라는 환경설정 파일이 만들어져있을텐데

properties 보다 yml 확장자의 문법이 좀 더 가독성이 좋기때문에 저는 파일이름을 변경하여 yml 로 변경한 상태입니다

 

 

 

application.properties 문서

https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.server

 

 

 

더욱 공부하게 된다면 java 파일에서 환경설정에서 세팅한 값을 불러와서 사용할 수 있다고 하네요

( 예를들면 테스트서버 / 실제 운영서버의 세팅 값 )

 


3. src/main

/webapp/WEB-INF/views

MVC 구조에서 Views 를 저장하는 곳입니다.

 

Spring 에서는 jsp 파일을 사용하려면 필수로 설정 해야할게 몇 가지 있습니다.

파일경로를 줄바꿈으로 차별을 준 이유는 프로젝트를 처음 만들면 src/main 밖에 없어서 그 하위의

/webapp/WEB-INF/views 는 직접 폴더를 생성해주셔야 하기때문입니다.

 

 

왜 하필 경로는  /webapp/WEB-INF/views  고정이어야 하나요?

나중에 pom.xml 에 추가해야할 tomcat-embed-jasper 라이브러리와  application.yml 에서 추가해야할 prefix

와 관련이 있습니다 

 

pom.xml 에 추가해야할 tomcat-embed-jasper

pom.xml에 관한 내용은 마지막에 설명하겠습니다 !! 의존성관리하는 파일 이라고만 알아두세요 ~

<!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-jasper -->
		<dependency>
		    <groupId>org.apache.tomcat.embed</groupId>
		    <artifactId>tomcat-embed-jasper</artifactId>
		    <version>9.0.40</version>
		</dependency>

 

pom.xml을 변경하고 난 뒤에는 Project Clean 하는거 잊지마세요 ~

아무리해도 추가한 라이브러리가 인식이 안된다면 서버를 재시작 해보세요 !!!

 

 

application.yml 에서 추가해야할 prefix

prefix 와 suffix 는 각각 접두어, 접미어 라는 뜻을 지니고 있는데요

tomcat-embed-jasper 에서 jsp 파일에 접근하는 경로가 src/main/webapp 까지였는데

 

yml 파일에서 접두어 접미어 설정을 미리 해둔다면

여기에 접두에 /WEB-INF/views/ 가 붙게 되고 접미에 .jsp 가 붙게됩니다.

 

그런데 어디의 접두 접미 라는것일까요?

바로 Controller 에서 return 하는 View의 이름입니다.

yml 파일에 prefix , suffix 가 없다면 index2 함수처럼 view의 전체경로와 확장자를 모두 입력해줘야 합니다.

 

하지만 yml 파일에 prefix,  suffix 가 설정되어 있다면 !!!

index 함수처럼 view 의 파일이름만 반환하면 알아서 찾아가게 됩니다.

 

이 부분이 더 궁금하시다면 ViewResolver 를 찾아보시면 될 것 같습니다!

 

 

 

4. pom.xml

pom.xml 에 필요한 라이브러리를 작성하면 프로젝트 빌드시에 자동으로 다운로드받아 관리해주는 파일입니다.

맨 처음 프로젝트 생성할 때

pom.xml 파일이 없으시다면 의존성 관리타입을 Maven 으로 해주셔야 합니다 !!

이 외의 타입으로는 Gradle 이 있는데 관리방법과 사용빈도도 Maven이랑 거의 비슷해서 자신에게 맞는 관리도구를 사용하시면 됩니다.

 

그리고 자신이 원하는 라이브러리를 검색하여 즉시 추가할 수 있습니다.

제가 알고있는 스프링 개발에 반드시필요한 라이브러리는

Spring Web

Spring Web Services 이고요 ( 전반적인 스프링 기능이 다 담겨있어서 자세하게는 어떤 기능이 있는지 잘 모르겠습니다)

 

SpringBoot Dev Tools : 프로젝트의 파일 변겅후 저장을 감지하여 서버를 자동으로 재실행 해줍니다 (편의성)

 

이 외에 자주 쓰이므로 공부하면 도움이되는 라이브러리들은 

MySQL , MyBatis , OAuth2.0 , Security , JPA .. 가 있습니다 !!

 

 

 


'프로젝트 구조' 만 설명하려고 했는데 자꾸 뭐라도 더 쓰고싶다는 생각에 정보를 계속 눌러담은 느낌이 드네요 .

꾸준히 정리 해보겠습니다.

문제 www.acmicpc.net/problem/1110

문제

0보다 크거나 같고, 99보다 작거나 같은 정수가 주어질 때 다음과 같은 연산을 할 수 있다. 먼저 주어진 수가 10보다 작다면 앞에 0을 붙여 두 자리 수로 만들고, 각 자리의 숫자를 더한다. 그 다음, 주어진 수의 가장 오른쪽 자리 수와 앞에서 구한 합의 가장 오른쪽 자리 수를 이어 붙이면 새로운 수를 만들 수 있다. 다음 예를 보자.

26부터 시작한다. 2+6 = 8이다. 새로운 수는 68이다. 6+8 = 14이다. 새로운 수는 84이다. 8+4 = 12이다. 새로운 수는 42이다. 4+2 = 6이다. 새로운 수는 26이다.

위의 예는 4번만에 원래 수로 돌아올 수 있다. 따라서 26의 사이클의 길이는 4이다.

N이 주어졌을 때, N의 사이클의 길이를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N이 주어진다. N은 0보다 크거나 같고, 99보다 작거나 같은 정수이다.

출력

첫째 줄에 N의 사이클 길이를 출력한다.

예제 입력 1

26

예제 출력 1

4

예제 입력 2

55

예제 출력 2

3

예제 입력 3

1

예제 출력 3

60

예제 입력 4

0

예제 출력 4

1

 

 

나의생각

26을 입력받아서 2와 6으로 나누려면 

26 / 10 = 2 ( int 형이기때문에 소수점은 버림 )

26 % 10 = 6

입력값 , 왼쪽숫자, 오른쪽숫자, 결과값 변수를 모두 만들어서 따로따로 관리

 

 

정답소스

더보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.Scanner;
 
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int count=0;
        int first = 0;
        int nextNum = 0;
        first = sc.nextInt();
 
        int fistSave = first;
        while(true){
 
            int leftNum = (first/10);
            int rightNum = (first%10);
 
            int result = leftNum + rightNum;
            nextNum = (rightNum*10+ (result%10);
            first = nextNum;
 
            count++;
            if(first == fistSave)
                break;
        }
        System.out.println(count);
    }
}
 
cs

 

ajax 문법이 어느역할을 하는지? 를 중점에 두고 보시면 좋을겁니다 !!

 

사용된 예제는

Spring이 아니고 DynamicWebProject 입니다. 통신방법 메소드 맵핑 하는 맥락은 비슷비슷하지만요~

 


ajax 공부를 하기위해 여러 블로그들을 돌아다니다 보면,  $.get , $.post ? .success() 등등.. 다양한 문법이 있는데

문법은 다 다르지만 작동은 다 될거에요 !!! 

 

ajax 문법 정의

$.ajax({

  (요청 정보 작성)

  (필수)type: get,post,delete ...    , HTTP Method (기본값 GET 이지만 그래도 명시해주는게 좋음)

  (필수)url: "http://localhost:8080/블라블라" , 요청을 보낼 주소

  data:  , 서버에 전송할 http의 body ( 보통 GET 요청은 data가 없다 )

  contentType:  , (기본값)" application / x - www - form - urlencoded "

  dataType: "text, html, xml, json ... " 서버에서 반환되는 데이터 형식을 지정합니다.

  (dataType 생략할 시 jQuery가 알아서 정함)

})

.done(function(result){

  요청 성공시 동작하는 콜백함수입니다!!

})

 

이렇게 틀을 먼저 만들고 내용을 채워나가는 걸 추천드립니다 ~

$.ajax({}).done(res=>{}).fail(error=>{})

 

getajax.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<script src="https://code.jquery.com/jquery-3.5.1.js"></script>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="getajax()">
    <input type="text" id="username" name="username" placeholder="username"/>
    <input type="password" id="password" name="password" placeholder="password"/>
</form>
<button onclick="getajax()">클릭</button>
</body>
 
<script>
function getajax(){
    var username = $("#username").val();
    var password = $("#password").val();
    console.log(username);
    console.log(password);
 
    var params = "username="+username;
    params += "&password="+password;
    
    $.ajax({
        //GET으로 key=value로 데이터를 전달하고 text/plain으로 응답받을 예정
        type:"GET",
        url:"http://localhost:8000/ajax/ajax1?" + params,
        //data: , get은 전송할 http의 body가 없음, 그래서 data필드가 필요없음
        //contentType: ,전송한 data가 없으니까 그 data를 설명한 필드도 필요없음
        dataType: "text" 
    })
    .done(function(result){ //ajax통신 완료후에 정상이면 실행
        alert(result);
        })
    .fail(function(){
        alert("통신실패");
        });
}
    
</script>
</html>
cs

get방식의 주소요청 (파라미터가 주소에 보인다)

31행 : url = http://localhost:8000/ajax/ajax1? + "username="+username + "&password="+password

 

 

Ajax1.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.cos.ajax;
 
import java.io.IOException;
import java.io.PrintWriter;
 
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
@WebServlet("/ajax1")
public class Ajax1 extends HttpServlet {
    private static final long serialVersionUID = 1L;
 
    public Ajax1() {
        super();
    }
 
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        
        System.out.println("username: "+username);
        System.out.println("password: "+password);
        
        // ajax의 done함수 result 에 들어갈 내용
        PrintWriter out = response.getWriter();
        response.setCharacterEncoding("utf-8");
        
        StringBuffer sb = new StringBuffer();
        sb.append("username="+username+"\n");
        sb.append("password="+password);
        
        out.println(sb);
        out.flush();
    }
 
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 
    }
 
}
 
cs

 

12행 : @WebServlet("/ajax1") : /ajax1 으로 끝나는 주소요청을 담당하는 java파일임을 알린다.

20행 : doGet(request, response) : get 요청일때 동작하는 함수

21, 22행 : 요청된 주소( url = http://localhost:8000/ajax/ajax1? + "username="+username +"&password="+password )의 파라미터 이름이 username, password인 값을 자바 변수로 정의한다

 

28행 : requset (요청)은 모두 파악했으니, 이제 response (응답)할 차례.

32, 33행 : 응답을 HTML로 하기때문에 그에 맞는 문법을 사용한다. ( HTML 태그가 사용가능하다 )

 

 

 

 

 

 

 

결과

 

결과 해석

 

ajax 문법 정의

$.ajax({

  (요청 정보 작성)

  (필수)type: get,post,delete ...    , HTTP Method (기본값 GET 이지만 그래도 명시해주는게 좋음)

  (필수)url: "http://localhost:8080/블라블라" , 요청을 보낼 주소

  data:  , 서버에 전송할 http의 body ( 보통 GET 요청은 data가 없다 )

  contentType:  , (기본값)" application / x - www - form - urlencoded "

  dataType: "text, html, xml, json ... " 서버에서 반환되는 데이터 형식을 지정합니다.

  (dataType 생략할 시 jQuery가 알아서 정함)

})

.done(function(result){

  요청 성공시 동작하는 콜백함수입니다!!

})

 

 

 

 

 

 

 

 

 

 

아직 미완성입니다

'AJAX' 카테고리의 다른 글

ajax 통신 개념정리  (0) 2021.03.27
(펌)HTTP 그리고 REST API 다가가기  (0) 2021.03.27

참고 블로그

webdevtechblog.com/ajax-%EC%9D%98-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8%EC%99%80-%EC%84%9C%EB%B2%84-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%86%B5%EC%8B%A0-d681c905e2a9

 

AJAX 의 클라이언트와 서버 비동기 통신

클라이언트와 서버의 비동기 통신

webdevtechblog.com

jinbroing.tistory.com/99

 

[자바스크립트] Ajax 통신

[목표] - 모던웹 구현에 필요한 Ajax 개념알기 - Ajax 직접 구현해보는 것 마음먹기 [먼저 보면 좋은 게시글] 1) HTTP 프로토콜 자세히 알기 : http://jinbroing.tistory.com/96 2) 싱글쓰레드 자바스크립트 엔..

jinbroing.tistory.com


목표

JQuery의 Ajax를 이용해서 클라이언트와 서버간의 비동기 통신방법에 대해서 배워봅시다.

서론

개발을 하다보면 화면전환(Refresh) 없이, CRUD를 처리해야하는 상황이 생깁니다. 그럴때 편리하게 사용할 수 있는게 JQuery의 Ajax를 이용해서 비동기 통신을 하면 가능합니다. 제가 자바와 스프링을 이용하여 웹 개발을 하면서 공부한 내용을 정리하여 공유하고자 합니다.

 

 

[Ajax란]

- 자바스크립트를 이용해서 비동기적으로 브라우저와 서버가 데이터를 주고 받는 방식을 말함

- 새로운 언어나 프레임워크, 라이브러리가 아님, 네트워크 통신 방식을 말함

- Asynchronous Javascript and XML : 자바스크립트로 비동기 통신을 하고, XML 형식으로 데이터 리턴을 받는다는 뜻

- XML 데이터보다 JSON 데이터 포멧을 훨씬 더 많이 사용함

- 자바스크립트를 통해서 서버에 요청을 하고, 서버로부터 데이터를 리턴받음

- DOM을 제어해서 서버로부터 리턴받은 데이터를 가지고 랜더링함

 

[Ajax 사용하면 좋은 점]

- 비동기 통신의 이점 : 통신 후 데이터 바인딩하는 동안 사용자가 어플리케이션을 사용할 수 있음

- 전체 페이지 로딩(페이지 요청)시 모든 데이터를 서버로부터 받는 것이 아니라 필요한 부분을 그때마다 일부분 랜더링 시키는 것이 가능함

- 일부 데이터를 위해 클라이언트가 전체 페이지를 요청하지않아도됨

- 다시 말해 통신 때마다 페이지 전체 리로드를 하지 않음, 그 말은 브라우저 랜더링 엔진이 하는 일이 줄어든다는 것

- 이를 클라이언트 사이드 랜더링이라 함

클라이언트에서 서버로 데이터 전달

Ajax 를 사용하는데 있어 주로 사용되는 데이터 포맷은 CSV, JSON, XML 형식이 있다.(XML 만 사용하는 것이 아니다.) 

GET, POST, DELETE, UPDATE 등 일반적인 HTTP 요청을 이용해 CRUD 를 처리하며 REST API 와 궁합이 잘맞는다. 

 

클라이언트에서 서버로 데이터를 전송하려는 데이터들은 form 안에 위치하게 됩니다. 주로 input type들이서버로 전송해야할 데이터들입니다. 그리고 우리는 JQuery를 이용해서 form안에 있는 데이터들을 Ajax를 이용해 비동기 방식으로 서버로 전송하게 됩니다.

서버는 클라이언트에서 보낸 데이터를 받아 처리하고 비동기의 데이터 타입에 알맞은 반환을 해야합니다. 이렇게 서로 다른 언어간 데이터를 주고 받기 위해서는 특정한 문법이 필요한데 대표적인게 JSON 혹은 XML을 이용하게 되는데 JSON이 보편적이고 데이터 교환에 있어서 편리합니다.

이와 같은 과정을 진행하기 위해서는 차근차근 로직을 세우고 이해해야합니다. 그럼 저와 같이 로직을 세워보겠습니다. (참고로 제가 세운 로직보다 더 좋은 로직이 있을 수 있으며, 혼자 로직을 세워보는 습관을 들이는게 좋습니다.)

 

[Ajax 동작방식]

1) 서버로 정보 요청 : 이벤트 발생 -> 핸들러 함수 호출 -> 서버 요청 객체 생성 및 메서드 호출

2) 서버 내부 처리 후 응답 : json 또는 xml 형태로 데이터 전달

3) 응답을 받으면 이벤트 발생(onload), 이벤트의 콜백함수 호출

로직(Algorithm)

  • 비동기 처리를 위해서 Ajax를 사용한다.
  • GET 방식인지 POST 방식인지 정한다.
  • 클라이언트가 서버로 보낼 데이터를 만들어 보낸다 : GET의 경우 전달할 데이터값이 있으면 보내야할 URL에 쿼리스트링 또는 data에 키와 밸류를 선언해서 보낸다. POST의 경우 폼에 있는 데이터를 서버로 전송하기 위해서 formData와 serialize를 사용한다.
  • 서버에서 클라이언트로 보내야할 dataType을 정한다.


다음 포스팅에서 STS DynamicWebProject 로 다양한 형태(QueryString, json) Ajax예시를 만들어보겠습니다

'AJAX' 카테고리의 다른 글

ajax 통신 방법 (QueryString)  (0) 2021.03.27
(펌)HTTP 그리고 REST API 다가가기  (0) 2021.03.27

jinbroing.tistory.com/96

 

[웹기본개념] HTTP 그리고 REST API 다가가기

[HTTP란] - HyperText Transper Protocol의 준말, 하이퍼텍스트 트랜스퍼란 링크 기반으로 데이터를 요청하고 받겠다는 것 - 클라이언트와 서버가 요청을 하고 응답을 하기위해 따르는 프로토콜 - HTML 문서

jinbroing.tistory.com

 

설명이 너무 잘 되어있습니다.

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

 

 

[HTTP란]

- HyperText Transper Protocol의 준말, 하이퍼텍스트 트랜스퍼란 링크 기반으로 데이터를 요청하고 받겠다는 것

- 클라이언트와 서버가 요청을 하고 응답을 하기위해 따르는 프로토콜

- HTML 문서를 주고 받을 수 있음, 뿐만 아니라 이미지, 동영상, 오디오, 텍스트 문서 등을 주고 받을 수 있음

 

 

[HTTP 동작 방식]

 

- 클라이언트 : 웹어플리케이션의 경우 크롬, 파폭, IE 등 브라우저를 통햇허 서버에 요청을 함(프로토콜 + 도메인 + URI)

- 서버 : 클라이언트로부터 받은 요청을 내부적으로 처리하여 그에 대한 결과를 응답해줌

 

 

[HTTP 특징 및 기능]

1) connectless + stateless

- 1번 요청-응답 후 연결을 끊어버림

- 클라이언트의 이전 상태를 알 수 없음 : 그래서 쿠키와 세션(클라이언트와 서버의 이전 상태정보 저장)이 필요함

- 수십만명이 웹서비스를 사용(요청)하더라도 최소 유지를 할 수 있기 때문에, 많은 유저의 요청을 처리할 수 있음

 

2) keep-alive : HTTP 1.1부터 지원하는 기능

- 1번 항목에서 언급했듯이 HTTP는 1번의 요청에 대해 1번의 응답 하는 것을 기준으로 설계됨

- 요즘 웹사이트들을 보면 하나의 페이지에 수십개 이미지, css 파일, js 파일이 있는 것을 볼 수 있음

- 1 요청 1응답 기준이라면 여러번 연결을 끊었다 붙였다 해야함 굉장히 비효율적

- keep-alive 지원으로 지정된 시간동안 연결을 끊지않고 연결된 상태를 유지할 수 있음

- keep-alive time out 내 클라이언트가 재요청하면 새로운 연결이 아닌 연결된 것을 이용함

1) 웹서버 연결

2) HTML 문서 다운로드

3) 필요한 img, css, js 등 다운로드

4) 연결 끊음

 

- 모든 웹서버, 브라우저가 HTTP 1.1 지원

- 아래는 네이버 페이지에 대한 요청 헤더임 

 

[HTTP Request 구조]

- 사용자(클라이언트 프로그램을 이용한)가 서버에 요청을 보낼 때 HTTP Request 구조를 알아본 것

 

 

1) 메소드(Header) : 사용자가 서버에 요청하는 메소드, HTTP 버젼을 확인할 수 있음

2) 요청헤더(Header) : 서버에 전달되는 사용자 정보(클라이언트 브라우저 정보 : 문자코드, 언어, 파일 종류)

3) 공백 : HTTP Request Header(헤더)와 본문 부분을 구분하기위한 공백

4) 본문(message) : HTTP Request 요청 메세지를 담고 있는 부분, GET 메소드의 경우 요청정보가 주소에 담겨져 있어 본문은 빈 상태

 

- 아래는 해당 페이지 HTTP Request의 header 부분의 이미지 : 요청 본문 아님

 

 

[HTTP Response 구조]

- 서버가 사용자 요청에 대한 응답을 할 때 HTTP Response 구조를 알아본 것

 

 

1) 상태코드(Header) : 사용자의 요청에 대한 서버 처리결과를 나타냄(200, 404, 500 등 여러 상태코드가 있음)

2) 응답헤더(Header) : 사용자에게 전달한 데이터 정보를 나타냄

3) 공백 : HTTP Response Header(헤더)와 본문 부분을 구분하기위한 공백

4) 본문(message) : 사용자에게 전달한 데이터 내용을 담고 있음(요청에 대한 데이터 응답)

 

- 아래는 해당 페이지 HTTP Request의 header 부분의 이미지 : 응답 본문 아님

 

[HTTP method 그리고 REST API]

- 메소드란? 요청의 종류를 서버에 알리기위해 사용하는 것, 게시판 기능(CRUD, REST API)을 만들 때 사용

- 메소드 종류

1) GET : 정보를 요청하기위해 사용(Read)

2) POST : 정보를 입력하기위해 사용(Create)

3) PUT : 정보를 업데이트하기위해 사용(Update)

4) DELETE : 정보를 삭제하기위해 사용(Delete)

 

- REST API란? HTTP 프로토콜 장점을 살릴 수 있는 네트워크 기반 아키텍처

1) REST API를 구현하기위해 HTTP method + 모든 개체 리소스화 + URL 디자인(라우팅) 필요

- 라우팅이란? 클라이언트의 요청에 대한 결과(응답)를 어떻게 이어줄 것인가를 처리하는 것

- URI를 이용한 접근 : 모든 개체를 리소스로 보고, 리소스에 고유번호를 부여

- URL 디자인 원칙 : 자원에 대한 처리를 주소에 나타내지않는다(HTTP method는 내부적으로 처리하도록), 어떤 자원인지 주소에 명확하게 나타냄

 

- 레일즈로 REST URL 디자인한 것

- book 이라는 자원(db 테이블 구현)에 대한 처리를 설계함

- 실제 주소는 http://도메인/books 혹은 뒤에 book 자원 중 하나에 대한 처리(:id), book 자원 새로 만들기 new 만 있음

- update, delete, read(show)를 주소 상에서 보여주지 않음

 

- REST API를 구현하기위해 HTTP 프로토콜에 대한 이해, method 종류, 라우팅에 대한 이해가 있어야함

- HTTP method를 클라이언트가 요청할 때 서버에 넘길 수 있도록(처리 각각에 맞는)

 

 

[참고자료]

1) 위키피디아 HTTP : https://ko.wikipedia.org/wiki/HTTP

2) joinc.com HTTP : https://www.joinc.co.kr/w/Site/Network_Programing/AdvancedComm/HTTP

3) exoluse 블로그 : http://exoluse.egloos.com/v/4572381

4) Wooeong's lab : http://wooeong.tistory.com/entry/HTTP%EB%9E%80

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

 

Ajax 통신을 사용할때 Request Header를 개발자가 잘 생각해서 써야하는데 , 이 글을 읽는다면

Request Header 작성에 도움이 될 것 같습니다.

'AJAX' 카테고리의 다른 글

ajax 통신 방법 (QueryString)  (0) 2021.03.27
ajax 통신 개념정리  (0) 2021.03.27

list.jsp

<div class="container">

	<div class="m-2">
		<form class="form-inline d-flex justify-content-end" action="/blog/board">
			<input type="hidden" name="cmd" value="search" /> 
			<input type="hidden" name="page" value="0" /> 
			<input type="text" name="keyword" class="form-control mr-sm-2" placeholder="Search">
			<button class="btn btn-primary m-1">검색</button>

		</form>
	</div>
	<div class="progress col-md-12 m-2">
		<div class="progress-bar" style="width: ${currentPosition}%"></div>
	</div>

위의 form 태그를 유심히 보면 action="/blog/board" 이후에

그 밑에 <input> 들의 name, value가 서버로 전송 될 것임을 알 수 있다.

 

action = "blog/board?cmd=search&page=0&keyword=[검색한단어]"

 

 

 

BoardController.java

else if(cmd.equals("search")) {
			String keyword = request.getParameter("keyword");
			int page = Integer.parseInt(request.getParameter("page"));

			List<Board> boards = boardService.글검색(keyword, page);
			request.setAttribute("boards", boards);

			int boardCount = boardService.글개수(keyword);
			int lastPage = (boardCount-1)/4; // 2/4 = 0, 3/4 = 0, 4/4 = 1, 9/4 = 2 ( 0page, 1page, 2page) 
			double currentPosition = (double)page/(lastPage)*100;



			request.setAttribute("lastPage", lastPage);
			request.setAttribute("currentPosition", currentPosition);
			RequestDispatcher dis = request.getRequestDispatcher("board/list.jsp");
			dis.forward(request, response);
		}

cmd.equals("list")와 코드가 유사하다.

글검색() 의 결과가 저장된 boards 를 setAttribute 하여 dispatcher 를 이용해 list페이지로 이동하게 한다.

 

글검색 된 boards 도 페이지처리가 적용되어야 하기 때문에 글개수(keyword) 함수를 오버로딩하여 새로 만들어야 한다.

 

 

BoardService.java

public List<Board> 글검색(String keyword, int page){
		return boardDao.findByKeyword(keyword, page);
	}

public int 글개수(String keyword) {
		return boardDao.count(keyword);
	}

 

BoardDao.java

public List<Board> findByKeyword(String keyword, int page){
		String sql = "SELECT * FROM  board WHERE title like ? ORDER BY id DESC LIMIT ?, 4"; // 0,4   4,4   8,4
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;
		List<Board> boards = new ArrayList<>();
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "%"+keyword+"%");
			pstmt.setInt(2, page*4); // 0 -> 0, 1 ->4, 2->8
			rs =  pstmt.executeQuery();

			// Persistence API
			while(rs.next()) { // 커서를 이동하는 함수
				Board board = Board.builder()
						.id(rs.getInt("id"))
						.title(rs.getString("title"))
						.content(rs.getString("content"))
						.readCount(rs.getInt("readCount"))
						.userId(rs.getInt("userId"))
						.createDate(rs.getTimestamp("createDate"))
						.build();
				boards.add(board);	
			}
			return boards;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}

		return null;
	}
    
    
    public int count(String keyword) {
		String sql = "SELECT count(*) FROM board WHERE title like ?";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;

		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "%"+keyword+"%");
			rs =  pstmt.executeQuery();
			if(rs.next()) {
				return rs.getInt(1);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}
		return -1;
	}

BoardDao.list 와 코드가 유사하다.

 

 

 

list.jsp

<!-- disabled -->
	<ul class="pagination justify-content-center">

		<c:choose>
			<c:when test="${empty param.keyword}">
				<c:set var ="pagePrev" value="/blog/board?cmd=list&page=${param.page-1 }"></c:set>
				<c:set var="pageNext" value="/blog/board?cmd=list&page=${param.page+1}"/>
			</c:when>
			<c:otherwise>
				<c:set var="pagePrev" value="/blog/board?cmd=search&page=${param.page-1}&keyword=${param.keyword}"/>
				<c:set var="pageNext" value="/blog/board?cmd=search&page=${param.page+1}&keyword=${param.keyword}"/>
			</c:otherwise>
		</c:choose>

		<c:choose>
			<c:when test="${param.page == 0}">
				<li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>	
			</c:when>
			<c:otherwise>
				<li class="page-item"><a class="page-link" href="${pageScope.pagePrev}">Previous</a></li>
			</c:otherwise>
		</c:choose>

		<c:choose>
			<c:when test="${lastPage == param.page}">
				<li class="page-item disabled"><a class="page-link" href="#">Next</a></li>		
			</c:when>
			<c:otherwise>
				<li class="page-item"><a class="page-link" href="${pageScope.pageNext}">Next</a></li>
			</c:otherwise>
		</c:choose>

		
	</ul>
</div>
</body>
</html>

페이지 버튼도 동적으로 바뀌게 한다. 이 부분이 가장 머리를 많이 쓰는 부분이긴 한데...

ㅠㅠ

detail.jsp

<div class="m-2">
	<c:if test="${sessionScope.principal.id == reply.userId }">
		<i onclick="deleteReply(${reply.id})" class="material-icons">delete</i>
	</c:if>
    </div>
</li>
</c:forEach>

delete 버튼이 나에게만 보이도록 c:if 문으로 userId를 비교함

 

 

boardDetail.js

function deleteReply(id){
	// 세션의 유저의 id와 reply의 userId를 비교해서 같을때만!!
	alert("댓글 아이디 : "+id);
	$.ajax({
		type : "post",
		url : "/blog/reply?cmd=delete&id="+id,
		dataType : "json"
	}).done(function(result) { //  { "statusCode" : 1 }
		if (result.statusCode == 1) {
			console.log(result);
			$("#reply-"+id).remove();
		} else {
			alert("댓글삭제 실패");
		}
	});
}


post 요청을 함, body 데이터는 필요없어서 data, ContentType 은 작성하지 않았음

 

 

 

 

ReplyDao.java

public int deleteById(int id) {
		String sql = "DELETE FROM reply WHERE id = ?";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs = null;

		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, id);
			int result = pstmt.executeUpdate();
			return result;

		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt);
		}
		return -1;
	}

 

 

ReplyService.java

public int 댓글삭제(int id) {
		return replyDao.deleteById(id);
	}

 

 

ReplyController.java

else if(cmd.equals("delete")) {
			int id = Integer.parseInt(request.getParameter("id"));
			int result = replyService.댓글삭제(id);

			CommonRespDto commonDto = new CommonRespDto<>();
			commonDto.setStatusCode(result);  //1, -1

			Gson gson = new Gson();
			String jsonData = gson.toJson(commonDto);
			// { "statusCode" : 1 }
			Script.responseData(response, jsonData);
		}

쿼리스트링에 있는 id값을 id변수에 저장한다.

replyService.댓글삭제 의 return 값을 result 변수에 저장한다.

CommonRespDto<T>{

  int statusCode

  int T data

}

CommonRespDto 객체를 하나 만든다

새로만든 객체의 statusCode 의 값을 result로 set

data는 null 일 것이다.

 

객체를 toJson( Json하고싶은 JAVA Object ) 해서 응답한다. -> { "statusCode" : 1 }

( ajax deleteById 에게 jsonData를 result 로 응답한다. ) (항상 JSON으로 통신해야 함)

boardDetail.js - deleteById()

function deleteReply(id){
	// 세션의 유저의 id와 reply의 userId를 비교해서 같을때만!!
	//alert("댓글 아이디 : "+id);
	$.ajax({
		type : "post",
		url : "/blog/reply?cmd=delete&id="+id,
		dataType : "json"
	}).done(function(result) { //  { "statusCode" : 1 }
		if (result.statusCode == 1) {
			console.log(result);
			$("#reply-"+id).remove();
		} else {
			alert("댓글삭제 실패");
		}
	});
}

Script.responseData()

public static void responseData(HttpServletResponse response, String jsonData) {

		PrintWriter out;
		try {
			out = response.getWriter();
			out.print(jsonData);
			out.flush(); // 버퍼 비우기
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

 

detail.jsp

<!-- 댓글 리스트 시작-->
	<ul id="reply__list" class="media-list">

		<c:forEach var="reply" items="${replys}">
			<!-- 댓글 아이템 -->
			<li id="reply-${reply.id}" class="media">
				<div class="media-body">
					<strong class="text-primary">${reply.userId}</strong>
					<p>${reply.content}</p>
				</div>
				<div class="m-2">
					<i onclick="deleteReply(${reply.id})" class="material-icons">delete</i>

				</div>
			</li>

		</c:forEach>


	</ul>
<!-- 댓글 리스트 끝-->

forEach 문으로 댓글들을 불러옵니다.

 

 

 

boardDetail.js

function replySave(userId, boardId) {
	var data = {
		userId : userId,
		boardId : boardId,
		content : $("#content").val()
	}
	$.ajax({
		type : "post",
		url : "/blog/reply?cmd=save",
		data : JSON.stringify(data),
		contentType : "application/json; charset=utf-8",
		dataType : "json"
	}).done(function(result) {
		if (result.statusCode == 1) {
			console.log(result);

			addReply(result.data);
			location.reload();
		} else {
			alert("댓글쓰기 실패");
		}
	});
}

location.reload(); 한 줄 추가

 

 

 

ReplyDao.java

public List<Reply> findAll(int boardId){
		String sql = "SELECT * FROM reply WHERE boardId = ? ORDER BY id DESC";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;

		List<Reply> replys = new ArrayList<>();
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, boardId);
			rs =  pstmt.executeQuery();

			// Persistence API
			while(rs.next()) { // 커서를 이동하는 함수
				Reply reply = new Reply();
				reply.setId(rs.getInt("id"));
				reply.setUserId(rs.getInt("userId"));
				reply.setBoardId(rs.getInt("boardId"));
				reply.setContent(rs.getString("content"));
				replys.add(reply);
			}
			return replys;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}
		return null;
	}

 

 

 

ReplyService.java

public List<Reply> 글목록보기(int boardId){
		return replyDao.findAll(boardId);
	}

 

 

 

BoardController.java

protected void doProcess(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String cmd = request.getParameter("cmd");
		BoardrService boardService = new BoardrService();
        
		ReplyService replyService = new ReplyService();
        
		// http://localhost:8080/blog/board?cmd=saveForm
		HttpSession session = request.getSession();

doProcess 상단에 ReplyService 선언

 

else if(cmd.equals("detail")) {
			int id = Integer.parseInt(request.getParameter("id"));

			DetailRespDto dto = boardService.글상세보기(id); // board테이블+user테이블 = 조인된 데이터!!
			List<Reply> replys = replyService.글목록보기(id);

			if(dto == null) {
				Script.back(response, "상세보기에 실패하였습니다");
			}else {
				request.setAttribute("dto", dto);
				request.setAttribute("replys", replys);
				//System.out.println("DetailRespDto : "+dto);
				RequestDispatcher dis = request.getRequestDispatcher("board/detail.jsp");
				dis.forward(request, response);
			}
		}

detail (게시글 상세보기) 호출할 때 댓글 목록도 함께 가져오게하기

setAttribute replys 하여서 detail.jsp에서  jstl 사용할 수 있게하기

완전히 이해하려면 영상을 봐야할듯 합니다..

www.youtube.com/watch?v=9zJSkdY10oE&list=PL93mKxaRDidHvJs0PvxcZnUCrUYQZSzBT&index=30

 

detail.jsp

<!-- 댓글 박스 -->
  <div class="row bootstrap snippets">
    <div class="col-md-12">
      <div class="comment-wrapper">
        <div class="panel panel-info">
          <div class="panel-heading m-2"><b>Comment</b></div>
            <div class="panel-body">
              <input type="hidden" name="userId" value="${sessionScope.principal.id}" />
              <input type="hidden" name="boardId" value="${dto.id}" />
              <textarea id="content" id="reply__write__form" class="form-control" placeholder="write a comment..." rows="2"></textarea>
              <br>

              <button onClick="replySave(${sessionScope.principal.id}, ${dto.id})" class="btn btn-primary pull-right">댓글쓰기</button>

              <script>
							

		function replySave(userId, boardId){

                var data = {
                  userId: userId,
                  boardId: boardId,
                  content: $("#content").val()
                }
                $.ajax({
                  type: "post",
                  url: "/blog/reply?cmd=save",
                  data: JSON.stringify(data),
                  contentType: "application/json; charset=utf-8",
                  dataType: "json"
                }).done(function(result){
                  if(result.statusCode == 1){
                  $("#reply__list").prepend("<div>"+data.content+"</div>")
                }else{
                  alert("댓글쓰기 실패");
                }
               	 });
                }


                </script>
              <div class="clearfix"></div>
              <hr />

<!-- 댓글 리스트 시작-->

ajax를 사용하면 나중에 Controller에서 BufferedReader 로 읽을 수 있습니다.

 

 

 

ReplyDao.java

public int save(SaveReqDto dto) { // 회원가입
		String sql = "INSERT INTO reply(userId, boardId, content, createDate) VALUES(?,?,?, now())";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
        ResultSet rs = null;
		int generateKey;
		try {
			pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
			pstmt.setInt(1, dto.getUserId());
			pstmt.setInt(2, dto.getBoardId());
			pstmt.setString(3, dto.getContent());
			int result = pstmt.executeUpdate();
			
            rs = pstmt.getGeneratedKeys();
			if(rs.next()) {
				generateKey = rs.getInt(1);
				System.out.println("생성된 키(ID) : "+generateKey);
				if(result == 1) {
					return generateKey;	
				}

			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt);
		}
		return -1;
	}
    
    public Reply findById(int id){
		String sql = "SELECT * FROM reply WHERE id = ?";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, id);
			rs =  pstmt.executeQuery();

			// Persistence API
			if(rs.next()) { // 커서를 이동하는 함수
				Reply reply = new Reply();
				reply.setId(rs.getInt("id"));
				reply.setUserId(rs.getInt("userId"));
				reply.setBoardId(rs.getInt("boardId"));
				reply.setContent(rs.getString("content"));
				return reply;
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}
		return null;
	}

sql 입니다.

 

 

SaveReqDto.java

import lombok.Data;

@Data
public class SaveReqDto {
	private int userId;
	private int boardId;
	private String content;
}

글저장에 필요한 데이터 전송 객체

 

 

ReplyService.java

import com.cos.blog.domain.reply.ReplyDao;
import com.cos.blog.domain.reply.dto.SaveReqDto;

public class ReplyService {

	private ReplyDao replyDao;

	public ReplyService() {
		replyDao = new ReplyDao();
	}

	public int 댓글쓰기(SaveReqDto dto) {
		return replyDao.save(dto);
	}
    
    public Reply 댓글찾기(int id) {
		return replyDao.findById(id);
	}
}

 

 

 

 

ReplyController.java (서블릿)

@WebServlet("/reply")
public class ReplyController extends HttpServlet {
	private static final long serialVersionUID = 1L;
    public ReplyController() {
        super();
    }
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doProcess(request, response);
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doProcess(request, response);
	}

	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String cmd = request.getParameter("cmd");
		ReplyService replyService = new ReplyService();
		// http://localhost:8080/blog/reply?cmd=save
		HttpSession session = request.getSession();

		if (cmd.equals("save")) {
			// ajax 요청으로 얻은 data를 Buffer로 받음
			BufferedReader br = request.getReader();
			String reqData = br.readLine();
			Gson gson = new Gson();
			SaveReqDto dto = gson.fromJson(reqData, SaveReqDto.class);
			System.out.println("dto : "+dto);

			CommonRespDto<Reply> commonRespDto = new CommonRespDto<>();
			Reply reply = null;
			int result = replyService.댓글쓰기(dto);
			if(result != -1) {
				reply = replyService.댓글찾기(result);
				commonRespDto.setStatusCode(1); //1, -1
				commonRespDto.setData(reply);
			}else {
				commonRespDto.setStatusCode(-1); //1, -1
			}
            
			String responseData = gson.toJson(commonRespDto); 
			System.out.println("responseData : "+responseData);
			Script.responseData(response, responseData);
		}
	}

}
function replySave(userId, boardId){

                var data = {
                  userId: userId,
                  boardId: boardId,
                  content: $("#content").val()
                }
                $.ajax({
                  type: "post",
                  url: "/blog/reply?cmd=save",
                  data: JSON.stringify(data),
                  contentType: "application/json; charset=utf-8",
                  dataType: "json"
                }).done(function(result){
                  if(result.statusCode == 1){
                  $("#reply__list").prepend("<div>"+data.content+"</div>")
                }else{
                  alert("댓글쓰기 실패");
                }
               	 });
                }

댓글쓰기 ajax가 호출되면 (cmd=save) 

BurrferedReader br에 ajax로 요청하면서 넘기는 값 { userId: ooo, boardId: ooo, content: #("content").val() }을

String 으로 reqData 변수 에 저장합니다

reqData = " { userId: ooo, boardId: ooo, content: #("content").val() } "

 

방금 만든 SaveReqDto 객체에 String reqData 를 gson.fromJson( json형태의 String , 변환할 객체.class ) 을 이용해서

JSON 데이터를 JAVA Object 화 시킵니다. ( 자바코드로 써먹을수가 있다. )

 

if-else : setStatusCode, setData해서 commonRespDto를 만든다.

 

Script.responseData() 를 써서 다시 gson.toJson( Json으로 변환하고싶은 JAVA Object ) 해서 응답한다.

(JAVA Object를 JSON으로 변환 시킵니다.)

 

 

 

 

 

Script.java

public static void responseData(HttpServletResponse response, String jsonData) {

		PrintWriter out;
		try {
			out = response.getWriter();
			out.print(jsonData);
			out.flush(); // 버퍼 비우기
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

.jsp 를 응답하는게아니라 data를 응답하기위해서 만든 함수입니다.

ReplyController 에서 사용하기위해 만들었습니다.

 

jsonData를 받아서 그냥 그대로 다시 PrintWriter 해서 응답해줍니다.

 

 

 

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

detail.jsp

<script> ajax 부분을 모두 지우고, 따로 외부 js파일로 만들것입니다.

 

<script src="/blog/js/boardDetail.js"></script>

 

boardDetail.js

function addReply(data){

	var replyItem = `<li id="reply-${data.id}" class="media">`;
	replyItem += `<div class="media-body">`;
	replyItem += `<strong class="text-primary">${data.userId}</strong>`;
	replyItem += `<p>${data.content}.</p></div>`;
	replyItem += `<div class="m-2">`;

	replyItem += `<i onclick="deleteReply(${data.id})" class="material-icons">delete</i></div></li>`;

	$("#reply__list").prepend(replyItem);
}

function deleteReply(id){
	// 세션의 유저의 id와 reply의 userId를 비교해서 같을때만!!
	alert("댓글 아이디 : "+id);
}

function replySave(userId, boardId) {

	var data = {
		userId : userId,
		boardId : boardId,
		content : $("#content").val()
	}

	$.ajax({
		type : "post",
		url : "/blog/reply?cmd=save",
		data : JSON.stringify(data),
		contentType : "application/json; charset=utf-8",
		dataType : "json"
	}).done(function(result) {
		if (result.statusCode == 1) {
			console.log(result);
			addReply(result.data);
			$("#content").val("");
		} else {
			alert("댓글쓰기 실패");
		}
	});
}


function deleteById(boardId){

	$.ajax({
		type: "post",
		url: "/blog/board?cmd=delete&id="+boardId,
		dataType: "json"
	}).done(function(result){
		console.log(result);
		if(result.statusCode == 1){
			location.href="index.jsp";
		}else{
			alert("삭제에 실패하였습니다.");
		}
	});
} 

댓글 삭제는 아직 미구현.

댓글이 prepend 되는것을  addReply 함수로 만들어서 적용시킴.

ㅎㅇ

 

 

detail.jsp

삭제버튼 옆에 수정버튼을 만들어줍시다

<div class="container">
	<c:if test="${sessionScope.principal.id == dto.userId}">
		<a href="/blog/board?cmd=updateForm&id=${dto.id}" class="btn btn-warning" >수정</a>
		<button onClick="deleteById(${dto.id})" class="btn btn-danger">삭제</button>
	</c:if>

 

 

 

updateForm.jsp

<%@page import="com.cos.blog.domain.user.User"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%@ include file="../layout/header.jsp" %>

<!-- 해당 페이지로 직접 URL(자원에 직접 파일.확장자) 접근을 하게 되면 또 파일 내부에서 세션 체크를 해야함. -->
<!-- 필터에 .jsp로 접근하는 모든 접근을 막아버리면 됨. -->

<div class="container">
	<form action="/blog/board?cmd=update" method="POST">
		<input type="hidden" name="id" value="${dto.id}" />
		<div class="form-group">
			<label for="title">Title:</label>
			<input type="text" class="form-control" placeholder="title" id="title" name="title"  value="${dto.title}" />
		</div>

		<div class="form-group">
			<label for="content">Content:</label>
			<textarea id="summernote" class="form-control" rows="5" id="content" name="content">
				${dto.content}
			</textarea>
		</div>

		<button type="submit" class="btn btn-primary">글쓰기 수정</button>
	</form>
</div>

  <script>
  	$('#summernote').summernote({
        placeholder: '글을 쓰세요.',
        tabsize: 2,
        height: 400
      });
  </script>
</body>
</html>

saveForm 복붙해서 만든겁니다. 

title 과 content에 작성했던 내용들이 있어야하니까 value = ${dto.title} 이렇게 넣어줬습니다

나중에 컨트롤러에서 setAttribute 해서 키값 dto로 줘야겠지요??

 

 

BoardDao.java

public int update(UpdateReqDto dto) {
		String sql = "UPDATE board SET title = ?, content = ? WHERE id = ?";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, dto.getTitle());
			pstmt.setString(2, dto.getContent());
			pstmt.setInt(3, dto.getId());
			int result = pstmt.executeUpdate();
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt);
		}
		return -1;
	}

무수한 SQL문의 반복

 

 

UpdateReqDto.java

package com.cos.blog.domain.board.dto;

import lombok.Data;

@Data
public class UpdateReqDto {
	private int id;
	private String title;
	private String content;
}

글 수정에만 필요한 데이터 전송객체

 

 

 

BoardService.java

public int 글수정(UpdateReqDto dto) {
		return boardDao.update(dto);
	}

 

 

 

 

 

BoardController.java

else if(cmd.equals("updateForm")) {
			int id = Integer.parseInt(request.getParameter("id"));
			DetailRespDto dto = boardService.글상세보기(id);
			request.setAttribute("dto", dto);
			RequestDispatcher dis = request.getRequestDispatcher("board/updateForm.jsp");
			dis.forward(request, response);
		}else if(cmd.equals("update")) {
			int id = Integer.parseInt(request.getParameter("id"));
			String title = request.getParameter("title");
			String content = request.getParameter("content");

			UpdateReqDto dto = new UpdateReqDto();
			dto.setId(id);
			dto.setTitle(title);
			dto.setContent(content);

			int result = boardService.글수정(dto);

			if(result == 1) {
				// 고민해보세요. 왜 RequestDispatcher 안썻는지... 한번 써보세요. detail.jsp 호출
				response.sendRedirect("/blog/board?cmd=detail&id="+id);
			}else {
				Script.back(response,"글 수정에 실패하였습니다.");
			}
		}

updateForm 은 글 수정하기 페이지를 들어갔을 때 ,

update는 글 수정 완료 버튼을 눌렀을 때.

 

 

결과

 

 

stackoverflow.com/questions/54273412/failed-to-install-the-following-android-sdk-packages-as-some-licences-have-not

 

 

 

 

SDK 패키지의 라이센스가 수락되지 않았습니다.   라고하는거 같다

 

원인이 정확하지는 않지만 안드로이드 폴더에서 Sdk 폴더를 지우고 새로만들고 내맘대로 조작하다가

오류가 발생한것같다.

 

먼저 SDK 경로 확인

 

 

 

 

 

 

 

쉬프트 우클릭을 하면 '여기에 Powershell 창 열기' 가 나온다.

 

 

 

스택오버플로우 형님이 가르쳐준대로 Windows 명령어 실행.

1/6 << 남은 5개도 Y 로 수락해주면 끝

 

 

 

머리아프시면 안드로이드 스튜디오 재설치 해보시는게 제일 빠르고 편한 방법입니다.

 

 

 



하이요

 

 

detail.jsp

detail.jsp 상단에 삭제 버튼을 만들어 줍니다.

그리고 자바스크립트, 제이쿼리를 이용한 ajax 문법입니다.

 

이전의 게시물에 ajax 정의를 했었지만 또 하겠습니다.

ㅡㅡㅡㅡAjax 통신 정의ㅡㅡㅡㅡㅡ

Ajax (Async Javascript And XML)는 웹 페이지에서 새로운 데이터를 보여주려고 할 때 웹페이지 전체를 새로고침 하지 않고, 보여주고자 하는 데이터가 포함된 페이지의 일부 만을 로드 하기 위한 기법입니다.

Ajax는 비동기 처리 모델 (또는 non-blocking 이라고도 함)을 사용하여 데이터를 처리합니다.

 

동기 처리 모델에서 브라우저는 자바스크립트 코드를 만나면 스크립트를 처리하기 전까지 다른 작업을 일시 중지하고, 자바스크립트 코드의 처리가 끝난 후 기존 작업을 진행합니다.

반면에 Ajax를 사용하면 브라우저는 서버에 데이터를 요청한 뒤 페이지의 나머지를 로드하고 페이지와 사용자의 상호작용을 처리합니다.

 

Ajax 동작방식

 

  • 요청(request) - 브라우저가 서버에 정보를 요청한다.
    type : 요청방식 (get, post, delete, put)   //  url : 요청주소 ( Controller 에서 Servlet이 처리함 )

    data : 서버로 보낼 데이터. 

    contentType : 응답받을 데이터의 형태, 보통은 데이터를 처리하기위해 JSON 이겠지만 
    Controller의 결과값을 보면 ok , fail 의 단순 문자열이기때문에 text

    응답에 성공을 한다면, done(function(data){  '대충 실행될 명령'   } ) 분기로 가서
    응답받은 데이터( data << Ajax 함수의 data 아님. Ajax의 결과값임 )로 해야할 일을 처리합니다.

  • 서버의 동작 - 서버는 JSON, XML 등의 형식으로 데이터를 전달한다.
    최근에는 JSON을 가장 많이 사용하고 있습니다.

  • 응답(response) - 브라우저에서 이벤트가 발생하여 콘텐츠를 처리한다.

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

더보기

<c:if test="${sessionScope.principal.id == dto.userId}">

<button onClick="deleteById(${dto.id})" class="btn btn-danger">삭제</button>

</c:if>

 

<script>

function deleteById(boardId){

$.ajax({

type: "post",

url: "/blog/board?cmd=delete&id="+boardId,

dataType: "json"

}).done(function(result){

console.log(result);

if(result.statusCode == 1){

location.href="index.jsp";

}else{

alert("삭제에 실패하였습니다.");

}

});

}

</script>

 

<c:if test="${sessionScope.principal.id == dto.userId}">
		<button onClick="deleteById(${dto.id})" class="btn btn-danger">삭제</button>
	</c:if>

	<script>
		function deleteById(boardId){
			$.ajax({
				type: "post",
				url: "/blog/board?cmd=delete&id="+boardId,
				dataType: "json"
			}).done(function(result){
				console.log(result);
				if(result.statusCode == 1){
					location.href="index.jsp";
				}else{
					alert("삭제에 실패하였습니다.");
				}
			});
		}
	</script>

 

 

 

BoardDao.java

실제로 글이 지워지도록 sql작성

public int deleteById(int id) { // 회원가입
		String sql = "DELETE FROM board WHERE id = ?";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, id);
			int result = pstmt.executeUpdate();
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt);
		}
		return -1;
	}

 

 

BoardService.java

public int 글삭제(int id) {
		return boardDao.deleteById(id);
	}

 

 

CommonRespDto.java

모든 요청,응답을 이 통합Dto 하나로 관리합니다.

제네릭을 사용하네요.

package com.cos.blog.domain.board.dto;

import lombok.Data;

@Data
public class CommonRespDto<T> {
	private int statusCode; // 1, -1
	private T data;
}

 

 

BoardController.java

else if(cmd.equals("delete")) {
			int id = Integer.parseInt(request.getParameter("id"));

			// DB에서 id값으로 글 삭제
			int result = boardService.글삭제(id);

			// 응답할 json 데이터를 생성
			CommonRespDto<String> commonRespDto = new CommonRespDto<>();
			commonRespDto.setStatusCode(result);
			commonRespDto.setData("성공");

			Gson gson = new Gson();
			String respData = gson.toJson(commonRespDto);
			System.out.println("respData : "+respData);
			PrintWriter out = response.getWriter();
			out.print(respData);
			out.flush();
		}

 

CommonRespDto 는 알아두면 편리한데 제가 아직 개념정리가 덜되어있어서 설명이 어렵습니다.

gston.toJson( JSON으로 변환하고싶은 객체 )

 

 

 

 

결과

 

자신이 작성한 글에만 버튼이 보임.

ㅎㅇ

 

 

detail.jsp

${dto} 는 나중에 Controller 에서 setAttribute 해서 주는 key 값입니다.

댓글창은 아직 구현하지 않고 임시로 만들어놓습니다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%@ include file="../layout/header.jsp" %>

<div class="container">
	<br />
	<br />
	<h6 class="m-2">
		작성자 : <i>${dto.username}</i> 조회수 : <i>${dto.readCount}</i> 글 번호 : <i>${dto.id}</i>
	</h6>
	<br />
	<h3 class="m-2">
		<b>${dto.title}</b>
	</h3>
	<hr />
	<div class="form-group">
		<div class="m-2">${dto.content}</div>
	</div>

	<hr />

	<!-- 댓글 박스 -->
	<div class="row bootstrap snippets">
		<div class="col-md-12">
			<div class="comment-wrapper">
				<div class="panel panel-info">
					<div class="panel-heading m-2"><b>Comment</b></div>
					<div class="panel-body">
						<textarea id="reply__write__form" class="form-control" placeholder="write a comment..." rows="2"></textarea>
						<br>
						<button onclick="#" class="btn btn-primary pull-right">댓글쓰기</button>
						<div class="clearfix"></div>
						<hr />

						<!-- 댓글 리스트 시작-->
						<ul id="reply__list" class="media-list">

								<!-- 댓글 아이템 -->
								<li id="reply-1" class="media">		
									<div class="media-body">
										<strong class="text-primary">홍길동</strong>
										<p>
											댓글입니다.
										</p>
									</div>
									<div class="m-2">

										<i onclick="#" class="material-icons">delete</i>

									</div>
								</li>

						</ul>
						<!-- 댓글 리스트 끝-->
					</div>
				</div>
			</div>

		</div>
	</div>
	<!-- 댓글 박스 끝 -->
</div>

</body>
</html>

 

 

 

header.jsp

? 뭐에 쓰이는지 모르겠네요

<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

 

 

 

 

DetailRespDto.java

글 상세보기 기능의 데이터운반을 도와주는 객체

package com.cos.blog.domain.board.dto;

import lombok.Data;

@Data
public class DetailRespDto {
	private int id;
	private String title;
	private String content;
	private int readCount;
	private String username;
	private int userId;
    
	public String getTitle() {
		return title.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
	}
}

 

 

 

BoardDao.java

findById(int id)

글 상세보기를 하기 위해서 Board 와 User를 join 한 결과에서 데이터를 가져옵니다.

왜냐하면 글에 대한 정보도 필요하고, 유저(작성자) 에 대한 정보도 필요하기 때문입니다.

 

updateReadCount(int id)

조회수를 1 증가시켜서 업데이트합니다.

public DetailRespDto findById(int id){
		StringBuffer sb = new StringBuffer();
		sb.append("select b.id, b.title, b.content, b.readCount, b.userId, u.username ");
		sb.append("from board b inner join user u ");
		sb.append("on b.userId = u.id ");
		sb.append("where b.id = ?");

		String sql = sb.toString();
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, id);
			rs =  pstmt.executeQuery();

			// Persistence API
			if(rs.next()) { // 커서를 이동하는 함수
				DetailRespDto dto = new DetailRespDto();
				dto.setId(rs.getInt("b.id"));
				dto.setTitle(rs.getString("b.title"));
				dto.setContent(rs.getString("b.content"));
				dto.setReadCount(rs.getInt("b.readCount"));
                dto.setUserId(rs.getInt("b.userId"));
				dto.setUsername(rs.getString("u.username"));
				return dto;
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}
		return null;
	}
    
    
    
    
    public int updateReadCount(int id) { // 회원가입
		String sql = "UPDATE board SET readCount = readCount+1 WHERE id = ?";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, id);
			int result = pstmt.executeUpdate();
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt);
		}
		return -1;
	}

 

BoardService.java

// 하나의 서비스안에 여러가지 DB관련 로직이 섞여 있죠.
	public DetailRespDto 글상세보기(int id) {
		// 조회수 업데이트치기
		int result = boardDao.updateReadCount(id);
		if(result == 1) {
			return boardDao.findById(id);
		}else {
			return null;
		}
	}

 

 

BoardController.java

list.jsp에서 상세보기 버튼을 누르게되면 cmd=detail 이 호출됩니다.

//list.jsp

<c:forEach var="board" items="${boards}">
		<div class="card col-md-12 m-2">
			<div class="card-body">
				<h4 class="card-title">${board.title}</h4>
				<a href="/blog/board?cmd=detail&id=${board.id}" class="btn btn-primary">상세보기</a>
			</div>
			<div>글 번호 : ${board.id }</div>
		</div>
	</c:forEach>

id값을 getParameter로 가져와서 자바코드에서 사용합니다.

글 상세보기(id)로 가져온 DetailRespDto 객체를 setAttribute 하여 dto 키값으로 jsp파일로 넘깁니다.

 

이 작업을 했기 때문에 detail.jsp 에서 ${dto} 를 사용할 수 있는것입니다.

else if(cmd.equals("detail")) {
			int id = Integer.parseInt(request.getParameter("id"));
			DetailRespDto dto = boardService.글상세보기(id); // board테이블+user테이블 = 조인된 데이터!!
			if(dto == null) {
				Script.back(response, "상세보기에 실패하였습니다");
			}else {
				request.setAttribute("dto", dto);
				//System.out.println("DetailRespDto : "+dto);
				RequestDispatcher dis = request.getRequestDispatcher("board/detail.jsp");
				dis.forward(request, response);
			}
		}

 

 

 

 

 

 

글 상세보기 결과

 

ㅎㅇㅎㅇㅎㅇ ㅠㅠ

 

 

 

list.jsp

jstl 문법의 choose - when / otherwise 를 이용해서 페이지 버튼의 disable 구현을 해보겠습니다.

currentPosition 과 lastpage 를 연산하는 코드는 아래에 있습니다 .

<div class="progress col-md-12 m-2">
		<div class="progress-bar" style="width: ${currentPosition}%"></div>
	</div>

	<!-- JSTL foreach문을 써서 뿌리세요. el표현식과 함께 -->
@@ -34,8 +34,25 @@
	<br />
	<!-- disabled -->
	<ul class="pagination justify-content-center">
		<c:choose>
			<c:when test="${param.page == 0}">
				<li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>	
			</c:when>
			<c:otherwise>
				<li class="page-item"><a class="page-link" href="/blog/board?cmd=list&page=${param.page-1}">Previous</a></li>
			</c:otherwise>
		</c:choose>

		<c:choose>
			<c:when test="${lastPage == param.page}">
				<li class="page-item disabled"><a class="page-link" href="#">Next</a></li>		
			</c:when>
			<c:otherwise>
				<li class="page-item"><a class="page-link" href="/blog/board?cmd=list&page=${param.page+1}">Next</a></li>
			</c:otherwise>
		</c:choose>


	</ul>
</div>

 

 

BoardDao.java

board의 레코드 개수 (count 함수) 를 가져오는 함수입니다.

public int count() {
		String sql = "SELECT count(*), id FROM board";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;

		try {
			pstmt = conn.prepareStatement(sql);
			rs =  pstmt.executeQuery();
			if(rs.next()) {
				return rs.getInt(1);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}
		return -1;
	}

 

 

BoardService.java

서비스 추가~

public int 글개수() {
		return boardDao.count();
	}

 

 

BoardController.java

list가 호출될때마다 lastpage 와 currentPosition 을 연산해줍니다. 

jsp에서도 사용할 수 있도록 setAttribute를 이용하여 키값과 데이터를 넘겨줍니다.

else if (cmd.equals("list")) {
			int page = Integer.parseInt(request.getParameter("page"));  // 최초 : 0, Next : 1, Next: 2
			List<Board> boards = boardService.글목록보기(page);
			request.setAttribute("boards", boards);

			// 계산 (전체 데이터수랑 한페이지몇개 - 총 몇페이지 나와야되는 계산) 3page라면 page의 맥스값은 2
			// page == 2가 되는 순간  isEnd = true
			// request.setAttribute("isEnd", true);
			int boardCount = boardService.글개수();
			int lastPage = (boardCount-1)/4; // 2/4 = 0, 3/4 = 0, 4/4 = 1, 9/4 = 2 ( 0page, 1page, 2page) 
			double currentPosition = (double)page/(lastPage)*100;

			request.setAttribute("lastPage", lastPage);
			request.setAttribute("currentPosition", currentPosition);
			RequestDispatcher dis = request.getRequestDispatcher("board/list.jsp");
			dis.forward(request, response);
		}

 

 

 

3페이지 (3/4 페이지)

 

 

 

 

 

 

4페이지(마지막페이지)

 

ㅎㅇㅎㅇ

 

List.jsp

페이지 하단에 previous , next 를 완성시켜줍니다.

버튼을 누르면 page값을 변동하여 이동하게 되는거같네요

 

jstl 문법 : ${param.변수명} 으로 변수를 바로 사용할 수 있다.

( 이렇게 말해야 되는게 맞는가 ? )

<ul class="pagination justify-content-center">
		<li class="page-item"><a class="page-link" href="/blog/board?cmd=list&page=${param.page-1}">Previous</a></li>
		<li class="page-item"><a class="page-link" href="/blog/board?cmd=list&page=${param.page+1}">Next</a></li>
	</ul>

 

Index.jsp

인덱스 페이지도 페이지 관리를 해주고.

<%
	RequestDispatcher dis = 
		request.getRequestDispatcher("board?cmd=list&page=0"); 
	dis.forward(request, response); // 톰켓이 생성하는 request와 response를 재사용한다. 다시 접근하는게 아니라 내부적으로 움직인다는 뜻.
%> 

 

 

BoardDao.java

LIMIT 을 추가해서 한 페이지에 게시글 4개만 불러와지도록 findAll 함수를 수정하겠습니다.

page 파라미터가 생겼어요.

public List<Board> findAll(int page){
		String sql = "SELECT * FROM  board ORDER BY id DESC LIMIT ?, 4"; // 0,4   4,4   8,4
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;
		List<Board> boards = new ArrayList<>();
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, page*4); // 0 -> 0, 1 ->4, 2->8
			rs =  pstmt.executeQuery();

			// Persistence API
			while(rs.next()) { // 커서를 이동하는 함수
				Board board = Board.builder()
						.id(rs.getInt("id"))
						.title(rs.getString("title"))
						.content(rs.getString("content"))
						.readCount(rs.getInt("readCount"))
						.userId(rs.getInt("userId"))
						.createDate(rs.getTimestamp("createDate"))
						.build();
				boards.add(board);	
			}
			return boards;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}
		
		return null;
	}

BoardService.java

page 파라미터가 생겼어요.

public List<Board> 글목록보기(int page){
		return boardDao.findAll(page);
	}

 

BoardController.java

page 파라미터가 생겼어요.

cmd=list 에서 page를 함께 받는것이기 때문에 이렇게 수정했어요.

else if (cmd.equals("list")) {
			int page = Integer.parseInt(request.getParameter("page"));  // 최초 : 0, Next : 1, Next: 2
			List<Board> boards = boardService.글목록보기(page);
			request.setAttribute("boards", boards);

			RequestDispatcher dis = request.getRequestDispatcher("board/list.jsp");
			dis.forward(request, response);
		}

 

 

 

첫 페이지

 

 

마지막페이지

 

저는 미리 완성된 블로그로 사진을 캡쳐하고있어서, 양 끝 페이지에 버튼이 disable 되었습니다.

다음 게시물에서 소개해드리겠습니다.

ㅎㅇ

일단 티스토리 글쓰기 << ㅈㄴ 이상합니다 .

원래 지원하던 다음블로그 ? 에디터가 좋았는데 Flash 가 2021년에 지원중단되면서 티스토리가 지원하는 신 에디터로 글을 써야하거든요

신 에디터 사진넣는거, 이건 좋ㄷ ㅏ이말이야. 근데 글자 폰트조절을 못하겠다는거 >> 화난다 .

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

글 목록 보기를 구현해봅시다.

'글 목록' 을 볼려면 글이 저장되어있는 데이터베이스를 Select 해야할것입니다.

데이터베이스가 어떻게 생겼었는지 다시 면상이나 한 번 보죠 ~

사실 제가 글쓰는 텀이 너무 길어서 까먹었습니다.

 

MySQL

 

create user 'bloguser'@'%' identified by '비밀번호';
GRANT ALL PRIVILEGES ON *.* TO 'bloguser'@'%';
create database blog; 
use blog;


drop table user;
drop table board;
drop table reply;

CREATE TABLE user(
	id int primary key auto_increment,
    username varchar(100) not null unique,
    password varchar(100) not null,
    email varchar(100) not null,
    address varchar(100),
    userRole varchar(20),
    createDate timestamp
) ;

CREATE TABLE board(
	id int primary key auto_increment,
    userId int,
    title varchar(100) not null,
    content longtext,
    readCount int default 0,
    createDate timestamp,
    foreign key (userId) references user (id)
);

CREATE TABLE reply(
	id int primary key auto_increment,
    userId int,
    boardId int,
    content varchar(300) not null,
    createDate timestamp,
    foreign key (userId) references user (id) on delete set null,
    foreign key (boardId) references board (id) on delete cascade
);

우리가 봐야할 데이터는 Board .

 

 

BoardDao.java

public List<Board> findAll(){
		String sql = "SELECT * FROM  board ORDER BY id DESC";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		ResultSet rs  = null;
		List<Board> boards = new ArrayList<>();
		try {
			pstmt = conn.prepareStatement(sql);
			rs =  pstmt.executeQuery();

			// Persistence API
			while(rs.next()) { // 커서를 이동하는 함수
				Board board = Board.builder()
						.id(rs.getInt("id"))
						.title(rs.getString("title"))
						.content(rs.getString("content"))
						.readCount(rs.getInt("readCount"))
						.userId(rs.getInt("userId"))
						.createDate(rs.getTimestamp("createDate"))
						.build();
				boards.add(board);	
			}
			return boards;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt, rs);
		}

		return null;
	}

BoardDao에 findAll() 함수 추가.

DB에서 Board를 SELECT 해서 싸ㅡ악 가져오네요

 

BoardService.java

public List<Board> 글목록보기(){
		return boardDao.findAll();
	}

BoardService에 글목록보기() 함수 추가. ( List 를 반환합니다 ~ )

 

BoardController.java

else if (cmd.equals("list")) {
			List<Board> boards = boardService.글목록보기();
			request.setAttribute("boards", boards);
			RequestDispatcher dis = request.getRequestDispatcher("board/list.jsp");
			dis.forward(request, response);
		}

BoardController에 cmd.equlas("list") 코드 추가.

글목록보기 함수의 결과값을 boards  List변수에 담습니다.

jsp에서 boards 데이터를 쓸 수 있도록 하기위해서 request.setAttribute(key , value) 를 해줍니다.

 

 

 

List.jsp

 저 부분에 DB에서 가져온 Board 데이터를 뿌려보겠습니다. 

 

<div class="container">

	<div class="m-2">
		<form class="form-inline d-flex justify-content-end" action="/blog/board">
			<input type="hidden" name="cmd" value="search" />
			<input type="hidden" name="page" value="0" />

			<input type="text" name="keyword" class="form-control mr-sm-2" placeholder="Search">			
			<button class="btn btn-primary m-1">검색</button>

		</form>
	</div>

	<div class="progress col-md-12 m-2">
		<div class="progress-bar" style="width: 70%"></div>
	</div>

		<!-- JSTL foreach문을 써서 뿌리세요. el표현식과 함께 -->

	<c:forEach var="board" items="${boards}">
		<div class="card col-md-12 m-2">
			<div class="card-body">
				<h4 class="card-title">${board.title}</h4>
				<a href="/blog/board?cmd=detail&id=${board.id}" class="btn btn-primary">상세보기</a>
			</div>
            <div>글 번호 : ${board.id }</div>
		</div>
	</c:forEach>

	<br />
	<ul class="pagination justify-content-center">
		<li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
		<li class="page-item"><a class="page-link" href="#">Next</a></li>
	</ul>
</div>

</body>
</html>

Controller 에서 jsp에서 사용하기위해서 setAttribute를 사용했다고 했었죠?

boards 라는 key 로 반복문(forEach) 을 사용하고있네요. 그리고 중간중간 ${board.title} 으로 

데이터를 get 쓰듯이 불러오고 있어요.

 

 

index.jsp에 sendRedirect를 dispatcher 로 고치셨나요 ??

<%
	RequestDispatcher dis = 
		request.getRequestDispatcher("board?cmd=list"); 
	dis.forward(request, response); // 톰켓이 생성하는 request와 response를 재사용한다. 다시 접근하는게 아니라 내부적으로 움직인다는 뜻.
%> 

 

 

 

 

 

결과

자바스크립트 배열 CRUD  (map, filter, slice, concat, spread연산자)

 

 

스프레드(전개)연산 ...
추가하기 concat
삭제하기(걸러내기) filter 
잘라내기 slice
반복하기 map (배열 전체복사)
수정하기 ...연산 응용

 

 

 

 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
 <script>
  // concat, filter, map, slice, 스프레드(전개) 연산자
  
  console.log("1. =============== 스프레드 연산자");
  const a = [1,2,3];
  const b = [...a];
  b.push(4); // b의 데이터 변경
  console.log(`a의 값은 : ${a}`); // 1,2,3
  console.log(`b의 값은 : ${b}`); // 1,2,3,4

  console.log("2. =============== 추가하기");
  const a2 = [1,2,3];
  const b2 = a2.concat(4);
  console.log(`a2의 값은 : ${a2}`); // 1,2,3
  console.log(`b2의 값은 : ${b2}`); // 1,2,3,4
  const c2 = [0, ...a2, 4];
  console.log(`c2의 값은 : ${c2}`); // 0,1,2,3,4

  console.log("3. =============== 걸러내기"); // 삭제하기
  const a3 = [1,2,3];
  const b3 = a3.filter((n)=> { return n !=1}); // bool을 return 받는다. -> true만 걸러낸다.
  console.log(`b3의 값은 : ${b3}`); // 2,3

  console.log("4. =============== 잘라내기");
  const a4 = [1,2,3];
  const b4 = a4.slice(0,2);
  console.log(b4); // [1,2]
  const c4 = [...a4.slice(0,2), 4, ...a4.slice(2,3)];
  console.log(c4); // [1,2,4,3]

  console.log("5. =============== 반복하기");
  const a5 = [1,2,3];
  //for(let i=0; i<a5.length; i++){
  //  console.log(a5[i]);
  //}
  // a5.forEach((n) => {console.log(n);}); // 리턴 못함.
  const b5 = a5.map((n) => n+10);  // const b5 = [...a5];
  console.log(b5);

const data = {phone:"2222"};
  const a6 = { id:1, name:"홍길동", phone:"1111", age:17, gender:"남"};
  const b6 = { ...a6, ...data}; //기존 데이터에 덮어씌움
  console.log(b6);


  const users = [
    {id:1, name:"구태모", phone:"2222"},
    {id:2, name:"이대엽", phone:"3333"},
    {id:3, name:"오승훈", phone:"4444"}
  ];

  const updateUserDto = {
    id:2, name:"홍길동"
  };

  const newUsers = users.
    map(user => user.id === updateUserDto.id ? {...user, ...updateUserDto} :user); // const newUser = {...users};
  
  console.log("newUsers", newUsers);
 </script>
</body>
</html>

결과 ( 깊은복사 )

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

 

import { useState } from 'react';
import './App.css';

function App() {
  // 다운로드 받음
  const [users, setUsers] = useState([
    { id: 1, name: '홍길동' },
    { id: 2, name: '임꺽정' },
    { id: 3, name: '장보고' },
    { id: 4, name: '코스' },
  ]);

  const download = () => {
    let sample = [
      { id: 1, name: '홍길동' },
      { id: 2, name: '임꺽정' },
      { id: 3, name: '장보고' },
      { id: 4, name: '코스' },
    ];

    setUsers([...sample]);
  };

  // 랜더링 시점 = 상태값 변경
  return (
    <div>
      <button onClick={download}>다운로드</button>
      {users.map((u) => (
        <h1>
          {u.id}, {u.name}
        </h1>
      ))}
    </div>
  );
}

export default App;

 

 

리엑트의 렌더링 시점 = 상태값 변경시점.
일반 변수를 상태 변수로 바꾸는법

React의 hooks 라이브러리를 사용함.
const [요소, set요소] = useState( );

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

상태변수 이기때문에 원래있던 값에 변경된 값을 덮어씌우는 형식이다.

조금 더 다듬어서 저장하겠습니다.

npm start 명령어를 실행하면

프로젝트 내부에있는 index.js 가 실행된다. 그속에는

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
)

라는 render 함수가 있고, 중간에있는 App 은 [컨트롤 + 클릭] 해보면 App.js를 가리키는 문법입니다.

 

  document.getElementById('root') 는 프로젝트 폴더가 만들어질때 같이 만들어진 public 이라는 폴더를 보시면

 

index.html 이라는 파일에서 root라는 id의 태그를 찾겠다는 뜻입니다.

index.html 생긴거보면 주석빼면 걍 아무것도없는 파일입니다

index.js의 최종 목적은 저 사이에 App.js 가 return 하고있는 코드를 집어넣겠다는 뜻입니다.

 

그렇기에 JS파일(App.js)에 HTML 문법을 써넣는 기술이 필요하고 그것을 JSX 문법이라고 합니다. 

 

 

옛날 리액트는 클래스 컴포넌트 방식을 사용했지만, 요즘은 함수형 컴포넌트 방식(Hooks)를 더 많이 사용합니다.

앞으로 사용하는 컴포넌트들은 모두 함수형 컴포넌트 방식을 사용할것입니다.

 

JSX 기본 문법

1. return은 하나의 태그만 가능하다.

2. 자바스크립트의 변수를 HTML 코드안에서 사용하려면 { } 중괄호 문법을 사용해야한다.

3. if문은 사용할 수 없고 삼항연산자를 사용해야한다. ( 조건 ?  참 실행문 : 거짓 실행문 )

4. 조건부 렌더링이 가능하다.(if문 처럼생김) ( 조건 && 참 실행문 )

 

 

React(리액트) 란 프론트엔드 개발 기술로,

브라우저에 변경된 데이터를 감지하여 그림을 그리는데 최적화 된 기술입니다.

 

 

리액트를 실습해 보기 위해서는 '변경 감지'를 무자비하게 인식해줄 '서버'가 필요합니다.

요청을 하면 응답을 해주는것 = 서버

 

 

node.js 서버를 사용하겠습니다.

nodejs.org/ko/

 

Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

작성시간 기준으로 LTS 버전이 14.15.4 버전이네요.

최신버전 보다는 LTS로 다운받아주세요

 

 

프론트엔드는 MS Visual Code 를 사용할겁니다.

백엔드는 나중에 Spring으로 사용할 예정이지만 지금은 필요없습니다.

 

 

원하는 경로에 프로젝트용 새 폴더를 만들고 MS Code 에서 Add WorkSpace 를 합니다

 

 

create-react-app.dev/docs/getting-started/

 

Getting Started | Create React App

Create React App is an officially supported way to create single-page React

create-react-app.dev

새 터미널을 여시고 (저는 cmd를 사용했습니다. 탭에 Terminal)

npx create-react-app my-app
cd my-app
npm start

my-app 이라는 이름으로 프로젝트를 만드는 명령어입니다.

npm start는 서버를 실행하는거구요

 

 

예상경로 :   C:\workspace\my-app> npm start

성공하셨다면 리액트로고가 빙글빙글 돌아가는 화면이 나올겁니다~ 

 

 

 

다음은 리액트 사용에 편리한 확장프로그램들 입니다

ESLint 는 리액트 문법을 검사해주는 프로그램이고,

자바스크립트는 인터프리터 언어이기때문에 실행 전까지는 오류를 알 수가 없습니다.

 

Reactjs code snippets 는 리액트에서 자주쓰이는 문법들을 자동완성 시켜주는 프로그램입니다.

 

 

 

 

 

 

 

ㅎㅇ

 

글쓰기 작성페이지 SaveForm.jsp 에서는

<%@page import="com.cos.blog.domain.user.User"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%@ include file="../layout/header.jsp" %>

<!-- 해당 페이지로 직접 URL(자원에 직접 파일.확장자) 접근을 하게 되면 또 파일 내부에서 세션 체크를 해야함. -->
<!-- 필터에 .jsp로 접근하는 모든 접근을 막아버리면 됨. -->

<div class="container">
	<form action="/blog/board?cmd=save" method="POST">
		<input type="hidden" name="userId" value="${sessionScope.principal.id}" />
		<div class="form-group">
			<label for="title">Title:</label>
			<input type="text" class="form-control" placeholder="title" id="title" name="title">
		</div>
	
		<div class="form-group">
			<label for="content">Content:</label>
			<textarea id="summernote" class="form-control" rows="5" id="content" name="content"></textarea>
		</div>
	
		<button type="submit" class="btn btn-primary">글쓰기 등록</button>
	</form>
</div>
  <script>
  	$('#summernote').summernote({
        placeholder: '글을 쓰세요.',
        tabsize: 2,
        height: 400
      });
  </script>
</body>
</html>

type="hidden " userId 값을 전송해야한다.

그렇다면 value값은 어디서 정해지는걸까 ?

답 : ${ } (EL표현식)을 사용하여 EL내장객체인 SessionScope에 접근하여 가져온다.

 

 

else if(cmd.equals("login")) { //로그인 완료
			// 서비스 호출
			String username = request.getParameter("username");
			String password = request.getParameter("password");
			LoginReqDto dto = new LoginReqDto();
			dto.setUsername(username);
			dto.setPassword(password);
			
			User userEntity = userService.로그인(dto);
			if(userEntity != null) {
				HttpSession session = request.getSession();
				session.setAttribute("principal", userEntity); //인증주체
				//response.sendRedirect("index.jsp"); //로그인 성공
				RequestDispatcher dis = request.getRequestDispatcher("index.jsp");
				dis.forward(request, response);
			}else {
				Script.back(response, "로그인 실패");
			}
			
			
		}

key값인 principal 은 UserController에서 cmd.equals("login") 기능 내부에서

session.setAttribute() 되어서 RequestDispatcher를 사용하여 저장된다

 

 

 

이번 글에서 설명하고자 하는것은

"로그인을 하지않은 사용자가 글쓰기를 시도하려한다면?" 에 대한 방법입니다.

 

config 패키지 

ForbiddenUrlConfig.java

package com.cos.blog.config;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

// 이제부터는 내부에서의 모든 요청은 RequestDispatcher로 해야한다. 
// 그래야 다시 필터를 타지 않는다.

public class ForbiddenUrlConfig implements Filter{

	@Override
	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
    	HttpServletResponse response = (HttpServletResponse) resp;

    	System.out.println("ForbiddenUrlConfig 접근");
    	System.out.println(request.getRequestURL());
    	System.out.println(request.getRequestURI());

    	if(request.getRequestURI().equals("/blog/") || request.getRequestURI().equals("/blog/index.jsp")) {
    		chain.doFilter(request, response);
    	}else {
    		PrintWriter out = response.getWriter();
    		out.print("잘못된 접근입니다.");
    		out.flush();
    	}

	}
}

 

WEB-INF 폴더에 있는

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="4.0"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee                       http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd">
	
	<description>MySQL Test App</description>
	<resource-ref>
		<description>DB Connection</description>
		<res-ref-name>jdbc/TestDB</res-ref-name>
		<res-type>javax.sql.DataSource</res-type>
		<res-auth>Container</res-auth>
	</resource-ref>
	
 	<filter>
		<filter-name>charConfig</filter-name>
		<filter-class>com.cos.blog.config.CharConfig</filter-class>
	</filter>

	<filter>
		<filter-name>forbiddenUrlConfig</filter-name>
		<filter-class>com.cos.blog.config.ForbiddenUrlConfig</filter-class>
	</filter>

	<!-- 매핑순서가 1 -->
	<filter-mapping>
		<filter-name>charConfig</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>



	<!-- 매핑순서가 2	 -->
	<filter-mapping>
		<filter-name>forbiddenUrlConfig</filter-name>
		<url-pattern>*.jsp</url-pattern>
	</filter-mapping>
</web-app>

만들었던 필터를 등록시킨다. 완전히 적용시키려면 서버를 재실행해야한다.

 

 

페이지 이동방법 설명

sendRedirect() = 외부로 갔다가 내부필터에 다시 들어옴

RequestDispatcher() = 톰캣이 생성하는 request, response를 재사용하여 덮어씌움

 

그래서 프로젝트 중에 사용했던 sendRedirect()를 모두 RequestDispatcher()로 바꿔주어야한다.

그렇지않으면 방금 만든 필터때문에 잘못된 접근 경고페이지를 보게될것이다.

바꾸어야할 파일 : UserController.java , BoardController.java .... 등등 sendRedirect() 사용했던 모든곳

 

예시

else if(cmd.equals("joinForm")) {
			//response.sendRedirect("user/joinForm.jsp");
			RequestDispatcher dis = 
					request.getRequestDispatcher("user/joinForm.jsp");
				dis.forward(request, response);
		}

 

 

 

ㅎㅇㅎㅇ

내용 정리할 시간이 너무 부족하네요 오랜만에 씁니다

 

blog 2 에서만들었던 빈깡통파일 list.jsp을 조금 채워봅시다.

list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%@ include file="../layout/header.jsp" %>

<div class="container">

	<div class="m-2">
		<form class="form-inline d-flex justify-content-end" action="/blog/board">
			<input type="hidden" name="cmd" value="search" />
			<input type="hidden" name="page" value="0" />

			<input type="text" name="keyword" class="form-control mr-sm-2" placeholder="Search">			
			<button class="btn btn-primary m-1">검색</button>

		</form>
	</div>

	<div class="progress col-md-12 m-2">
		<div class="progress-bar" style="width: 70%"></div>
	</div>

		<div class="card col-md-12 m-2">
			<div class="card-body">
				<h4 class="card-title">제목</h4>
				<a href="#" class="btn btn-primary">상세보기</a>
			</div>
		</div>

	<br />
	<ul class="pagination justify-content-center">
		<li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
		<li class="page-item"><a class="page-link" href="#">Next</a></li>
	</ul>
</div>

</body>
</html>

지금은 데이터가 있는 척~ 하면서 div - card 를 만들었지만 이후에, JSTL을 사용해서 글 목록을 불러와서 뿌릴겁니다.

 

 

글작성을 위한 페이지 작업

header.jsp

헤더부분에 summernote 를 사용하기위해 몇 줄 추가해줍니다.

<link
	href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css"
	rel="stylesheet">
<script
	src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script>
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Cos 블로그</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
	href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<script
	src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script
	src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script
	src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<link
	href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css"
	rel="stylesheet">
<script
	src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script>
</head>
<body>

	<nav class="navbar navbar-expand-md bg-dark navbar-dark">
		<a class="navbar-brand" href="<%=request.getContextPath()%>/index.jsp">블로그</a>
		<button class="navbar-toggler" type="button" data-toggle="collapse"
			data-target="#collapsibleNavbar">
			<span class="navbar-toggler-icon"></span>
		</button>
		<c:choose>
			<c:when test="${sessionScope.principal != null}">
				<div class="collapse navbar-collapse" id="collapsibleNavbar">
					<ul class="navbar-nav">
						<li class="nav-item"><a class="nav-link"
							href="<%=request.getContextPath()%>/board?cmd=saveForm">글쓰기</a></li>
						<li class="nav-item"><a class="nav-link"
							href="<%=request.getContextPath()%>/user?cmd=updateForm">회원정보</a>
						</li>
						<li class="nav-item"><a class="nav-link"
							href="<%=request.getContextPath()%>/user?cmd=logout">로그아웃</a></li>
					</ul>
				</div>
			</c:when>
			<c:otherwise>
				<div class="collapse navbar-collapse" id="collapsibleNavbar">
					<ul class="navbar-nav">
						<li class="nav-item"><a class="nav-link"
							href="<%=request.getContextPath()%>/user?cmd=joinForm">회원가입</a></li>
						<li class="nav-item"><a class="nav-link"
							href="<%=request.getContextPath()%>/user?cmd=loginForm">로그인</a></li>
					</ul>
				</div>
			</c:otherwise>
		</c:choose>
	</nav>
	<br>

여기서 이미 JSTL이 사용되고 있었네요 파일 상단에

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

이렇게 선언을 해주시면 jsp에서 java문법과 유사한 JSTL을 사용하실 수 있습니다.

<c: choose> , <c:when> , <c:otherwise> 가 사용되었네요 이 놈들은 if - else 처럼 사용되고있습니다.

 

 

<c:when test = "${sessionScope.principal != null}">

test 가 조건문입니다.  (principal : 로그인 인증 주체)

principal 이 null 이면 로그인 안한 상태 -> 로그인, 회원가입이 있는 View를 보여줌

principal 이 null 이 아니면 로그인 한 상태 -> 로그아웃, 글작성이 있는 View를 보여줌

 

saveForm.jsp

<%@page import="com.cos.blog.domain.user.User"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%@ include file="../layout/header.jsp" %>

<div class="container">
	<form action="#" method="POST">

		<form action="/blog/board?cmd=save" method="POST">
		<input type="hidden" name="userId" value="${sessionScope.principal.id}" />
			<label for="title">Title:</label>
			<input type="text" class="form-control" placeholder="title" id="title" name="title">
		</div>

		<div class="form-group">
			<label for="content">Content:</label>
			<textarea id="summernote" class="form-control" rows="5" id="content" name="content"></textarea>
		</div>

		<button type="submit" class="btn btn-primary">글쓰기 등록</button>
	</form>
</div>

  <script>
  	$('#summernote').summernote({
        placeholder: '글을 쓰세요.',
        tabsize: 2,
        height: 400
      });
  </script>
</body>
</html>

 

 

 

 

 

 

글작성준비 완료

제법 그럴싸해보이죠

이것이 라이브러리의 힘??

 

 

 

saveForm의 action에 cmd=save 를 해줬으니 Controller에 cmd.equals("save")의 기능을 만들어줘야합니다.

 

BoardController.java (Servlet 파일입니다)

package com.cos.blog.web;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.cos.blog.domain.board.dto.SaveReqDto;
import com.cos.blog.domain.user.User;
import com.cos.blog.service.BoardrService;
import com.cos.blog.util.Script;

// http://localhost:8080/blog/board
@WebServlet("/board")
public class BoardController extends HttpServlet {
	private static final long serialVersionUID = 1L;
    public BoardController() {
        super();
    }
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doProcess(request, response);
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doProcess(request, response);
	}

	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		String cmd = request.getParameter("cmd");
		BoardrService boardService = new BoardrService();
		// http://localhost:8080/blog/board?cmd=saveForm
		HttpSession session = request.getSession();
		if(cmd.equals("saveForm")) {
			User principal = (User) session.getAttribute("principal");
			if(principal != null) {
				response.sendRedirect("board/saveForm.jsp");
			}else {
				response.sendRedirect("user/loginForm.jsp");
			}	
		}else if(cmd.equals("save")) {
			int userId = Integer.parseInt(request.getParameter("userId"));
			String title = request.getParameter("title");
			String content = request.getParameter("content");

			SaveReqDto dto = new SaveReqDto();
			dto.setUserId(userId);
			dto.setTitle(title);
			dto.setContent(content);
			int result = boardService.글쓰기(dto);
			if(result == 1) { //정상
				response.sendRedirect("index.jsp");
			}else {
				Script.back(response, "글쓰기실패");
			}
		}
	}



}

SaveReqDto.java (글 저장'만'을 위한 Data Transfer Object)

package com.cos.blog.domain.board.dto;

import lombok.Data;

@Data
public class SaveReqDto {
	private int userId;
	private String title;
	private String content;
}

 

BoardDao.java (DB 작업)

package com.cos.blog.domain.board;

import java.sql.Connection;
import java.sql.PreparedStatement;

import com.cos.blog.config.DB;
import com.cos.blog.domain.board.dto.SaveReqDto;
import com.cos.blog.domain.user.dto.JoinReqDto;

public class BoardDao {

	public int save(SaveReqDto dto) { // 회원가입
		String sql = "INSERT INTO board(userId, title, content, createDate) VALUES(?,?,?, now())";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, dto.getUserId());
			pstmt.setString(2, dto.getTitle());
			pstmt.setString(3, dto.getContent());
			int result = pstmt.executeUpdate();
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt);
		}
		return -1;
	}
}

BoardService.java

package com.cos.blog.service;

import com.cos.blog.domain.board.BoardDao;
import com.cos.blog.domain.board.dto.SaveReqDto;
import com.cos.blog.domain.user.UserDao;

public class BoardrService {

	private BoardDao boardDao;

	public BoardrService() {
		boardDao = new BoardDao();
	}

	public int 글쓰기(SaveReqDto dto) {
		return boardDao.save(dto);
	}
}

 

 

 

글 작성의 흐름 설명

1. 사용자(새 클라이언트)가 블로그를 방문합니다

2. 회원가입 로그인을 이미 마친 상태이고, 글쓰기 버튼을 누릅니다. 

3. cmd=saveForm 이기 때문에 Controller의 cmd.equals("saveForm") 기능으로 분기됩니다. (글쓰기 페이지로 이동)

4. 로그인을 했으므로 userId 가 기본적으로 있고, 제목(title) , 내용 (content)를 작성하고 글작성완료 버튼을 누릅니다.

이때 cmd=save 이기 때문에 Controller의 cmd.equals("save") 기능으로 분기됩니다.

 

jsp에서 <form> 안에 있는 양식을 submit으로 웹 서버에 전송하게 되면 input타입에 name속성이 붙여진 값들을 getParameter함수로 값을 받아서 변수로 사용할 수 있게 됩니다.

 

saveForm.jsp

<form action="/blog/board?cmd=save" method="POST">
		<input type="hidden" name="userId" value="${sessionScope.principal.id}" />
			<label for="title">Title:</label>
			<input type="text" class="form-control" placeholder="title" id="title" name="title">
		</div>

		<div class="form-group">
			<label for="content">Content:</label>
			<textarea id="summernote" class="form-control" rows="5" id="content" name="content"></textarea>
		</div>

		<button type="submit" class="btn btn-primary">글쓰기 등록</button>
	</form>

input 타입의 name속성 : UserId, title, content

전송하고 싶은 값인데, 보이지 않았으면 좋겠다 -> type="hidden" 을 사용합니다.

 

BoardController.java

else if(cmd.equals("save")) {
			int userId = Integer.parseInt(request.getParameter("userId"));
			String title = request.getParameter("title");
			String content = request.getParameter("content");

			SaveReqDto dto = new SaveReqDto();
			dto.setUserId(userId);
			dto.setTitle(title);
			dto.setContent(content);
			int result = boardService.글쓰기(dto);
			if(result == 1) { //정상
				response.sendRedirect("index.jsp");
			}else {
				Script.back(response, "글쓰기실패");
			}
		}

 

request.getParameter( [form 안에있는 input의 name 값] ) 을 변수에 넣어서

내맘대로 사용할 수 있습니다.

글쓰기만을 위한 SaveReqDto에 넣어서 new SaveReqDto를 만들어주고 

boardService의 글쓰기함수에서 바로 방금만든 dto를 인수로 받아 사용합니다.

 

글쓰기 함수는 save(SaveReqDto)를 호출합니다

BoardService.java

package com.cos.blog.service;

import com.cos.blog.domain.board.BoardDao;
import com.cos.blog.domain.board.dto.SaveReqDto;
import com.cos.blog.domain.user.UserDao;

public class BoardrService {

	private BoardDao boardDao;

	public BoardrService() {
		boardDao = new BoardDao();
	}

	public int 글쓰기(SaveReqDto dto) {
		return boardDao.save(dto);
	}
}

save함수를 보겠습니다

BoardDao.java

package com.cos.blog.domain.board;

import java.sql.Connection;
import java.sql.PreparedStatement;

import com.cos.blog.config.DB;
import com.cos.blog.domain.board.dto.SaveReqDto;
import com.cos.blog.domain.user.dto.JoinReqDto;

public class BoardDao {

	public int save(SaveReqDto dto) { // 회원가입
		String sql = "INSERT INTO board(userId, title, content, createDate) VALUES(?,?,?, now())";
		Connection conn = DB.getConnection();
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, dto.getUserId());
			pstmt.setString(2, dto.getTitle());
			pstmt.setString(3, dto.getContent());
			int result = pstmt.executeUpdate();
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		} finally { // 무조건 실행
			DB.close(conn, pstmt);
		}
		return -1;
	}
}

방금 만든 dto값으로 sql문을 만들어서 DB INSERT작업을 합니다.

INSERT의 결과값은 성공했으면 1 실패했다면 1 이외의 값을 return 하게 됩니다.

 

 

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

+ Recent posts