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 new file mode 100644 index 000000000..4e79ee0f2 --- /dev/null +++ b/src/main/java/webserver/HttpRequest.java @@ -0,0 +1,74 @@ +package webserver; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +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); + + //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; + } + //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(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()); + } + + } + + public String getHeader(String name) { + return headers.get(name); + } + public HttpMethod getMethod() { + return requestline.getMethod(); + } + public String getPath() { + return requestline.getPath(); + } + 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 90195ec4e..e1b1e185d 100644 --- a/src/main/java/webserver/RequestHandler.java +++ b/src/main/java/webserver/RequestHandler.java @@ -1,55 +1,117 @@ 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 java.util.regex.Pattern; 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()) { + HttpRequest request = new HttpRequest(in); + HttpResponse response = new HttpResponse(out); + + 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); + + } + + 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"); + } + } else if ("/user/list".equals(path)) { + if (!isLogin(request.getHeader("Cookie"))) { + response.sendRedirect("/user/login.html"); + return; + } + + Collection users = DataBase.findAll(); + log.debug("user: {}", users); + 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() + "
"); + response.forwardBody(sb.toString()); + } + + else { + response.sendRedirect(path); + } + } catch (IOException e) { + log.error(e.getMessage()); + } + } + + private String getDefaultPath(String path) { + if (path.equals("/")) { + return "/index.html"; + } + return path; + } + + // cookieValue = (logined=true) + private boolean isLogin(String cookieValue) { + Map cookies = HttpRequestUtils.parseCookies(cookieValue); + String value = cookies.get("logined"); + + if (value == null) { + return false; + } + return Boolean.parseBoolean(value); + } + +} \ 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 + 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 @@
    -
    +