Skip to content

Commit

Permalink
[최주형] 11일차 - 4단계 구현 (#103)
Browse files Browse the repository at this point in the history
* FEAT : POST request 구현

* REFACTOR : builder 패턴으로 변경
HttpResponse 객체 생성자에 null 값 전달 하던 것 builder 패턴으로 변경

* REFACTOR : 쓰지 않는 함수 삭제

* DOCS : README.md 학습 내용 추가
HTTP POST 방식, JAVA builder 패턴 공부

* FORM : 주석 삭제

* FEAT : 로그인 기능 구현
LogInService 클래스 추가

* FEAT : 로그인 기능 구현 step2
로그인 성공 실패 나누어 놓음 UserController에서 LogInService를 활용해 boolean으로 리턴 받아놓음

* FEAT : null request 예외처리
  • Loading branch information
jhchoi57 authored Jan 17, 2023
1 parent eb24221 commit d40aa31
Show file tree
Hide file tree
Showing 16 changed files with 194 additions and 46 deletions.
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,29 @@ Java Web Application Server 2022
assertThat(result).isEqualTo("예상되는 결과");
}
```


-------------
+ Day 11
+ HTTP POST 방식
+ POST 방식은 데이터 전송을 기반으로 한 요청 메서드
+ GET 방식은 URL에 데이터를 붙여서 보내지만 POST 방식은 BODY에 데이터를 넣어 보냄
+ 그래서 POST 방식에는 Content-Type 헤더 필드가 있음
+ ex) application/x-www-form-urlencoded, text/plain, multipart/form-data
+ POST Request 예시)
```
POST /user/create HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 59
Content-Type: application/x-www-form-urlencoded
Accept: */*
userId=javajigi&password=password&name=%EB%B0%95%EC%9E%AC%EC%84%B1&email=javajigi%40slipp.net
```
+ JAVA 생성 패턴 [Builder 패턴]
+ 많은 Optional한 멤버 변수(파라미터)나 지속성 없는 상태 값들 처리 문제 해결
+ 구현 방법
+ 빌더 클래스를 Static Nested Class로 생성 - 관례적으로 클래스 이름 + Builder로 명명
+ 생성자는 public, 파라미터는 필수 값들
+ Optional한 값들은 속성마다 메소드로 제공, 리턴 값이 빌더 객체 자신이어야 함
+ 마지막으로 빌더 클래스 내에 build()메소드를 정의하여 최종 생성된 결과물 리턴,
builder를 통해서만 객체 생성을 하므로 생성 대상이 되는 클래스의 생성자는 private
7 changes: 5 additions & 2 deletions src/main/java/controller/StaticFileController.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ public HttpResponse makeResponse(HttpRequest httpRequest) throws IOException {
// TODO 여러 HttpStatus에 대한 처리 필요
// 일단은 200 OK 고정 박아놓음
// 만들어진 body로 응답 객체를 만들어서 리턴
return new HttpResponse(new HttpStatusLine(HttpStatus.OK, httpRequest.getHttpVersion()),
responseBody, contentType);
return new HttpResponse.HttpResponseBuilder()
.setHttpStatusLine(new HttpStatusLine(HttpStatus.OK, httpRequest.getHttpVersion()))
.setBody(responseBody)
.setContentType(contentType)
.build();
}
}
34 changes: 27 additions & 7 deletions src/main/java/controller/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import http.request.HttpRequest;
import http.response.HttpResponse;
import http.response.HttpStatusLine;
import model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import service.LogInService;
import service.SignUpService;
import util.HttpResponseUtils;

Expand All @@ -20,23 +22,41 @@ public HttpResponse makeResponse(HttpRequest httpRequest) throws IOException {
// Uri 받아옵시다
String uri = httpRequest.getUri();

// TODO 할 일 enum 으로 구현 가능 할듯

// 회원가입일 때
if (isSignUpService(uri)) {
// user 정보 받아서 데이터베이스에 입력
Database.addUser(SignUpService.makeUserInfo(uri));
Database.addUser(SignUpService.makeUserByBody(httpRequest.getBody()));
// 302 응답이라 location만 필요하기 때문에 body랑 contentType는 없음!
return new HttpResponse(new HttpStatusLine(HttpStatus.FOUND, httpRequest.getHttpVersion()), null, null);
return new HttpResponse.HttpResponseBuilder()
.setHttpStatusLine(new HttpStatusLine(HttpStatus.FOUND, httpRequest.getHttpVersion()))
.build();
}

// 로그인일 때
if(isLoginService(uri)){
// 성공
if(LogInService.isLoginSuccess(httpRequest.getBody())){
// index.html로 이동
// HTTP 헤더의 쿠키 값을 SID = 세션 ID로 응답
// 세션 ID는 적당한 크기의 무작위 숫자 또는 문자열
// 서버는 세션 아이디에 해당하는 User 정보에 접근 가능해야 한다.
}

// 실패
// /user/login_failed.html로 이동

}

//TODO 임시 코드 - return 예외처리 해야됨
byte[] responseBody = HttpResponseUtils.makeBody(httpRequest.getUri(), null);
return new HttpResponse(new HttpStatusLine(HttpStatus.OK, httpRequest.getHttpVersion()), responseBody, null);
return null;
}

public boolean isSignUpService(String uri){
return uri.startsWith("/user/create");
return uri.equals("/user/create");
}

public boolean isLoginService(String uri){
return uri.equals("/user/login");
}

}
3 changes: 3 additions & 0 deletions src/main/java/http/HttpHeader.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,7 @@ public String getAccept() {
}


public String getContentLength() {
return headers.get("Content-Length");
}
}
5 changes: 0 additions & 5 deletions src/main/java/http/HttpUri.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,4 @@ public String getUri() {
public boolean isStaticUri() {
return uri.contains(".");
}

public boolean isDynamicUri() {
return uri.contains("?");
}

}
7 changes: 7 additions & 0 deletions src/main/java/http/exception/NullHttpRequestException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package http.exception;

public class NullHttpRequestException extends RuntimeException{
public NullHttpRequestException(String message){
super(message);
}
}
19 changes: 13 additions & 6 deletions src/main/java/http/request/HttpRequest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package http.request;

import http.HttpHeader;
import http.exception.NullHttpRequestException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import util.HttpRequestUtils;
Expand All @@ -12,12 +13,14 @@ public class HttpRequest {
private static final Logger logger = LoggerFactory.getLogger(HttpRequest.class);
private HttpRequestLine httpRequestLine;
private HttpHeader httpHeader;
private String body;

public HttpRequest(BufferedReader br) throws IOException {
String line = br.readLine();
if (line == null) return;
if (line == null) throw new NullHttpRequestException("빈 http request !!================");
this.httpRequestLine = HttpRequestUtils.readRequestLine(line);
this.httpHeader = HttpRequestUtils.readHeaders(br);
if(httpRequestLine.getHttpMethod().equals("POST")) this.body = HttpRequestUtils.readBody(br, httpHeader);
}

public String getUri() {
Expand All @@ -28,17 +31,21 @@ public boolean wantStatic() {
return httpRequestLine.getHttpUri().isStaticUri();
}

public boolean wantDynamic() {
return httpRequestLine.getHttpUri().isDynamicUri();
}

public String getContentType() {
//logger.debug("Accept : {}", httpHeader.getAccept());
logger.debug("Accept : {}", httpHeader.getAccept());
return httpHeader.getAccept().split(",")[0];
}

public String getHttpVersion() {
return httpRequestLine.getHttpVersion();
}

public boolean isPost() {
logger.debug("HTTP method : {}", httpRequestLine.getHttpMethod());
return this.httpRequestLine.getHttpMethod().equals("POST");
}

public String getBody(){
return this.body;
}
}
2 changes: 2 additions & 0 deletions src/main/java/http/request/HttpRequestLine.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ public HttpUri getHttpUri() {
public String getHttpVersion() {
return version;
}

public String getHttpMethod(){ return method; }
}
57 changes: 46 additions & 11 deletions src/main/java/http/response/HttpResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,63 @@ public class HttpResponse {

private String contentType;

private HttpResponse(HttpResponseBuilder httpResponseBuilder){
this.statusLine = httpResponseBuilder.httpStatusLine;
this.body = httpResponseBuilder.body;
this.contentType = httpResponseBuilder.contentType;

public HttpResponse(HttpStatusLine statusLine, byte[] body, String contentType) {
this.statusLine = statusLine;
this.body = body;
this.contentType = contentType;
this.header = makeHeader();
}

private HttpHeader makeHeader() {
if (statusLine.checkStatus(HttpStatus.OK)) return makeResponse200Header();
if (statusLine.checkStatus(HttpStatus.FOUND)) return makeResponse302Header();

// 200 302 응답 둘다 아니면?
return null;
}

public static class HttpResponseBuilder{
private HttpStatusLine httpStatusLine;
private byte[] body;
private String contentType;

public HttpResponseBuilder(){}

public HttpResponseBuilder setHttpStatusLine(HttpStatusLine httpStatusLine){
this.httpStatusLine = httpStatusLine;
return this;
}

public HttpResponseBuilder setBody(byte[] body){
this.body = body;
return this;
}

public HttpResponseBuilder setContentType(String contentType){
this.contentType = contentType;
return this;
}

public HttpResponse build(){
return new HttpResponse(this);
}

// Status에 따른 Header 생성
if (statusLine.checkStatus(HttpStatus.OK)) makeResponse200Header();
if (statusLine.checkStatus(HttpStatus.FOUND)) makeResponse302Header();
}

private void makeResponse200Header() {
private HttpHeader makeResponse200Header() {
List<String> headerLines = new ArrayList<>();
logger.debug("contentType: {}", contentType);
headerLines.add("Content-Type: " + contentType + ";charset=utf-8" + System.lineSeparator());
headerLines.add("Content-Length: " + body.length + System.lineSeparator());
this.header = new HttpHeader(headerLines);
return new HttpHeader(headerLines);
}

private void makeResponse302Header() {
private HttpHeader makeResponse302Header() {
List<String> headerLines = new ArrayList<>();
// TODO 회원 가입에 대한 리다이렉트로 첫 페이지 띄워주게 하드 코딩 했는데 나중에 확장 해야 할듯
headerLines.add("Location: " + "/index.html" + System.lineSeparator());
this.header = new HttpHeader(headerLines);
return new HttpHeader(headerLines);
}

public void send(DataOutputStream dos) throws IOException {
Expand All @@ -51,6 +84,8 @@ public void send(DataOutputStream dos) throws IOException {

private void responseHeader(DataOutputStream dos) {
try {
logger.debug("send : {}", statusLine.toStringForResponse());
logger.debug("send : {}", header.toString());
dos.writeBytes(statusLine.toStringForResponse());
dos.writeBytes(header.toString());
} catch (IOException e) {
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/service/LogInService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package service;

import db.Database;
import model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import util.HttpRequestUtils;

import java.util.Map;

public class LogInService {
private static final Logger logger = LoggerFactory.getLogger(SignUpService.class);

public static boolean isLoginSuccess(String body) {
Map<String, String> params = HttpRequestUtils.parseQueryString(body);

User tryLoginUser = Database.findUserById(params.get("userId"));
logger.debug("User : {}", tryLoginUser);

// id가 없을 때
if(tryLoginUser == null) {
logger.debug("User not Found !!");
return false;
}

// 비밀번호가 맞아서 로그인 성공
if(tryLoginUser.getPassword().equals(params.get("password"))){
logger.debug("Login success !!");
return true;
}

// 비밀번호 틀림
logger.debug("Login failed !!");
return false;

}
}
12 changes: 3 additions & 9 deletions src/main/java/service/SignUpService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@
import java.util.Map;
import java.util.regex.Pattern;

public class SignUpService {
public class SignUpService{
private static final Logger logger = LoggerFactory.getLogger(SignUpService.class);
public static User makeUserInfo(String uri) {
// 회원 가입 정보들을 Map에 담는 과정
// uri에서 ? 문자 뒤에 있는 query string을 분리 > query string을 & 문자 스플릿 해서 map 자료구조에 넣어줌
Map<String, String> params = HttpRequestUtils.parseQueryString(getQueryStringInUri(uri));
public static User makeUserByBody(String body) {
Map<String, String> params = HttpRequestUtils.parseQueryString(body);

User user = new User(
params.get("userId"),
Expand All @@ -26,8 +24,4 @@ public static User makeUserInfo(String uri) {

return user;
}

public static String getQueryStringInUri(String uri) {
return uri.split(Pattern.quote("?"))[1];
}
}
11 changes: 11 additions & 0 deletions src/main/java/util/HttpRequestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,15 @@ public static String takeValueRequestParam(String[] requestParam){
}


public static String readBody(BufferedReader br, HttpHeader header) throws IOException {
String requestBody = readData(br, Integer.parseInt(header.getContentLength()));
logger.debug("Request Body : {}", requestBody);
return requestBody;
}

public static String readData(BufferedReader br, int contentLength) throws IOException{
char[] body = new char[contentLength];
br.read(body, 0, contentLength);
return String.copyValueOf(body);
}
}
8 changes: 6 additions & 2 deletions src/main/java/util/HttpResponseUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ public static String makeFilePath(String contentType) {
return basePath + staticFilePath;
}

public static byte[] makeBody(String httpUri, String filePath) throws IOException {
public static byte[] makeBody(String httpUri, String filePath) {
logger.debug("filePath get : {}", filePath + httpUri);
return Files.readAllBytes(new File(filePath + httpUri).toPath());
try {
return Files.readAllBytes(new File(filePath + httpUri).toPath());
} catch (IOException e) {
return " 파일을 찾지 못함 !".getBytes();
}
}

}
3 changes: 2 additions & 1 deletion src/main/java/webserver/ControllerHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ public static Controller handleController(HttpRequest httpRequest) {
//TODO httpRequest 보고 어떤 컨트롤러 써야되는지 리턴 해줘야됨

// 어떤 기능 (회원가입, 로그인 등) 을 원한다면?
if (httpRequest.wantDynamic()) {
// POST 요청일 때
if (httpRequest.isPost()) {
// 누가 원하는지 > 누구의 컨트롤러가 필요한지 넘겨줌
// User가 원하면 UserController
return whoWant(httpRequest.getUri());
Expand Down
Loading

0 comments on commit d40aa31

Please sign in to comment.