11/27
로그인 처리하기 bitcamp-java-project-server
bitcamp-web-project ex04 Servlet01
서블릿컨텍스트는 웹어플리케이션 당 1개임!!! 즉 자바프로젝트의 서블릿 컨텍스트는 1개라는 뜻
httpsession은 클라이언트 당 1개
webapps안에 있는 폴더 당 서블릿컨텍스트가 1개
post요청은 무조건 UTF-8인코딩 해줘야한다. get방식은 상관없다.
session에 각 클라이언트 고유 정보를 담는다.
클라이언트가 들어올때 세션아이디 발급, 그걸로 client를 식별,
테스트하려고 가짜로 만든 것들을 꼽는 것 -> mokup
package com.eomcs.pms.web;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import javax.servlet.ServletContext;
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.eomcs.pms.domain.Member;
import com.eomcs.pms.service.MemberService;
@WebServlet("/auth/login")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1;
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 클라이언트에게 전용 보관소(세션)를 준비한다.
HttpSession session = request.getSession();
// 클라이언트로 데이터를 출력할 때 사용할 스트림 준비
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head><title>로그인</title></head>");
out.println("<body>");
try {
out.println("<h1><b>로그인</h1>");
if (session.getAttribute("loginUser") != null) {
out.println("<p>로그인 된 상태입니다!</p>");
} else {
// 클라이언트가 보낸 데이터를 꺼낸다.
String email = request.getParameter("email");
String password = request.getParameter("password");
// 서블릿이 로그인 작업에 사용할 도구를 준비한다.
ServletContext ctx = request.getServletContext();
MemberService memberService = (MemberService) ctx.getAttribute("memberService");
Member member = memberService.get(email, password);
if (member == null) {
out.println("<p>사용자 정보가 맞지 않습니다.</p>");
} else {
// 로그인이 성공했으면 회원 정보를
// 각 클라이언트의 전용 보관소인 session에 저장한다.
session.setAttribute("loginUser", member);
out.printf("<p>%s 님 반갑습니다.</p>\n", member.getName());
}
}
} catch (Exception e) {
out.println("<h2>작업 처리 중 오류 발생! - %s</h2>");
out.printf("<pre>%s</pre>\n", e.getMessage());
StringWriter errout = new StringWriter();
e.printStackTrace(new PrintWriter(errout));
out.println("<h3>상세 오류 내용</h3>");
out.printf("<pre>%s</pre>\n", errout.toString());
}
out.println("</body>");
out.println("</html>");
}
}
GET 요청 HTTP 프로토콜 예)
URL : Uniform Resource Locator(예 : %EA%B0%80)
- 7bit 장비를 정상적으로 통과하기 위함.
- 웹 브라우저가 알아서 해주는 인코딩
URI : Uniform Resource Identifier
웹 개발자라면, 특히 프론트엔드라면 자바스크립트에서 데이터를 보낼때 웹브라우저에서 데이터를 보내는게 아니라서 개발자가 직접 URL인코딩을 해서 서버로 보내야 한다는 사실을 반드시 기억하자! 특히 Ajax사용할 때.
URI (Uniform Resource Identifier)
=> 웹 자원의 위치를 가리키는 식별자
=> 종류
URL(Uniform Resource Locator)
scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]
예) http://localhost:8080/ex04/s1?name=홍길동&age=20
URN(Uniform Resource Name)
::= "urn:" ":"
예) urn:lex:eu:council:directive:2010-03-09;2010-19-UE
=> GET 요청은 데이터를 request-URI에 붙여서 보낸다.
=> request-URI 예)
/java-web/ex04/s1?name=%ED%99%8D%EA%B8%B8%EB%8F%99&age=20
서블릿 URL : /java-web/ex04/s1
데이터(Query String) : name=%ED%99%8D%EA%B8%B8%EB%8F%99&age=20
=> 데이터 형식
이름=값&이름=값&이름=값
=> URL 인코딩
- 데이터를 보낼 때 7bit 네트워크 장비를 거치면 8비트 데이터가 깨진다.
- 이를 방지하고자 보내는 데이터를 7비트로 변환한다.
- 어떻게? 원래 코드 값을 아스키(ASCII) 문자 코드로 변환한다.
- ASCII 코드는 7비트이기 때문에 데이터를 주고 받을 때 깨지지 않을 것이다.
- URL 인코딩이란? 문자 코드의 값을 ASCII 코드화시키는 것이다.
- 예) "ABC가각"을 전송한다고 가정하자
"ABC가각"의 문자 코드(UTF-8) 값 : 414243EAB080EAB081
7비트 장비를 통과:
41 => 0100 0001 => [7비트 장비] => 100 0001 => [8비트로 복원] => 0100 0001
42 => 0100 0010 => [7비트 장비] => 100 0001 => [8비트로 복원] => 0100 0010
43 => 0100 0011 => [7비트 장비] => 100 0011 => [8비트로 복원] => 0100 0011
EA => 1110 1010 => [7비트 장비] => 110 1010 => [8비트로 복원] => 0110 1010
ASCII 문자코드로 변환 :
=> 코드 값이 이미 ASCII 라면 그대로
41 ==> 41
42 ==> 42
=> 코드 값이 ASCII 가 아니라면 각 4비트 값을 아스키 문자라 간주하고 코드로 변환한다.
E ==> 'E' ==> 45
A ==> 'A' ==> 41
이렇게 변경한 후, URL 인코딩 값임을 표시하기 위해 앞에 '%' 코드를 붙인다.
EA ==> 25 45 41 ==> 사람이 보는 문자로 표현하면 ==> %EA ==>
%EA 문자를 받은 쪽에서는 원래의 값을 변환(URL 디코딩)한다.
%EA(3바이트) ==> 1110 1010(1바이트)
## Post요청 Http 프로토콜 예)
=> POST 요청은 데이터를 message-body에 붙여서 보낸다.
=> 데이터 형식과 URL 인코딩은 GET 요청과 같다.
=> 예)
POST /java-web/ex04/s2 HTTP/1.1 Host: localhost:8080 Connection: keep-alive Content-Length: 33
Pragma: no-cache Cache-Control: no-cache Origin: http://localhost:8080 Upgrade-Insecure-Requests:
1 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Macintosh; Intel Mac
OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36 Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng, Referer:
http://localhost:8080/java-web/ex04/test02.html Accept-Encoding: gzip, deflate, br
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,la;q=0.6
빈 줄
name=ABC%EA%B0%80%EA%B0%81&age=20
## Get vs Post
GET 요청 vs POST 요청
1) 전송 데이터 용량
=> GET
- 대부분의 웹서버가 request-line과 헤더의 크기를 8KB로 제한하고 있다.
- 따라서 긴 게시글과 같은 큰 용량의 데이터를 GET 방식으로 전송할 수 없다.
=> POST
- HTTP 요청 헤더 다음에 message-body 부분에 데이터를 두기 때문에
용량의 제한 없이 웹 서버에 전송할 수 있다.
- 즉 웹 서버가 제한하지 않는 한 전송 데이터의 크기에 제한이 없다.
- 웹 서버가 제한한다?
특정 사이트에서는 게시글의 크기나 첨부파일의 크기에 제한을 둔다.
=> 용도
게시글 조회나 검색어 입력 같은 간단한 데이터 전송에는 GET 요청으로 보내고
게시글 등록이나 첨부파일 같은 큰 데이터 전송에는 POST 요청으로 보낸다.
2) 바이너리 데이터 전송
=> GET
- request-URI가 텍스트로 되어 있다.
따라서 바이너리 데이터를 request-URI에 붙여서 전송할 수 없다.
- 그럼에도 꼭 GET 요청으로 바이너리 데이터를 보내고자 한다면?
바이너리 데이터를 텍스트로 변환하면 된다.
예를 들어 바이너리 데이터를 Base64로 인코딩하여 텍스트를 만든 후에
GET 요청 방식대로 이름=값 으로 보내면 된다.
- 그래도 결국 용량 제한 때문에 바이너리 데이터를 GET 요청으로 전송하는 것은 바람직하지 않다.
=> POST
- 이 방식에서도 이름=값 형태로는 바이너리 값을 전송할 수 없다.
- multipart 형식을 사용하면 바이너리 데이터를 보낼 수 있다.
- 보통 파일 업로드를 구현할 때 이 multipart 전송 방식으로 사용한다.
3) 보안
=> GET
- URL에 전송 데이터가 포함되어 있기 때문에
사용자 아이디나 암호 같은 데이터를 GET 방식으로 전송하는 것은 위험하다.
- 웹 브라우저는 주소 창에 입력한 값을 내부 캐시에 보관해 두기 때문이다.
- 그러나 게시물 번호 같은 데이터는 URL에 포함되어야 한다.
그래야 다른 사람에게 URL과 함께 데이터를 보낼 수 있다.
=> POST
- mesage-body 부분에 데이터가 있기 때문에
웹 브라우저는 캐시에 보관하지 않는다.
- 또한 주소 창에도 보이지 않는다.
- 사용자 암호 같은 데이터를 전송할 때는 특히 이 방식으로 보내는 것이 바람직 하다.
즉 보내는 데이터를 웹 브라우저의 캐시 메모리에 남기고 싶지 않을 때는 POST 방식을 사용한다.
- 꺼꾸로 특정 페이지를 조회하는 URL일 경우 POST 방식을 사용하면
URL에 조회하려는 정보의 번호나 키를 포함할 수 없기 때문에
이런 상황에서는 POST 방식이 적절하지 않다.
오히려 GET 방식이 적합하다.
getbootstrap.com
브라우저가 보내는건 딱 두가지, get아니면 post밖에 없다. 그래서 method폼엔 delete을 작성할 수 없다.
즉 delete은 get이나 post나 뭘 써도 상관없다. 하지만 실무에서는 대부분 get방식을 쓴다.
무한스크롤 원리 : 스크롤을 내린다 -> 내릴때 자바스크립트로 서버에 그 다음 화면을 요청한다 : 그 다음 화면을 받아온다. 자동으로 되는게 아니라 다시 요청하는거임!!
요청하지 않으면 서버는 응답하지 않는다. 지가 알아서 자동으로 하는게 아니란말임!
mapper파일 절대 자동으로 바뀌게 하지 말것!! 보안상 그래서는 안돼ㅑㅁ!!!!\
## 이미지 업로드하기
이미지는 multipart형식으로 보내야한다.
현재의 서블릿은 multipart형식이 아니기 때문에 이걸 처리할 수 있도록 선언해줘야한다.
`@MultipartConfig(maxFileSize = 1024 * 1024 * 10)`
애노테이션을 붙여준다.
업로드 배포폴더에 이미지가 올라간다. 프로젝트 폴더에 올라가는게 아니다! 그럼 왜 만드냐? 만들어둬야 배포폴더에 파일이 업로드 될 것 아니냐고!!!
지금 방법의 단점 : 똑같은 이름의 사진 파일이 올라올 경우 그걸 덮어써버린다.
- 방법??? DB에 저장할 때 임의의 랜덤 아이디를 발급해서 그 Id를 파일명으로 쓴다.
```java
// <input type="file"...>입력 값 꺼내기
Part photoPart = request.getPart("photo");
// 회원 사진을 저장할 위치를 알아낸다
// => 컨텍스트루트/upload/파일
// => 파일을 저장할 때 사용할 파일명을 준비한다.
String filename = UUID.randomUUID().toString();
String saveFilePath = ctx.getRealPath("/upload/" + filename);
// 해당 위치에 업로드된 사진 파일을 저장한다.
photoPart.write(saveFilePath);
// DB에 사진 파일 이름을 저장하기 위해 객체에 보관한다.
member.setPhoto(filename);
```