From fa53c622383f65f0c92e6a665bc3e1e0f8694512 Mon Sep 17 00:00:00 2001 From: yoondori2 Date: Tue, 23 Aug 2022 14:34:50 +0900 Subject: [PATCH 1/3] add training process --- src/main/java/webserver/RequestHandler.java | 266 ++++++++++++++++---- webapp/index.html | 2 +- webapp/user/form.html | 2 +- 3 files changed, 225 insertions(+), 45 deletions(-) diff --git a/src/main/java/webserver/RequestHandler.java b/src/main/java/webserver/RequestHandler.java index 90195ec4e..48cc76f1f 100644 --- a/src/main/java/webserver/RequestHandler.java +++ b/src/main/java/webserver/RequestHandler.java @@ -1,55 +1,235 @@ package webserver; +import java.io.BufferedReader; import java.io.DataOutputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.net.Socket; +import java.nio.file.Files; +import java.util.Collection; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import db.DataBase; +import model.User; +import util.HttpRequestUtils; +import util.IOUtils; + + public class RequestHandler extends Thread { - private static final Logger log = LoggerFactory.getLogger(RequestHandler.class); - - private Socket connection; - - public RequestHandler(Socket connectionSocket) { - this.connection = connectionSocket; - } - - public void run() { - log.debug("New Client Connect! Connected IP : {}, Port : {}", connection.getInetAddress(), - connection.getPort()); - - try (InputStream in = connection.getInputStream(); OutputStream out = connection.getOutputStream()) { - // TODO 사용자 요청에 대한 처리는 이 곳에 구현하면 된다. - DataOutputStream dos = new DataOutputStream(out); - byte[] body = "Hello World".getBytes(); - response200Header(dos, body.length); - responseBody(dos, body); - } catch (IOException e) { - log.error(e.getMessage()); - } - } - - private void response200Header(DataOutputStream dos, int lengthOfBodyContent) { - try { - dos.writeBytes("HTTP/1.1 200 OK \r\n"); - dos.writeBytes("Content-Type: text/html;charset=utf-8\r\n"); - dos.writeBytes("Content-Length: " + lengthOfBodyContent + "\r\n"); - dos.writeBytes("\r\n"); - } catch (IOException e) { - log.error(e.getMessage()); - } - } - - private void responseBody(DataOutputStream dos, byte[] body) { - try { - dos.write(body, 0, body.length); - dos.flush(); - } catch (IOException e) { - log.error(e.getMessage()); - } - } -} + private static final Logger log = LoggerFactory.getLogger(RequestHandler.class); + + private Socket connection; + + public RequestHandler(Socket connectionSocket) { + this.connection = connectionSocket; + } + + public void run() { + log.debug("New Client Connect! Connected IP : {}, Port : {}", connection.getInetAddress(), + connection.getPort()); + + try (InputStream in = connection.getInputStream(); OutputStream out = connection.getOutputStream()) { + + // 1.1 inputstream 한 줄로 읽기 + BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); + // 1.2 라인별로 HTTP 요청 정보 읽어오기 + String line = br.readLine(); + + log.debug("request line : {}", line); + // 3.1 post의 경우 본문에 값이 담기므로 length를 0으로 초기화시켜준다. + int contentLength = 0; + // 1.3 line이 null값인 경우 예외 처리하기 + if (line == null) { + return; + } + // 1.5 문자열 분리하기 ( GET /index.html HTTP/1.1 ) + String[] tokens = line.split(" "); + + // 6.1 사용자 목록에서 로그인 유무를 판단해서 보여주기 위한 변수 + boolean logined = false; + + // 1.4 헤더 마지막은 while문으로 확인이 가능하다. + while (!line.equals("")) { + log.debug("header line: {} ", line); + line = br.readLine(); + if (line.contains("Cookie")) { + logined = isLogin(line); + } + // 3.2 contentLength 읽어와서 가져오기 + if (line.contains("Content-Length")) { + // 콘턴츠 길이 가져오기 + contentLength = getContentLength(line); + } + } + // 2.1 HTTP 요청 첫 번째 라인에서 요청 URL을 추출한다. + String url = tokens[1]; // user\create?userId=apple&.... + + if ("/user/create".contains(url)) { + // 3.3 본문 길이만큼 읽어오기 + String body = IOUtils.readData(br, contentLength); + // 2.3 이름이랑 값 파싱하기 (필자가 구현한 API) + Map params = HttpRequestUtils.parseQueryString(body); + // 2.4 User 객체에 저장해주기 + User user = new User(params.get("userId"), params.get("password"), params.get("name"), + params.get("email")); + + log.debug("user: {}" , user); + // 4.1 리다이렉트방식으로 페이지 이동하므로 302코드 이용하기 + DataOutputStream dos = new DataOutputStream(out); + response302Header(dos, "/index.html"); + + // 5.1 database에 user정보를 저장해둔다. + DataBase.addUser(user); + + //7.1 css파일 읽어오기 + } else if (url.endsWith(".css")) { + DataOutputStream dos = new DataOutputStream(out); + byte[] body = Files.readAllBytes(new File("./webapp" + url).toPath()); + response200CssHeader(dos, body.length); + responseBody(dos, body); + } + // 5.2 login버튼 눌렀을때 body 읽어오고 값 파싱하고 저장해둔 database에 userid 와 같으면 성공했음 알려주기 + else if ("/user/login".contains(url)) { + String body = IOUtils.readData(br, contentLength); + Map params = HttpRequestUtils.parseQueryString(body); + User user = DataBase.findUserById(params.get("userId")); + if (user == null) { + responseResource(out, "/user/login_failed.html"); + } + if (user.getPassword().equals(params.get("password"))) { + DataOutputStream dos = new DataOutputStream(out); + response302LoginSuccessHeader(dos); + } else { + responseResource(out, "/user/login_failed.html"); + + } + // 6.1 사용자 목록 들어갔을때 목록 띄우기 + } else if ("/user/list".contains(url)) { + if (!logined) { + responseResource(out, "/user/login.html"); + return; + + } + System.out.println("뭐지?"); + // 6.2 담아준 user 가져오기 + Collection users = DataBase.findAll(); + log.debug("user: {}",users); + // 6.3 StringBuilder 사용해서 출력하기 + StringBuilder sb = new StringBuilder(); + + sb.append(""); + for (User user : users) { + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + } + sb.append("
" + user.getUserId() + "" + user.getName() + "" + user.getEmail() + "
"); + // 6.4 출력문 byte형식으로 변환해서 읽어오기 + byte[] body = sb.toString().getBytes(); + // 6.5 츨력스트림 열기 + DataOutputStream dos = new DataOutputStream(out); + // 6.6 성공했으므로 200코드로 출력스트림이랑 body길이 보내기 + response200Header(dos, body.length); + // 6.7 스트림 닫기 + responseBody(dos, body); + } + + else { + responseResource(out, url); + } + } catch (IOException e) { + log.error(e.getMessage()); + } + } + + private void response200CssHeader(DataOutputStream dos, int lengthOfBodyContent) { + try { + dos.writeBytes("HTTP/1.1 200 OK \r\n"); + dos.writeBytes("Content-Type: text/css;charset=utf-8\r\n"); + dos.writeBytes("Content-Length: " + lengthOfBodyContent+ "\r\n"); + dos.writeBytes("\r\n"); + } catch (IOException e) { + log.error(e.getMessage()); + } + + } + + private boolean isLogin(String line) { + String[] headerTokens = line.split(":"); + Map cookies = HttpRequestUtils.parseCookies(headerTokens[1].trim()); + String value = cookies.get("logined"); + + if (value == null) { + return false; + } + return Boolean.parseBoolean(value); + } + + // 로그인 성공했을 경우 리다이렉트로 이동하게 해줌. + private void response302LoginSuccessHeader(DataOutputStream dos) { + try { + dos.writeBytes("HTTP/1.1 302 Redirect \r\n"); + dos.writeBytes("Set-Cookie: logined=true \r\n"); + dos.writeBytes("Location: /index.html" + "\r\n"); + dos.writeBytes("\r\n"); + } catch (IOException e) { + log.error(e.getMessage()); + + } + } + + // 파일 읽고 응답해주는 메소드 + private void responseResource(OutputStream out, String url) throws IOException { + DataOutputStream dos = new DataOutputStream(out); + byte[] body = Files.readAllBytes(new File("./webapp" + url).toPath()); + response200Header(dos, body.length); + responseBody(dos, body); + + } + + private int getContentLength(String line) { + // line : Content-length:59 // headertokens {Content-length, 59} + String[] headertokens = line.split(":"); + return Integer.parseInt(headertokens[1].trim()); // 59 + } + + // 2xx: 성공 + private void response200Header(DataOutputStream dos, int lengthOfBodyContent) { + try { + dos.writeBytes("HTTP/1.1 200 OK \r\n"); + dos.writeBytes("Content-Type: text/html;charset=utf-8\r\n"); + dos.writeBytes("Content-Length: " + lengthOfBodyContent + "\r\n"); + dos.writeBytes("\r\n"); + } catch (IOException e) { + log.error(e.getMessage()); + } + } + + // 3xx : 리다이렉션(요청을 마치기 위해 추가 동작이 필요하다) + private void response302Header(DataOutputStream dos, String url) { + try { + dos.writeBytes("HTTP/1.1 302 Redirect \r\n"); + dos.writeBytes("Location: " + url + "\r\n"); + dos.writeBytes("\r\n"); + } catch (IOException e) { + log.error(e.getMessage()); + } + } + + private void responseBody(DataOutputStream dos, byte[] body) { + try { + dos.write(body, 0, body.length); + dos.flush(); + } catch (IOException e) { + log.error(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/webapp/index.html b/webapp/index.html index 17cbd51d1..bae7e7632 100644 --- a/webapp/index.html +++ b/webapp/index.html @@ -40,7 +40,7 @@
  • Facebook
  • -
  • +
  • =
  • diff --git a/webapp/user/form.html b/webapp/user/form.html index 96fe1bd3a..f7a3b5612 100644 --- a/webapp/user/form.html +++ b/webapp/user/form.html @@ -75,7 +75,7 @@
    -
    +
    From ae7f093a8c92e7d252384bce26190ac207f30c11 Mon Sep 17 00:00:00 2001 From: yoondori2 Date: Tue, 23 Aug 2022 16:02:34 +0900 Subject: [PATCH 2/3] add training code --- src/main/java/webserver/HttpRequest.java | 89 ++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/main/java/webserver/HttpRequest.java diff --git a/src/main/java/webserver/HttpRequest.java b/src/main/java/webserver/HttpRequest.java new file mode 100644 index 000000000..b915ca453 --- /dev/null +++ b/src/main/java/webserver/HttpRequest.java @@ -0,0 +1,89 @@ +package webserver; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import util.HttpRequestUtils; +import util.IOUtils; + +public class HttpRequest { + private static final Logger log = LoggerFactory.getLogger(HttpRequest.class); + + private String method; + private String path; + private Map headers = new HashMap(); + private Map params = new HashMap(); + + public HttpRequest(InputStream in) { + try { + BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); + String line = br.readLine(); + + if (line == null) { + return; + } + + processRequestLine(line); + + while (line.equals("")) { + log.debug("header line: {}", line); + line = br.readLine(); + String[] tokens = line.split(":"); + headers.put(tokens[0].trim(), tokens[1].trim()); + + } + if("POST".equals(method)) { + String body = IOUtils.readData(br, Integer.parseInt(headers.get("Content-Length"))); + params = HttpRequestUtils.parseQueryString(body); + + } + } catch (IOException io) { + log.error(io.getMessage()); + } + + } + + private void processRequestLine(String requestline) { + log.debug("request line: {}", requestline); // GET /doc/text.html HTTP/1.1 + String[] tokens = requestline.split(" "); + method = tokens[0]; // GET + + if ("POST".equals(method)) { + path = tokens[1]; + return; + } + int index = tokens[1].indexOf("?"); + if (index == -1) { + path = tokens[1]; + } else { + path = tokens[1].substring(0, index); // 잘라내고 싶은 범위 정하기 (0부터 index까지) + params = HttpRequestUtils.parseQueryString(tokens[1].substring(index + 1));// 파라미터 받아오기 + + } + + } + + public String getMethod() { + return method; + } + + public String getPath() { + return path; + } + + public String getHeader(String name) { + return headers.get(name); + } + + public String getParameter(String name) { + return params.get(name); + } +} From 09f8f80f884a1f47084d2440c4589fdfb1a3b8ad Mon Sep 17 00:00:00 2001 From: yoondori2 Date: Wed, 24 Aug 2022 17:07:43 +0900 Subject: [PATCH 3/3] add refactoring --- src/main/java/webserver/HttpMethod.java | 16 ++ src/main/java/webserver/HttpRequest.java | 79 +++---- src/main/java/webserver/HttpResponse.java | 98 +++++++++ src/main/java/webserver/RequestHandler.java | 200 ++++------------- src/main/java/webserver/RequestLine.java | 57 +++++ src/test/java/util/HttpRequestTest.java | 39 ++++ src/test/java/util/HttpResponseTest.java | 49 +++++ src/test/java/util/RequestLineTest.java | 33 +++ src/test/resources/Http_Cookie.txt | 4 + src/test/resources/Http_Forward.txt | 228 ++++++++++++++++++++ src/test/resources/Http_GET.txt | 7 + src/test/resources/Http_POST.txt | 8 + src/test/resources/Http_Redirect.txt | 3 + 13 files changed, 615 insertions(+), 206 deletions(-) create mode 100644 src/main/java/webserver/HttpMethod.java create mode 100644 src/main/java/webserver/HttpResponse.java create mode 100644 src/main/java/webserver/RequestLine.java create mode 100644 src/test/java/util/HttpRequestTest.java create mode 100644 src/test/java/util/HttpResponseTest.java create mode 100644 src/test/java/util/RequestLineTest.java create mode 100644 src/test/resources/Http_Cookie.txt create mode 100644 src/test/resources/Http_Forward.txt create mode 100644 src/test/resources/Http_GET.txt create mode 100644 src/test/resources/Http_POST.txt create mode 100644 src/test/resources/Http_Redirect.txt diff --git a/src/main/java/webserver/HttpMethod.java b/src/main/java/webserver/HttpMethod.java new file mode 100644 index 000000000..6a5b3bc13 --- /dev/null +++ b/src/main/java/webserver/HttpMethod.java @@ -0,0 +1,16 @@ +package webserver; + +//객체를 최대한 활용하려고 연습하기 위해서는 객체에서 값을 꺼낸 후 로직을 구현하려고 하지말고 +//값을 가지고 있는 객체에 메세지를 보내 일ㅇ릉 시키도록 연습하는 것이 좋다. +//이 메소드도 get, post 값을 꺼내 비교하는 것이아니라 이 값을 가지고 있는 +//Httpmethod가 Post여부를 판단하도록 메세지를 보내 물어보고 있다. + +public enum HttpMethod { + GET, + POST; + + + public boolean isPost() { + return this == POST; + } +} diff --git a/src/main/java/webserver/HttpRequest.java b/src/main/java/webserver/HttpRequest.java index b915ca453..4e79ee0f2 100644 --- a/src/main/java/webserver/HttpRequest.java +++ b/src/main/java/webserver/HttpRequest.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Map; @@ -16,73 +15,59 @@ public class HttpRequest { private static final Logger log = LoggerFactory.getLogger(HttpRequest.class); - - private String method; - private String path; - private Map headers = new HashMap(); - private Map params = new HashMap(); - + + //HTTP 메소드 , URL, 헤더, 본문을 분리하는 작업을 한다. + private RequestLine requestline; + private Map headers = new HashMap(); //헤더 + private Map params = new HashMap(); //post로 들어오는 파리미터 받아오기 + private HttpMethod method; + + public HttpRequest(InputStream in) { try { BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); String line = br.readLine(); - + if (line == null) { return; } - - processRequestLine(line); - - while (line.equals("")) { - log.debug("header line: {}", line); - line = br.readLine(); - String[] tokens = line.split(":"); - headers.put(tokens[0].trim(), tokens[1].trim()); - + //1.request line 받아오기 + requestline = new RequestLine(line); + + + //2.header 받아오기 + line = br.readLine(); + while (!line.equals("")) { + log.debug("header: {}", line); // header : Connection: keep-alive + String[] tokens = line.split(":"); // tokens = [Connection, keep-alive] + //2.1.해더는 Map에 저장한다. + headers.put(tokens[0].trim(), tokens[1].trim()); + line = br.readLine(); + } - if("POST".equals(method)) { + if(method.POST.equals(getMethod())) { String body = IOUtils.readData(br, Integer.parseInt(headers.get("Content-Length"))); params = HttpRequestUtils.parseQueryString(body); + }else { + //get 메소드인 경우 url에서 추출해왔으므로 그대로 담아주기 + params = requestline.getParams(); } } catch (IOException io) { log.error(io.getMessage()); } } - - private void processRequestLine(String requestline) { - log.debug("request line: {}", requestline); // GET /doc/text.html HTTP/1.1 - String[] tokens = requestline.split(" "); - method = tokens[0]; // GET - - if ("POST".equals(method)) { - path = tokens[1]; - return; - } - int index = tokens[1].indexOf("?"); - if (index == -1) { - path = tokens[1]; - } else { - path = tokens[1].substring(0, index); // 잘라내고 싶은 범위 정하기 (0부터 index까지) - params = HttpRequestUtils.parseQueryString(tokens[1].substring(index + 1));// 파라미터 받아오기 - - } - + + public String getHeader(String name) { + return headers.get(name); } - - public String getMethod() { - return method; + public HttpMethod getMethod() { + return requestline.getMethod(); } - public String getPath() { - return path; + return requestline.getPath(); } - - public String getHeader(String name) { - return headers.get(name); - } - public String getParameter(String name) { return params.get(name); } diff --git a/src/main/java/webserver/HttpResponse.java b/src/main/java/webserver/HttpResponse.java new file mode 100644 index 000000000..58393b743 --- /dev/null +++ b/src/main/java/webserver/HttpResponse.java @@ -0,0 +1,98 @@ +package webserver; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HttpResponse { +private static final Logger log = LoggerFactory.getLogger(HttpRequest.class); + + private DataOutputStream dos = null; + private Map headers = new HashMap(); //헤더 + + public HttpResponse(OutputStream out) { + dos = new DataOutputStream(out); + + } + public void addHeader(String key, String value) { + headers.put(key, value); + } + //HTMl ,CSS , 자바스크립트 파일 읽어 응답으로 보내기. + public void forward(String url) { + byte[] body = null; + try { + body = Files.readAllBytes(new File("./webapp" + url).toPath()); + if(url.endsWith(".css")) { + headers.put("Content-Type", "text/css"); + }else if (url.endsWith(".js")) { + headers.put("Contenxt-Type", "application/javascript"); + }else { + headers.put("Content-Type", "text/html;charset=utf-8"); + } + } catch (IOException e) { + log.error(e.getMessage()); + } + response200Header(body.length); + responseBody(body); + } + + //body 읽어서 header에 저장하기.(Stringbuilder를 읽어오기 위해 만든 메소드) + public void forwardBody(String body) { + byte[] contents = body.getBytes(); + headers.put("Content-Type", "text/html;charset=utf-8"); + headers.put("Content-Length", contents.length+""); + response200Header(contents.length); + responseBody(contents); + } + //로그인 성공한 경우 (302코드) + public void sendRedirect (String redirectUrl) { + try { + dos.writeBytes("HTTP/1.1 302 Found \r\n"); + processHeaders(); + dos.writeBytes("Location: "+redirectUrl+"\r\n"); + dos.writeBytes("\r\n"); + } catch (IOException e) { + log.error(e.getMessage()); + } + } + //내용 읽어오기 성공한 경우 + private void response200Header(int lengthOfBodyContent) { + try { + dos.writeBytes("HTTP/1.1 200 OK \r\n"); + processHeaders(); + dos.writeBytes("\r\n"); + } catch (IOException e) { + log.error(e.getMessage()); + } + } + //stream 쓰고 닫아주기. + private void responseBody(byte[] body) { + try { + dos.write(body, 0, body.length); + dos.writeBytes("\r\n"); + dos.flush(); + } catch (IOException e) { + log.error(e.getMessage()); + } + } + //header를 처리하는 메소드이다. + private void processHeaders() { + try { + Set keys = headers.keySet(); //header의 키들만 다 데려오기 ex: Content-Type, Content-Length 등 + for (String key : keys) { + dos.writeBytes(key +":"+ headers.get(key) + "\r\n");// Content-Type: text/html \r\n + } + } catch (IOException e) { + log.error(e.getMessage()); + } + } + +} diff --git a/src/main/java/webserver/RequestHandler.java b/src/main/java/webserver/RequestHandler.java index 48cc76f1f..e1b1e185d 100644 --- a/src/main/java/webserver/RequestHandler.java +++ b/src/main/java/webserver/RequestHandler.java @@ -11,6 +11,7 @@ import java.nio.file.Files; import java.util.Collection; import java.util.Map; +import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,7 +21,6 @@ import util.HttpRequestUtils; import util.IOUtils; - public class RequestHandler extends Thread { private static final Logger log = LoggerFactory.getLogger(RequestHandler.class); @@ -35,92 +35,45 @@ public void run() { connection.getPort()); try (InputStream in = connection.getInputStream(); OutputStream out = connection.getOutputStream()) { + HttpRequest request = new HttpRequest(in); + HttpResponse response = new HttpResponse(out); - // 1.1 inputstream 한 줄로 읽기 - BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); - // 1.2 라인별로 HTTP 요청 정보 읽어오기 - String line = br.readLine(); - - log.debug("request line : {}", line); - // 3.1 post의 경우 본문에 값이 담기므로 length를 0으로 초기화시켜준다. - int contentLength = 0; - // 1.3 line이 null값인 경우 예외 처리하기 - if (line == null) { - return; - } - // 1.5 문자열 분리하기 ( GET /index.html HTTP/1.1 ) - String[] tokens = line.split(" "); - - // 6.1 사용자 목록에서 로그인 유무를 판단해서 보여주기 위한 변수 - boolean logined = false; - - // 1.4 헤더 마지막은 while문으로 확인이 가능하다. - while (!line.equals("")) { - log.debug("header line: {} ", line); - line = br.readLine(); - if (line.contains("Cookie")) { - logined = isLogin(line); - } - // 3.2 contentLength 읽어와서 가져오기 - if (line.contains("Content-Length")) { - // 콘턴츠 길이 가져오기 - contentLength = getContentLength(line); - } - } - // 2.1 HTTP 요청 첫 번째 라인에서 요청 URL을 추출한다. - String url = tokens[1]; // user\create?userId=apple&.... - - if ("/user/create".contains(url)) { - // 3.3 본문 길이만큼 읽어오기 - String body = IOUtils.readData(br, contentLength); - // 2.3 이름이랑 값 파싱하기 (필자가 구현한 API) - Map params = HttpRequestUtils.parseQueryString(body); - // 2.4 User 객체에 저장해주기 - User user = new User(params.get("userId"), params.get("password"), params.get("name"), - params.get("email")); - - log.debug("user: {}" , user); - // 4.1 리다이렉트방식으로 페이지 이동하므로 302코드 이용하기 - DataOutputStream dos = new DataOutputStream(out); - response302Header(dos, "/index.html"); - - // 5.1 database에 user정보를 저장해둔다. + String path = getDefaultPath(request.getPath()); + + if ("/user/create".equals(path)) { + + User user = new User(request.getParameter("userId"), request.getParameter("password"), + request.getParameter("name"), request.getParameter("email")); + + log.debug("user: {}", user); + + response.sendRedirect("/index.html"); DataBase.addUser(user); - - //7.1 css파일 읽어오기 - } else if (url.endsWith(".css")) { - DataOutputStream dos = new DataOutputStream(out); - byte[] body = Files.readAllBytes(new File("./webapp" + url).toPath()); - response200CssHeader(dos, body.length); - responseBody(dos, body); + } - // 5.2 login버튼 눌렀을때 body 읽어오고 값 파싱하고 저장해둔 database에 userid 와 같으면 성공했음 알려주기 - else if ("/user/login".contains(url)) { - String body = IOUtils.readData(br, contentLength); - Map params = HttpRequestUtils.parseQueryString(body); - User user = DataBase.findUserById(params.get("userId")); - if (user == null) { - responseResource(out, "/user/login_failed.html"); - } - if (user.getPassword().equals(params.get("password"))) { - DataOutputStream dos = new DataOutputStream(out); - response302LoginSuccessHeader(dos); - } else { - responseResource(out, "/user/login_failed.html"); + else if ("/user/login".equals(path)) { + User user = DataBase.findUserById(request.getParameter("userId")); + if (user != null) { + if (user.getPassword().equals(request.getParameter("password"))) { + response.addHeader("Set-Cookie:", "logined=true"); + response.sendRedirect("/index.html"); + } else { + response.sendRedirect("/user/login_failed.html"); + + } + + } else { + response.sendRedirect("/user/login_failed.html"); } - // 6.1 사용자 목록 들어갔을때 목록 띄우기 - } else if ("/user/list".contains(url)) { - if (!logined) { - responseResource(out, "/user/login.html"); + } else if ("/user/list".equals(path)) { + if (!isLogin(request.getHeader("Cookie"))) { + response.sendRedirect("/user/login.html"); return; - } - System.out.println("뭐지?"); - // 6.2 담아준 user 가져오기 + Collection users = DataBase.findAll(); - log.debug("user: {}",users); - // 6.3 StringBuilder 사용해서 출력하기 + log.debug("user: {}", users); StringBuilder sb = new StringBuilder(); sb.append(""); @@ -132,39 +85,27 @@ else if ("/user/login".contains(url)) { sb.append(""); } sb.append("
    "); - // 6.4 출력문 byte형식으로 변환해서 읽어오기 - byte[] body = sb.toString().getBytes(); - // 6.5 츨력스트림 열기 - DataOutputStream dos = new DataOutputStream(out); - // 6.6 성공했으므로 200코드로 출력스트림이랑 body길이 보내기 - response200Header(dos, body.length); - // 6.7 스트림 닫기 - responseBody(dos, body); + response.forwardBody(sb.toString()); } else { - responseResource(out, url); + response.sendRedirect(path); } } catch (IOException e) { log.error(e.getMessage()); } } - private void response200CssHeader(DataOutputStream dos, int lengthOfBodyContent) { - try { - dos.writeBytes("HTTP/1.1 200 OK \r\n"); - dos.writeBytes("Content-Type: text/css;charset=utf-8\r\n"); - dos.writeBytes("Content-Length: " + lengthOfBodyContent+ "\r\n"); - dos.writeBytes("\r\n"); - } catch (IOException e) { - log.error(e.getMessage()); + private String getDefaultPath(String path) { + if (path.equals("/")) { + return "/index.html"; } - + return path; } - private boolean isLogin(String line) { - String[] headerTokens = line.split(":"); - Map cookies = HttpRequestUtils.parseCookies(headerTokens[1].trim()); + // cookieValue = (logined=true) + private boolean isLogin(String cookieValue) { + Map cookies = HttpRequestUtils.parseCookies(cookieValue); String value = cookies.get("logined"); if (value == null) { @@ -173,63 +114,4 @@ private boolean isLogin(String line) { return Boolean.parseBoolean(value); } - // 로그인 성공했을 경우 리다이렉트로 이동하게 해줌. - private void response302LoginSuccessHeader(DataOutputStream dos) { - try { - dos.writeBytes("HTTP/1.1 302 Redirect \r\n"); - dos.writeBytes("Set-Cookie: logined=true \r\n"); - dos.writeBytes("Location: /index.html" + "\r\n"); - dos.writeBytes("\r\n"); - } catch (IOException e) { - log.error(e.getMessage()); - - } - } - - // 파일 읽고 응답해주는 메소드 - private void responseResource(OutputStream out, String url) throws IOException { - DataOutputStream dos = new DataOutputStream(out); - byte[] body = Files.readAllBytes(new File("./webapp" + url).toPath()); - response200Header(dos, body.length); - responseBody(dos, body); - - } - - private int getContentLength(String line) { - // line : Content-length:59 // headertokens {Content-length, 59} - String[] headertokens = line.split(":"); - return Integer.parseInt(headertokens[1].trim()); // 59 - } - - // 2xx: 성공 - private void response200Header(DataOutputStream dos, int lengthOfBodyContent) { - try { - dos.writeBytes("HTTP/1.1 200 OK \r\n"); - dos.writeBytes("Content-Type: text/html;charset=utf-8\r\n"); - dos.writeBytes("Content-Length: " + lengthOfBodyContent + "\r\n"); - dos.writeBytes("\r\n"); - } catch (IOException e) { - log.error(e.getMessage()); - } - } - - // 3xx : 리다이렉션(요청을 마치기 위해 추가 동작이 필요하다) - private void response302Header(DataOutputStream dos, String url) { - try { - dos.writeBytes("HTTP/1.1 302 Redirect \r\n"); - dos.writeBytes("Location: " + url + "\r\n"); - dos.writeBytes("\r\n"); - } catch (IOException e) { - log.error(e.getMessage()); - } - } - - private void responseBody(DataOutputStream dos, byte[] body) { - try { - dos.write(body, 0, body.length); - dos.flush(); - } catch (IOException e) { - log.error(e.getMessage()); - } - } } \ No newline at end of file diff --git a/src/main/java/webserver/RequestLine.java b/src/main/java/webserver/RequestLine.java new file mode 100644 index 000000000..f35011bb2 --- /dev/null +++ b/src/main/java/webserver/RequestLine.java @@ -0,0 +1,57 @@ +package webserver; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import util.HttpRequestUtils; + +public class RequestLine { + private static final Logger log = LoggerFactory.getLogger(HttpRequest.class); + + + private HttpMethod method; + private String path; + private Map params = new HashMap(); + + public RequestLine(String requestline) { + log.debug("request line: {}", requestline); // GET /doc/text.html HTTP/1.1 + String[] tokens = requestline.split(" "); // tokens = [GET, /doc/text.html, HTTP/1.1] + + if(tokens.length !=3) { + throw new IllegalArgumentException(requestline+"이 형식에 맞지 않습니다."); + } + //1. HttpMethod의 형태로 method에 담아준다. + method = HttpMethod.valueOf(tokens[0]); + + + //2.enum에 isPost 메소드 만들어서 넣어주기 + if (method.isPost()) { + path = tokens[1]; + return; + } + // get의 경우 url에 파라미터 있으므로 ?를 기준으로 나눠줘야 한다. + //ex) GET /user/create?userId=javajigi&password=password&name=JaeSung + int index = tokens[1].indexOf("?"); + if (index == -1) { + path = tokens[1]; + } else { + path = tokens[1].substring(0, index); // /user/create + //userId=javajigi&password=password&name=JaeSung + params = HttpRequestUtils.parseQueryString(tokens[1].substring(index + 1));// index바로 뒤부터 끝까지 가져오기 + } + } + + public HttpMethod getMethod() { + return method; + } + public String getPath() { + return path; + } + public Map getParams() { + return params; + } + +} diff --git a/src/test/java/util/HttpRequestTest.java b/src/test/java/util/HttpRequestTest.java new file mode 100644 index 000000000..ebdb25f97 --- /dev/null +++ b/src/test/java/util/HttpRequestTest.java @@ -0,0 +1,39 @@ +package util; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; + +import org.junit.Test; + +import webserver.HttpMethod; +import webserver.HttpRequest; + +public class HttpRequestTest { + private String testDirectory = "./src/test/resources/"; + + + @Test + public void request_GET() throws Exception{ + InputStream in = new FileInputStream(new File(testDirectory + "Http_GET.txt")); + HttpRequest request = new HttpRequest(in); + + assertEquals(HttpMethod.GET, request.getMethod()); + assertEquals("/user/create", request.getPath()); + assertEquals("keep-alive", request.getHeader("Connection")); + assertEquals("javajigi",request.getParameter("userId")); + + } + @Test + public void request_POST() throws Exception{ + InputStream in = new FileInputStream(new File(testDirectory + "Http_POST.txt")); + HttpRequest request = new HttpRequest(in); + + assertEquals(HttpMethod.POST, request.getMethod()); + assertEquals("/user/create", request.getPath()); + assertEquals("keep-alive",request.getHeader("Connection")); + assertEquals("javajigi",request.getParameter("userId")); + } +} diff --git a/src/test/java/util/HttpResponseTest.java b/src/test/java/util/HttpResponseTest.java new file mode 100644 index 000000000..7d9aac6dd --- /dev/null +++ b/src/test/java/util/HttpResponseTest.java @@ -0,0 +1,49 @@ +package util; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +import org.junit.Test; + +import webserver.HttpMethod; +import webserver.HttpRequest; +import webserver.HttpResponse; + +public class HttpResponseTest { + private String testDirectory = "./src/test/resources/"; + + @Test + public void responseForward() throws Exception{ + //Http_Forward.txt 결과는 응답 body에 index.html에 포함되어 있어야 한다. 이걸 어떻게 읽지..? + HttpResponse response = new HttpResponse(createOutputStream("Http_Forward.txt")); + response.forward("/index.html"); + } + + @Test + public void responseRedirect() throws Exception{ + //Http_Redirect.txt 결과는 응답 headere에 + //Location 정보가 /index.thml 로 포함되어 있어야 한다. + HttpResponse response = new HttpResponse(createOutputStream("Http_Redirect.txt")); + response.sendRedirect("/index.html"); + + } + + @Test + public void responseCookies() throws Exception{ + //Http_Cookie.txt 결과는 응답 header 에 Set-Cookie 값으로 + //logined-true 값이 포함되어 있어야 한다. + HttpResponse response = new HttpResponse(createOutputStream("Http_Cookie.txt")); + response.addHeader("Set-Cookie:", "logined=true"); + response.sendRedirect("/index.html"); + + } + private OutputStream createOutputStream(String filename) throws FileNotFoundException{ + return new FileOutputStream(new File(testDirectory + filename)); + } +} diff --git a/src/test/java/util/RequestLineTest.java b/src/test/java/util/RequestLineTest.java new file mode 100644 index 000000000..bd4d9a287 --- /dev/null +++ b/src/test/java/util/RequestLineTest.java @@ -0,0 +1,33 @@ +package util; + +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +import webserver.RequestLine; + +public class RequestLineTest { + + @Test + public void create_method() { + RequestLine line = new RequestLine("GET /index.html HTTP/1.1"); + assertEquals("GET", line.getMethod()); + assertEquals("/index.html", line.getPath()); + + line = new RequestLine("POST /index.html HTTP/1.1"); + assertEquals("POST", line.getMethod()); + assertEquals("/index.html", line.getPath());; + } + + @Test + public void create_path_and_params() { + RequestLine line = new RequestLine("GET /user/create?userId=javajigi&password=pass HTTP/1.1"); + assertEquals("GET", line.getMethod()); + assertEquals("/user/create", line.getPath()); + Map params = line.getParams(); + assertEquals(2, params.size()); + } +} diff --git a/src/test/resources/Http_Cookie.txt b/src/test/resources/Http_Cookie.txt new file mode 100644 index 000000000..baa045386 --- /dev/null +++ b/src/test/resources/Http_Cookie.txt @@ -0,0 +1,4 @@ +HTTP/1.1 302 Found +Set-Cookie::logined=true +Location: /index.html + diff --git a/src/test/resources/Http_Forward.txt b/src/test/resources/Http_Forward.txt new file mode 100644 index 000000000..9b52087ea --- /dev/null +++ b/src/test/resources/Http_Forward.txt @@ -0,0 +1,228 @@ +HTTP/1.1 200 OK +Content-Type:text/html;charset=utf-8 + + + + + + + SLiPP Java Web Programming + + + + + + + + + + +
    +
    +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + + + + + + + + + + + + + diff --git a/src/test/resources/Http_GET.txt b/src/test/resources/Http_GET.txt new file mode 100644 index 000000000..b673f783e --- /dev/null +++ b/src/test/resources/Http_GET.txt @@ -0,0 +1,7 @@ +GET /user/create?userId=javajigi&password=password&name=JaeSung HTTP/1.1 +Host: localhost:8080 +Connection: keep-alive +Accept: */* + + + \ No newline at end of file diff --git a/src/test/resources/Http_POST.txt b/src/test/resources/Http_POST.txt new file mode 100644 index 000000000..b790f0666 --- /dev/null +++ b/src/test/resources/Http_POST.txt @@ -0,0 +1,8 @@ +POST /user/create HTTP/1.1 +Host: localhost:8080 +Connection: keep-alive +Content-Length: 46 +Content-Type: application/x-www-form-urlencoded +Accept: */* + +userId=javajigi&password=password&name=JaeSung \ No newline at end of file diff --git a/src/test/resources/Http_Redirect.txt b/src/test/resources/Http_Redirect.txt new file mode 100644 index 000000000..6cfc06e55 --- /dev/null +++ b/src/test/resources/Http_Redirect.txt @@ -0,0 +1,3 @@ +HTTP/1.1 302 Found +Location: /index.html +