ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 0922. 댓글처리 Ajax, REST API
    Spring 2023. 9. 22. 17:51

     🌱서론

    자바스크립트는 멀티스레드를 지원하지 않기 때문에 비동기로만 할 수 있다.
     
    <요청 보내거나 받을 때 쓰는 인코딩>
     지금까지는 데이터를 form 태그를 사용한 1, 2번 방식으로 보냈다.


    <form enctype=" ">
    1. 이름 = 값&이름=값& ... => default 인코딩 : 제일 많이 씀
    2. header 띄우고 body ...=> multipart 인코딩 : 바이너리파일 
     
    이제 나오는 건 자바스크립트로 표현할 때 쓰는 Ajax 형식이다.

     "키" : 값,
     "키" : 값 ... }
    => application/json 인코딩 : Ajax

    (예시) .application/json 빼먹는 실수 많이 한다.


    📍Ajax

    Asynchronous Javascript and XML (비동기 자바스크립트와 XML)

    페이지의 이동(깜빡임) 없이 자바스크립트로 서버와 통신하는 기법이다.

    최근 데이터 표현 : xml < Json 

    *전송과 응답 시 데이터모드를 .application/json 인코딩으로 한다.

     

    브라우저에서 XMLHttpRequest 객체를 제공하지만 타이핑 양이 많아서 사용이 어렵고,

    이 객체를 사용할 수 있는 간소화된 라이브러리를 사용한다.

    <라이브러리 종류>
    - $.ajax : jquery 에서 제공하는 ajax 메서드, 예전에 주름잡음
    - ⭐ fetch() : js 기본함수로 제공, 요즘 많이 씀 => react나 순수 자바스크립트에서 많이 씀
    - axios : 최근에 jquery 안 쓰면 쓰는 또다른 후보 => 프론트에서 많이 씀
     


    📍REST API

    Representational State Transfer "상태를 표현"


    함수호출과 다를바 없으므로 일종의 라이브러리이다.

    • URI와 메서드 : 함수명
    • {bno} 관련게시글 : 변수

    기능적 매커니즘은 함수호출이고 다른 컴퓨터의 서버에서 함수호출이 제공된다는 차이점만 있다.
    => "RPC(Remote Procedure Call) 다른 컴퓨터의 함수를 호출하는 매커니즘"
     
    [장점]
    1. 클라이언트 파트가 브라우저일 필요없이 일반 어플리케이션도 웹서버의 자원을 사용할 수 있다.(자원=정보)
    2. 통신만 http로 하지 주고 받는 데이터에 html 처리를 할 필요가 없으니, 클라이언트 종류에 제한이 생기지 않는다.

    예시로 휴대폰을 사용할 때,
    JSON으로 데이터를 호출, 응답하고 있으니 (REST API는 아닐 수 있지만!)

    휴대폰은 브라우저가 아닌데 웹서버 정보 사용할 수 있는 이유이다.

    인터넷에서 웹서버는 자원을 관리한다.
    자원이라함은, 정보인데 게시글 1개나 여행지정보 1개 등을 예로 들 수 있고 이 정보를 식별하는 게 자원관리이다.
    이 정보는 해당 웹서버에 존재하는데 이를 URL(정확히는 URI)로 나타낼 수 있다.
    => URI로 정보를 식별 

    이전에 쓰던 URL 개념은 "페이지를 식별"하는 용도였다.
    REST API를 배울 때는 "정보를 식별"하자

    🖤즉, REST API URI 정보(자원)메서드(행위)로 대상과 어떤 일을 할지 표현하는 라이브러리이다.

    표현방식으론 일반적으로 JSON을 사용한다.


    ✨REST API의 메서드

     

    <예전>
    read : GET
    insert,update,delete : POST
     
    <현재>

    • read : GET
    • create : POST
    • modify : PUT
    • remove : DELETE

    * board 뒤에 식별번호={bno} : 그 글 한 개를 나타내겠다.

    REST API에서 자주 쓰는 5가지 메서드 표현

    - 1 -
    차이점은 board 뒤에 오는 url에서 나타난다.
     
    - 2 -
    추가된 정보 {bno} 부분은 key가 되고 이 key 에 대응하는 정보 하나로 해석한다.
    표현을 볼 때는 uri 와 메서드까지 다 봐야한다.
    수정과 삭제는 대상 지칭이 필요하니 key(={bno})가 들어가는 것이다.
    참고로 수정은 board의 해당 id가 게시글 수정을 하는 것이니 body와 함께 no를 컨트롤러에서 변수로 받음.
    나머지도 uri가 같을 땐, 메서드로 구분을 해야 한다.
     
    이제 서버는 해당 정보를 보고 할 일 한다.


    - 3 -
    GET(추출) 요청일 때는 전달할 게 없으니 요청이 끝이지만(=DELETE는 body 말고 대상만 필요)
    POST(생성)는 body의 내용이 전달되어야 한다. PUT(수정)도 마찬가지.
     
    - 4 -
    여지껏 어떤 페이지를 보고 싶을 때 Spring에서 파라미터를 받는 방식으로
    쿼리스트링인 @RequestParam(Get방식 통신)을 써서 ?bno=10 으로 했다면,


    rest 통신을 하려면 @PathVariable(RESTful방식 통신)을 사용하여 {bno}를 사용하면 된다.
    delete를 @PathVariable을 쓴 예로 표현식은 이렇게 된다.

    >> @DeleteMapping('/api/board/{bno}')

     

    - 5 -
    POST와 PUT에서 body구성은 Json으로 한다.
    요청과 응답 둘다 Json.

    (지금까지 응답은 Html로 받았다면 Ajax에서는 Json으로 받는다.)

     

    🌏 중간 참고하면 좋은 글

     

    https://velog.io/@dmchoi224/Rest-API-RequestParam-%EA%B3%BC-PathVariable

     

    Rest API , @RequestParam 과 @PathVariable

    REST API REST API 란 REST API 에서 REST는 Representational State Transfer 의 약자로 소프트웨어 프로그램 아키텍처의 한 형식. 즉, 자원을 이름 (자원의 표현) 으로 구분하여 해당 자원의 상태 (정보)를 주고 받

    velog.io


    🌱댓글처리 REST API

    다른 uri와 구분하기 위해 (웹브라우저가 아님을 표현) api를 앞에 붙인다.
    구분하지 않으면 보안 설정 시 까다로워진다.
     

    rest api

    관련게시글 번호 : 변수 파트 {bno} 
    댓글 id : Primary Key {no}
     
    view를 사용하지 않아서 html응답이 아니다.
    view를 통하지 않고 직접 응답하는 Json이다. (ex: avatar -> 이미지로 직접 응답함)
    직접 응답하려면 아바타처럼 responsebody를 직접 다 붙여줘야하는데 귀찮으니까 특수 컨트롤러를 만들자.
     
     

    JSON으로 응답하는 특수 컨트롤러
    @RestController

    처리결과를 뷰로 보내는 게 아닌 직접 응답하는 컨트롤러
    모든 메서드에 @ResponseBody 가 자동으로 추가되고 처리결과는 객체로 바로 리턴이 가능하다.
     
    ❗단, Gson(구글), Jackson같은 라이브러리가 등록되어있어야 한다.
    에러가 항상 200으로 나가서(=해당 객체를 json문자열로 변경해서 클라이언트에 응답) 처리에 대한 조작이 필요하다.
    * ResponseEntity<T>를 리턴하면 응답코드, 응답 헤더, 내용에 대한 직접 지정이 가능하다.
     
    ⭐POST, PUT 요청에서 Body를 Json 인코딩(application/json)해서 보냈으므로 이 json을 디코딩해서 @ModelAttribute에 보내야한다.
    ❗모델객체 앞에 @RequestBody를 추가해야 값이 들어간다. (안 붙이면 아무 값도 안 들어감)

    Jackson json dependency

    <dependency>
    	<groupId>com.fasterxml.jackson.core</groupId>
    	<artifactId>jackson-databind</artifactId>
    	<version>2.9.4</version>
    </dependency>


     


    📍서버 측 설계

     
    1. tbl_comment 댓글 테이블 생성

    drop table if exists tbl_comment;
    
    create table tbl_comment (
    no integer auto_increment primary key,
    bno integer not null,
    content varchar(2000) not null,
    writer varchar(50) not null,
    reg_date datetime default now(),
    update_date datetime default now(),
    constraint fk_comment_board foreign key(bno) references tbl_board(bno),
    constraint fk_comment_member foreign key(writer) 
    references tbl_member(username)
    );
    
    select * from tbl_comment;


    2. VO 객체 생성

     
    CommentVO

    package org.galapagos.domain;
    
    import java.util.Date;
    
    import lombok.Data;
    
    @Data
    public class CommentVO {
    private Long no;
    private Long bno;
    private String writer;
    private String content;
    private Date regDate;
    private Date updateDate;
    }

     

    3. 인터페이스 Mapping

    CommentMapper 인터페이스 생성
    RestAPI에 대응하도록

    • 목록보기(readAll) : 글번호(bno) 필요 - foreign key(Long bno)
    • 하나 얻기-보기(get) : no (댓글 id - pk) >> (Long no)
    • 생성(create) , 수정(update) : Body 전달 필요 (CommentVO vo)
    • 삭제(delete) : comment의 id 필요 (Long no) *댓글삭제니까 댓글 id : pk
    package org.galapagos.mapper;
    
    import java.util.List;
    
    import org.galapagos.domain.CommentVO;
    
    public interface CommentMapper {
    	
    	List<CommentVO> readAll(Long bno);
    	CommentVO get(Long no);
    	
    	void create(CommentVO vo);
    	void update(CommentVO vo);
    	void delete(Long no);
    
    }

    *다시 참고 REST API

     
     

    4. XML 

    CommentMapper.xml 생성
     
    insert하면 pk는 auto_increment 라서 코멘트의 값에 어떤 값이(CommentVO no xxx) 배정됐는지 모른다.
    이걸 확인하는 게 selectKey(VO객체의 keyproperty, keycolumn 확인)
    selectKey 설정 안 하면 무슨 값인지 모르니까 넣어줘야 한다.

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="org.galapagos.mapper.CommentMapper">
    
    	<select id="readAll" resultType="CommentVO">
    		select * from tbl_comment
    		where bno = #{bno} <!-- Foreign Key : 글번호 bno -->
    	</select>
    
    	<select id="get" resultType="CommentVO">
    		select * from tbl_comment
    		where no = #{no} <!-- 댓글 id - pk -->
    	</select>
    
    	<insert id="create">
    		insert into tbl_comment (bno, writer, content)
    		values(#{bno}, #{writer}, #{content})
    		<!-- insert로 인해 property랑 column에 auto_increment 된 값이 무엇인지 확인용 -->
    		<selectKey resultType="Long" keyProperty="no" keyColumn="no"
    			order="AFTER">
    			SELECT LAST_INSERT_ID()
    		</selectKey>
    	</insert>
    	
    	<update id="update">
    		update tbl_comment
    		set
    			content = #{content},
    			update_date = now()
    		where no = #{no}
    	</update>
    	
    	<delete id="delete">
    		delete from tbl_comment
    		where no = #{no}
    	</delete>
    	
    </mapper>

     
    서비스 안 만들고 컨트롤러로 바로 연결하기 
     

    5. Controller 설정

    CommentController 생성
     
    *댓글 조회이니 게시글번호 필요없으면 빼도 된다.
    29열 @Pathvariable Long bno << 
     
    🖤@메서드명Mapping("")로 할 경우 공통 url로 들어갈 부분을 @RequestMapping으로 설정한다.

    RESTfull 메서드명 + Mapping

    package org.galapagos.controller;
    
    import java.util.List;
    
    import org.galapagos.domain.CommentVO;
    import org.galapagos.mapper.CommentMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.DeleteMapping;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.PutMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/api/board/{bno}/comment") //("")로 할 경우 공통 url로 들어갈 부분
    public class CommentController {
    	@Autowired
    	CommentMapper mapper;
    
    	@GetMapping("")
    	public List<CommentVO> readComments(@PathVariable Long bno) { // 글번호(게시글 보여줌)
    		return mapper.readAll(bno); //경로변수에 있던 글번호
    	}
    
    	@GetMapping("/{no}") //자체적인 pathvariable List<>로 생성된 배열[] 뒤에 붙여라.
    	public CommentVO readComment(@PathVariable Long bno, @PathVariable Long no) { //글번호(게시글) + 댓글 id(달린 댓글들)
    		return mapper.get(no); //댓글 보는 거니 글번호 bno는 필요없으면 빼도 된다.
    	}
    
    	@PostMapping("")
    	public CommentVO create(@RequestBody CommentVO vo) { // body 전달 (생성할 댓글내용)
    		mapper.create(vo);
    		return mapper.get(vo.getNo()); //생성한 댓글 다시 꺼내서 리턴(댓글 id = no)
    	}
    
    	@PutMapping("/{no}")
    	public CommentVO update(@PathVariable Long no, @RequestBody CommentVO vo) { //댓글 id + body(수정할 댓글내용)
    		System.out.println("==> " + vo); //수정한 댓글의 내용 body
    		mapper.update(vo);
    		return mapper.get(vo.getNo()); //올바른 방법
    		//return vo; //그냥 받은 대로 바로 리턴
    	}
    
    	@DeleteMapping("/{no}")
    	public String delete(@PathVariable Long no) { //댓글 id
    		System.out.println("delete ==>" + no); // 댓글삭제 (댓글 id를 삭제했다고 알림)
    		mapper.delete(no);
    		return "OK";
    	}
    }

     
    get 요청으로 댓글 있는지만 확인 가능한 단계이니 해 보자
    >> localhost:8080/api/board/존재하는 게시글 번호 중 내가 아는 거/comment
    빈 배열의 댓글 등장

    댓글이 없으니 [] 라고 뜬다.


    ❗클라이언트 측 설계를 하기 전에 REST 테스트가 필요하다.

    PostMan은 가입이 번거로우니 크롬 확장 프로그램인 Talend API Tester를 설치하자.

     
    설치한 화면

    Talend API Tester

    댓글 조회

    아까 사용한 댓글 요청 url 복사해서 입력 후 [Send] (alt+enter 해도 send 된다.)

     

     
    응답 화면 

    메서드 고를 수 있다. (밑에 3개는 지금 안 씀)

     

    댓글 등록

     
    POST 메서드에서 우측 body에 json 문자열을 만들면 된다.
    ❗해당 게시글의 작성자가 아닌 다른 작성자로 등록해야 한다.
    (우리 설정이 본인글에는 대댓글만 되지 댓글은 안 되는 걸로 설정할 거라서;;)
    key는 항상 문자열 등록하고 >> "bno" : 36
     

    컨트롤러의 postmapping create가 작동할 예정

    댓글 없는 상태에서 create하면 selectKey no 배정이 처리될 예정.

    updateDate, regDate의 값은 안 넘겼으므로 default now가 들어가서 여전히 null이다.

    => null이면 create(vo)가 바로 안 넘어가고 no가 배정된다고 했다.
    => get이 필요하므로 DB에서 꺼내서 리턴해야 한다.
    그러면 >>return mapper.get(vo.getNo()); 에서는 값이 들어가 있다.

    send해 보면 csrf 토큰 때문에 403 에러 뜬다.
    REST API에 대해서는 csrf 토큰을 안 쓰겠다고 설정해 줘야 한다...

     
    SecurityConfig에서 csrf 필터 아래에 코드 추가
    ignoreingAntMatchers의 패턴에 맞으면 csrf 체크를 안 하겠다는 뜻 >>/api/**

    http.csrf().ignoringAntMatchers("/api/**");

    다시 send 누르면 찰나의 시간동안 [abort]가 카운트와 함께 노란색 버튼으로 지나간 후 다시 send로 돌아간다.
    abort는 중단버튼으로 찰나의 0.n초 안에 누르면 중단되나 보다.

    regDate와 updateDate에 값이 부여되었다.
     
    sql에서 확인 

     
    이제 GET 요청으로 읽어보자

    url 뒤에 슬래시하고 댓글 no 입력하면 해당 댓글만 나온다.
    >>http://localhost:8080/api/board/36/comment/1

    브라우저 주소창에 직접 입력


     

    댓글 수정

     
    ${content}와 ${no}를 꼭 넘겨야 한다.
    >>http://localhost:8080/api/board/36/comment/1
    PUT

    bno는 url에 제시했으니 no랑 content만

    다른 부분 다 비워지고 보낸 데이터만 담김

    updateDate 바뀜

     
    DB 한 번 읽어서 리턴하는 게 올바른 방법이다.

    결과는 같지만 아무튼 DB에서 한 번 읽어서 보낸다는 점이 다르다.

     

    댓글 삭제

     

    +) 없는 댓글을 삭제 요청했을 때
    404를 기대했는데 서버 내부에서 에러가 발생해서 500에러가 아니면 정상처리로 간주해서 별 에러 없다.
    에러로 취급하고 싶으면 @ResponseEntity<T> 를 써서 응답코드,헤더, 내용 직접 지정해서 대응 가능하다.
     
     
     


    📍클라이언트 측 설계

     
     
    Ajax
     

    * fetch() 함수
    매개변수 기본 2개를 받는다.
    첫 번째 인자 : URL (주로 REST url 사용)
    ex) http://localhost:8080/api/board/36/comment/6
    두 번째 인자 : 옵션 객체 => content type, body 등
     
    리턴 : Promise 타입 객체 (비동기 함수가 리턴하는 타입이다.)
     
    비동기 함수는 함수가 끝났을 때 처리할 로직이 담긴 callback함수를 필수로 가지고 있다.(click, ready event처리 등)
    그런데 이러면 콜백 안에 콜백 안에 콜백이 된다. 콜백지옥 => 해석불가
     
    콜백지옥의 개선안으로 나온 게 Promise 객체이다.
    커피 시키고 진동벨 받으면 울릴 때까지 다른 일 하는 것 : 비동기 (다른 일 못 하고 기다리면 동기)
    진동벨 역할 : promise 객체
     

    🖤promise 객체의 메서드 (fetch의 리턴값으로 호출된다.)
    .then((response) => console.log("response:", response))  작업 끝나면 실행하겠다
    .catch((error) => console.log("error:", error)); 실행하다가 예외가 발생하면 작동

     
    *function 키워드없이 괄호로만 하는 람다식(=화살표함수)
    괄호 안 : 매개변수 / 화살표 우측 : 함수 body
     
    함수가 한 줄 코드이면 중괄호{ }와 리턴이 생략되어있다.
    "function(response){ } return console.~이하생략" 의 축약형태가 위(🖤)의 함수식이다.
     
    [표준안]

    fetch(url).then((response) => response.json()).then((data) => console.log(data));

     
    [새 버전]
    비동기 매커니즘이지만 표기는 동기스타일로 지원해 주는 형식

    ✨async

    - await : 기다리겠다. "비동기"(빼먹으면 undefined 에러)
    - let res 에서 res : response를 말함 
    - json 도 비동기라 기다림
    - 예외는 catch가 처리
     
    모양은 동기지만 실제 진행은 주루루 진행. promise의 then 절로 다 들어가버림.

    async function rest_get(url) {
    try {
    let res = await fetch(url);
    return await res.json();
    } catch(e) {
    console.log(e);
    } 
    }

    fetch와 async 비교

    await
    1. Promise를 리턴하는 비동기 함수 앞에 붙일 수 있고
    2. async 키워드가 붙은 함수에서만 쓸 수 있다.
    3. await를 쓸지 안 쓸지는 선택사항이다.(빼도 문법 에러 아님)
    4. await를 생략하면 실제 리턴값이 아니라 Promise 객체로 작업한 코드가 되어 의도치 않은 결과가 나온다.
    (에러 날 수도, 이상한 데이터 들어가서 에러 안 날 수도)

    <적용>
     
    board의 get.jsp
     
    1. 일단은 화살표 안 쓰고 익명함수 function을 사용해서 해 보자

     	fetch('/api/board/36/comment') //경로만 가져오기, 두 번째 매개변수 없으니 GET요청
     		.then(function (res) {//익명함수 function
    			console.log(res);
    	}); //callback 1개 등록

     
    게시글 하나 선택해서 개발자 모드로 콘솔을 찍어보면..

    콘솔 찍었던 부분을 return res.json(); 으로 바꾸면 promise 객체가 리턴 된다.

     	fetch('/api/board/36/comment') //경로만 가져오기, 두 번째 매개변수 없으니 GET요청
     		.then(function(res) {//익명함수 function, 리턴값의 메서드 then을 호출 -> response 객체
    			return res.json(); //Json 문자열을 실제 객체(promise)로 바꿔라 "역직렬화"
    		}) //callback 1개 등록
    		.then(function(data) {
    			console.log(data); //data라는 댓글 배열
    		});
    		// res=>console.log(res) 와 같음
    });

     
    비동기 함수를 동기처럼 나타낸 최종 

     	fetch('/api/board/36/comment') //경로만 가져오기, 두 번째 매개변수 없으니 GET요청
     		.then(function(res) {//익명함수 function, 리턴값의 메서드 then을 호출 -> response 객체
    			console.log('응답 수신');
     			return res.json(); //Json 문자열을 실제 객체(promise)로 바꿔라 "역직렬화"
    		}) //callback 1개 등록
    		.then(function(data) {
    			console.log('Json 데이터 변환완료');
    			console.log(data); //data라는 댓글 배열
    		});
    		console.log('fetch 호출 완료');
    		// res=>console.log(res) 와 같음
    });

    [순서]
    비동기이므로 출력순서가 코드 순서 그대로 나오지 않는다.
     
    (1) 하드코딩한 경로로 fetch 호출(GET 요청) 💁"커피 주문할게요"
    ➡️ (2) response 반응 🤹‍♀️"네, 기다리세요"
    ➡️ (3) data 정보 보냄 💁  ☕🤹‍♀️ "커피 나왔습니다"

    callback 호출 순서

    *모양 

    매개변수 => 함수 본체(body)

     
    자 이제 고치자~! (길다)

    원래 형태
    화살표 추가했으니 res 감싼 괄호 제거
    한 줄이니 대괄호 제거
    한 줄이니 리턴 제거
    아랫줄 then도 똑같이 정리.


    이제 콜백지옥으로부터 자유로워지게 async 형식으로 만들자

     

    ⭐ready의 function앞에 async 추가
    let res = ⭐await 앞에 붙였으니 .then 정리

     

    let data = await 추가 했으니 나머지 .then 정리

     

    최종 형태

     

    🤹‍♀️더 정리할 수 있다.

     
    리소스의 js 폴더에 rest.js 로 자바스크립트 코드 정리하기
    예외도 이 코드가 처리해 줄 것이다!

    rest.js 

    async function rest_get(url) {
    	try {
    		let res = await fetch(url);
    		return await res.json();
    	} catch(e) {
    	console.log(e);
    	} 
    }
    /*이클립스의 자바스크립트 분석기가 await가 뭔지 몰라서 나는 에러이므로 브라우저에서는 괜찮다.*/

     
    get.jsp에서 스크립트 태그로 정리

    <script src="/resources/js/rest.js"></script>

    기존 fetch 함수 지우고 상수로 url 정의한 rest_get 요청 코드 삽입 

    const url = '/api/board/36/comment';
     	let data = await rest_get(url);
     	console.log(data);

    진짜 최종

    출력! 

    상세보기에서 개발자모드로 콘솔 찍은 거 확인

     
    url 뒤에 댓글id 입력하면 역시 단일 데이터 리턴


    ⭐하드코딩 변수처리하기

     
    - bno el로 얻기(board에 제시되어 있으니까 사용 가능)

    	//철기
    	//bno 하드코딩 변수 처리
    	const bno = ${board.bno}
    	
     	const url = '/api/board/' + bno + '/comment';
     	console.log(url);
     	let data = await rest_get(url);
     	console.log(data);

    출력화면

    진짜진짜 최종 get.jsp
    ❗괄호처리가 중간에 마무리되니 주의
    ❗❗주석 너무 많음 주의

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
    
    <%@include file="../layouts/header.jsp"%>
    
    <script src="/resources/js/rest.js"></script>
    
    <script>
    $(document).ready(async function() {
    
    	$('.remove').click(function(){
    		if(!confirm('정말 삭제할까요?')) return;		
    		document.forms.removeForm.submit();
    	});	
     
    	//철기
    	//bno 하드코딩 변수 처리
    	const bno = ${board.bno}
    	
     	const url = '/api/board/' + bno + '/comment';
     	console.log(url);
     	let data = await rest_get(url);
     	console.log(data);
    	
     	//청동기
        //rest.js로 뺀 후 스크립트 처리
     	/*const url = '/api/board/36/comment/1';
     	let data = await rest_get(url); 
     	console.log(data); */
     	
     	
     	//신석기
        // async로 가기 위한 준비
     /* let res = await fetch('/api/board/36/comment');  //GET 요청
    	let data = await res.json());
    	console.log(data); */
    		
    	});
    	
    	//구석기
        //callback .then 사용
     /* fetch('/api/board/36/comment') //경로만 가져오기, 두 번째 매개변수 없으니 GET요청
     		.then((res) {//익명함수 function, 리턴값의 메서드 then을 호출 -> response 객체
    		console.log('응답 수신');
            // 위에 두 줄이 res=>console.log(res) 와 같음
     		return res.json(); //Json 문자열을 실제 객체(promise)로 바꿔라 "역직렬화"
    		}) //callback 1개 등록
    		.then(function(data) {
    		console.log('Json 데이터 변환완료');
    		console.log(data); //data라는 댓글 배열
    		});
    		console.log('fetch 호출 완료');
    		});
    		 */ 
    
    </script>
    
    <h1 class="page-header"><i class="far fa-file-alt"></i> ${board.title}</h1>
    	
    <div class="d-flex justify-content-between">
    	<div><i class="fas fa-user"></i> ${board.writer}</div>
    	<div>
    		<i class="fas fa-clock"></i> 
    		<fmt:formatDate pattern="yyyy-MM-dd" value="${board.regDate}"/>
    	</div>
    </div>
    	
    <hr>
    	
    <div>
    	${board.content}
    </div>
    
    <div class="mt-4">
    	<a href="${cri.getLink('list')}" class="btn btn-primary list">
    		<i class="fas fa-list"></i> 목록</a>
    		
     <c:if test="${username == board.writer}">
    	<a href="${cri.getLinkWithBno('modify', board.bno) }" class="btn btn-primary modify">
    		<i class="far fa-edit"></i> 수정</a>
    	<a href="#" class="btn btn-danger remove">
    		<i class="fas fa-trash-alt"></i> 삭제</a>
    </c:if>
    </div>
    
    <%-- 
    <form id="listForm" action="/board/list" method="get" >
    	<input type="hidden" name="pageNum" value="${cri.pageNum}"/>
    	<input type="hidden" name="amount" value="${cri.amount}"/>
    	<input type="hidden" name="type" value="${cri.type}"/>
    	<input type="hidden" name="keyword" value="${cri.keyword}"/>
    </form>
    
    <form id="modifyForm" action="/board/modify" method="get" >
    	<input type="hidden" id="bno" name="bno" value="${board.bno}"/>
    	<input type="hidden" name="pageNum" value="${cri.pageNum}"/>
    	<input type="hidden" name="amount" value="${cri.amount}"/>
    	<input type="hidden" name="type" value="${cri.type}"/>
    	<input type="hidden" name="keyword" value="${cri.keyword}"/>
    </form>
     --%>
     
    <form action="remove" method="post" name="removeForm">
    <!-- csrf 토큰 안 넣으면 404 에러 -->
    <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token }" />
    	<input type="hidden" name="bno" value="${board.bno}"/>
    	<input type="hidden" name="pageNum" value="${cri.pageNum}"/>
    	<input type="hidden" name="amount" value="${cri.amount}"/>
    	<input type="hidden" name="type" value="${cri.type}"/>
    	<input type="hidden" name="keyword" value="${cri.keyword}"/>
    </form>
    
    <%@include file="../layouts/footer.jsp"%>

     

Designed by Tistory.