diff --git a/study/src/test/java/study/FileTest.java b/study/src/test/java/study/FileTest.java index e1b6cca042..68924c3c87 100644 --- a/study/src/test/java/study/FileTest.java +++ b/study/src/test/java/study/FileTest.java @@ -1,13 +1,15 @@ package study; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; /** * 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. @@ -28,7 +30,8 @@ class FileTest { final String fileName = "nextstep.txt"; // todo - final String actual = ""; + final URL resource = getClass().getClassLoader().getResource(fileName); + final String actual = resource.getFile(); assertThat(actual).endsWith(fileName); } @@ -40,14 +43,16 @@ class FileTest { * File, Files 클래스를 사용하여 파일의 내용을 읽어보자. */ @Test - void 파일의_내용을_읽는다() { + void 파일의_내용을_읽는다() throws IOException { final String fileName = "nextstep.txt"; + final URL resource = getClass().getClassLoader().getResource(fileName); + // todo - final Path path = null; + final Path path = new File(resource.getPath()).toPath(); // todo - final List actual = Collections.emptyList(); + final List actual = Files.readAllLines(path); assertThat(actual).containsOnly("nextstep"); } diff --git a/study/src/test/java/study/IOStreamTest.java b/study/src/test/java/study/IOStreamTest.java index 47a79356b6..1d5b776341 100644 --- a/study/src/test/java/study/IOStreamTest.java +++ b/study/src/test/java/study/IOStreamTest.java @@ -1,14 +1,24 @@ package study; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import java.io.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - /** * 자바는 스트림(Stream)으로부터 I/O를 사용한다. * 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다. @@ -53,7 +63,7 @@ class OutputStream_학습_테스트 { * todo * OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다 */ - + outputStream.write(bytes); final String actual = outputStream.toString(); assertThat(actual).isEqualTo("nextstep"); @@ -78,6 +88,7 @@ class OutputStream_학습_테스트 { * flush를 사용해서 테스트를 통과시킨다. * ByteArrayOutputStream과 어떤 차이가 있을까? */ + outputStream.flush(); verify(outputStream, atLeastOnce()).flush(); outputStream.close(); @@ -96,6 +107,11 @@ class OutputStream_학습_테스트 { * try-with-resources를 사용한다. * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. */ + try (OutputStream ignored = outputStream) { + // outputStream을 사용하는 코드 + } catch (IOException e) { + e.printStackTrace(); + } verify(outputStream, atLeastOnce()).close(); } @@ -128,7 +144,16 @@ class InputStream_학습_테스트 { * todo * inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까? */ - final String actual = ""; + + byte[] byteArray = new byte[1024]; + int bytesRead; + final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + while ((bytesRead = inputStream.read(byteArray)) != -1) { + byteArrayOutputStream.write(byteArray, 0, bytesRead); + } + + // 바이트 배열을 UTF-8 문자열로 변환 + final String actual = new String(byteArrayOutputStream.toByteArray(), "UTF-8"); assertThat(actual).isEqualTo("🤩"); assertThat(inputStream.read()).isEqualTo(-1); @@ -148,6 +173,11 @@ class InputStream_학습_테스트 { * try-with-resources를 사용한다. * java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다. */ + try (InputStream ignored = inputStream) { + // inputStream을 사용하는 코드 + } catch (IOException e) { + e.printStackTrace(); + } verify(inputStream, atLeastOnce()).close(); } @@ -169,12 +199,12 @@ class FilterStream_학습_테스트 { * 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까? */ @Test - void 필터인_BufferedInputStream를_사용해보자() { + void 필터인_BufferedInputStream를_사용해보자() throws IOException { final String text = "필터에 연결해보자."; final InputStream inputStream = new ByteArrayInputStream(text.getBytes()); - final InputStream bufferedInputStream = null; + final InputStream bufferedInputStream = new BufferedInputStream(inputStream); - final byte[] actual = new byte[0]; + final byte[] actual = bufferedInputStream.readAllBytes(); assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class); assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes()); @@ -206,6 +236,14 @@ class InputStreamReader_학습_테스트 { final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes()); final StringBuilder actual = new StringBuilder(); + try (final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + actual.append(line).append("\r\n"); + } + } catch (IOException e) { + throw new RuntimeException(e); + } assertThat(actual).hasToString(emoji); } diff --git a/tomcat/src/main/java/nextstep/jwp/model/Session.java b/tomcat/src/main/java/nextstep/jwp/model/Session.java new file mode 100644 index 0000000000..2ac8ceb637 --- /dev/null +++ b/tomcat/src/main/java/nextstep/jwp/model/Session.java @@ -0,0 +1,22 @@ +package nextstep.jwp.model; + +import java.util.HashMap; +import java.util.Map; + +public class Session { + + private final String id; + private final Map values = new HashMap<>(); + + public Session(final String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setAttribute(final String name, final Object value) { + values.put(name, value); + } +} diff --git a/tomcat/src/main/java/org/apache/catalina/Manager.java b/tomcat/src/main/java/org/apache/catalina/Manager.java index e69410f6a9..1153287219 100644 --- a/tomcat/src/main/java/org/apache/catalina/Manager.java +++ b/tomcat/src/main/java/org/apache/catalina/Manager.java @@ -1,8 +1,7 @@ package org.apache.catalina; -import jakarta.servlet.http.HttpSession; - import java.io.IOException; +import nextstep.jwp.model.Session; /** * A Manager manages the pool of Sessions that are associated with a @@ -29,7 +28,7 @@ public interface Manager { * * @param session Session to be added */ - void add(HttpSession session); + void add(Session session); /** * Return the active Session, associated with this Manager, with the @@ -45,12 +44,12 @@ public interface Manager { * @return the request session or {@code null} if a session with the * requested ID could not be found */ - HttpSession findSession(String id) throws IOException; + Session findSession(String id) throws IOException; /** * Remove this Session from the active Sessions for this Manager. * * @param session Session to be removed */ - void remove(HttpSession session); + void remove(Session session); } diff --git a/tomcat/src/main/java/org/apache/catalina/SessionManager.java b/tomcat/src/main/java/org/apache/catalina/SessionManager.java new file mode 100644 index 0000000000..6a5e32e6b1 --- /dev/null +++ b/tomcat/src/main/java/org/apache/catalina/SessionManager.java @@ -0,0 +1,27 @@ +package org.apache.catalina; + +import java.util.HashMap; +import java.util.Map; +import nextstep.jwp.model.Session; + +public class SessionManager implements Manager { + + private static final Map SESSIONS = new HashMap<>(); + + @Override + public void add(final Session session) { + SESSIONS.put(session.getId(), session); + } + + @Override + public Session findSession(final String id) { + return SESSIONS.get(id); + } + + @Override + public void remove(final Session session) { + SESSIONS.remove(session.getId()); + } + + public SessionManager() {} +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java index 7f1b2c7e96..b45081fb6c 100644 --- a/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java +++ b/tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java @@ -1,17 +1,31 @@ package org.apache.coyote.http11; +import nextstep.jwp.db.InMemoryUserRepository; import nextstep.jwp.exception.UncheckedServletException; +import nextstep.jwp.model.Session; +import nextstep.jwp.model.User; +import org.apache.catalina.SessionManager; import org.apache.coyote.Processor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.BufferedReader; +import java.io.File; import java.io.IOException; +import java.io.InputStreamReader; import java.net.Socket; +import java.net.URL; +import java.nio.file.Files; +import java.util.Optional; +import java.util.UUID; + +import static org.apache.coyote.http11.HttpHeaderType.*; +import static org.apache.coyote.http11.HttpStatusCode.*; public class Http11Processor implements Runnable, Processor { private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); - + private static final SessionManager sessionManager = new SessionManager(); private final Socket connection; public Http11Processor(final Socket connection) { @@ -27,21 +41,143 @@ public void run() { @Override public void process(final Socket connection) { try (final var inputStream = connection.getInputStream(); - final var outputStream = connection.getOutputStream()) { - - final var responseBody = "Hello world!"; + final var outputStream = connection.getOutputStream(); + final var reader = new BufferedReader(new InputStreamReader(inputStream))) { - final var response = String.join("\r\n", - "HTTP/1.1 200 OK ", - "Content-Type: text/html;charset=utf-8 ", - "Content-Length: " + responseBody.getBytes().length + " ", - "", - responseBody); + final HttpRequestParser httpRequestParser = new HttpRequestParser(reader); + final HttpRequest httpRequest = httpRequestParser.parse(); + final HttpCookie httpCookie = HttpCookie.of(httpRequest.getHeader().get("Cookie")); - outputStream.write(response.getBytes()); + // TODO: generate RequestHandler + final HttpResponse httpResponse = handleRequest(httpRequest); + outputStream.write(httpResponse.stringify().getBytes()); outputStream.flush(); } catch (IOException | UncheckedServletException e) { log.error(e.getMessage(), e); } } + + private HttpResponse handleRequest(final HttpRequest httpRequest) throws IOException { + if (httpRequest.getTarget().equals("/")) { + return handleRootRequest(); + } + + if (httpRequest.getTarget().equals("/login")) { + return handleLoginRequest(httpRequest); + } + + if (httpRequest.getTarget().equals("/register")) { + return handleRegisterRequest(httpRequest); + } + + return handleResourceRequest(httpRequest, httpRequest.getTarget()); + } + + private HttpResponse handleRootRequest() { + final HttpResponse httpResponse = HttpResponse.init(); + httpResponse.addHeader(CONTENT_TYPE, "text/html;charset=utf-8"); + httpResponse.setStatusCode(OK); + httpResponse.setBody("Hello world!"); + return httpResponse; + } + + private HttpResponse handleRegisterRequest(final HttpRequest httpRequest) throws IOException { + if (httpRequest.getMethod().equals("GET")) { + return handleResourceRequest(httpRequest, "register.html"); + } + + if (httpRequest.getMethod().equals("POST")) { + final HttpResponse httpResponse = HttpResponse.init(); + final User newUser = new User( + httpRequest.getBody().get("account"), + httpRequest.getBody().get("password"), + httpRequest.getBody().get("email") + ); + InMemoryUserRepository.save(newUser); + httpResponse.addHeader(CONTENT_TYPE, "text/html;charset=utf-8"); + httpResponse.addHeader(LOCATION, "/index.html"); + httpResponse.setStatusCode(FOUND); + return httpResponse; + } + return handleResourceRequest(httpRequest, "404.html"); // Method Not Allowed + } + + private HttpResponse handleLoginRequest(final HttpRequest httpRequest) throws IOException { + final HttpCookie httpCookie = HttpCookie.of(httpRequest.getHeader().get("Cookie")); + String sessionId = httpCookie.getCookie("JSESSIONID"); + + if (httpRequest.getMethod().equals("GET")) { + final HttpResponse httpResponse = HttpResponse.init(); + + final Session session = sessionManager.findSession(sessionId); + if (session != null) { + // already login user + httpResponse.setStatusCode(FOUND); + httpResponse.addHeader(CONTENT_TYPE, "text/html;charset=utf-8"); + httpResponse.addHeader(LOCATION, "/index.html"); + return httpResponse; + } + // not login user + return handleResourceRequest(httpRequest, "login.html"); + } + + if (httpRequest.getMethod().equals("POST")) { + final HttpResponse httpResponse = HttpResponse.init(); + + Optional user = InMemoryUserRepository.findByAccount(httpRequest.getBody().get("account")); + if (user.isEmpty() || !user.get().checkPassword(httpRequest.getBody().get("password"))) { + // invalid user + httpResponse.addHeader(CONTENT_TYPE, "text/html;charset=utf-8"); + httpResponse.addHeader(LOCATION, "/401.html"); + httpResponse.setStatusCode(FOUND); + return httpResponse; + } + + // valid user + log.info("user: {}", user.get()); + + if (sessionId != null) { // if already have session + httpResponse.addHeader(CONTENT_TYPE, "text/html;charset=utf-8"); + httpResponse.addHeader(LOCATION, "/index.html"); + httpResponse.setStatusCode(FOUND); + return httpResponse; + } + + // if no session + Session session = new Session(String.valueOf(UUID.randomUUID())); + session.setAttribute("user", user); + sessionManager.add(session); + sessionId = session.getId(); + + httpResponse.addHeader(CONTENT_TYPE, "text/html;charset=utf-8"); + httpResponse.addHeader(LOCATION, "/index.html"); + httpResponse.addHeader(SET_COOKIE, "JSESSIONID=" + sessionId); + httpResponse.setStatusCode(FOUND); + return httpResponse; + } + return handleResourceRequest(httpRequest, "404.html"); // Method Not Allowed + } + + private HttpResponse handleResourceRequest(final HttpRequest httpRequest, final String resourceUrl) throws IOException { + final HttpResponse httpResponse = HttpResponse.init(); + String contentType = "text/html;charset=utf-8"; + + if (httpRequest.getHeader().get("Accept") != null) { + contentType = httpRequest.getHeader().get("Accept").split(",")[0]; + } + + URL resource = getClass().getClassLoader().getResource("static/" + resourceUrl); + if (resource != null) { + httpResponse.setStatusCode(OK); + } else { + resource = getClass().getClassLoader().getResource("static/" + "404.html"); + httpResponse.setStatusCode(NOT_FOUND); + contentType = "text/html;charset=utf-8"; + } + + final String responseBody = new String(Files.readAllBytes(new File(resource.getFile()).toPath())); + httpResponse.addHeader(CONTENT_TYPE, contentType); + httpResponse.setBody(responseBody); + return httpResponse; + } } diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java new file mode 100644 index 0000000000..6475ac5195 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpCookie.java @@ -0,0 +1,33 @@ +package org.apache.coyote.http11; + +import java.util.HashMap; +import java.util.Map; + +public class HttpCookie { + + public final Map value; + + private HttpCookie(final Map value) { + this.value = value; + } + + public static HttpCookie of(final String cookies) { + return new HttpCookie(parse(cookies)); + } + + private static Map parse(final String cookies) { + if (cookies == null) { + return new HashMap<>(); + } + Map cookieValue = new HashMap<>(); + for (String cookie : cookies.split("; ")) { + String[] pair = cookie.split("="); + cookieValue.put(pair[0], pair[1]); + } + return cookieValue; + } + + public String getCookie(final String key) { + return value.get(key); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaderType.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaderType.java new file mode 100644 index 0000000000..ec017785a9 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaderType.java @@ -0,0 +1,20 @@ +package org.apache.coyote.http11; + +public enum HttpHeaderType { + + CONTENT_TYPE("Content-Type"), + CONTENT_LENGTH("Content-Length"), + LOCATION("Location"), + SET_COOKIE("Set-Cookie"), + ACCEPT("Accept"); + + private final String name; + + HttpHeaderType(final String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java new file mode 100644 index 0000000000..e5e7570c3f --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpHeaders.java @@ -0,0 +1,31 @@ +package org.apache.coyote.http11; + +import java.util.HashMap; +import java.util.Map; + +import static org.apache.coyote.http11.HttpHeaderType.CONTENT_LENGTH; + +public class HttpHeaders { + + private final Map headers = new HashMap<>(); + + public String getContentLength() { + return headers.get(CONTENT_LENGTH.getName()); + } + + public String getHeaderValue(final HttpHeaderType type) { + return headers.get(type.getName()); + } + + public void setHeaderValue(final HttpHeaderType type, final String value) { + headers.put(type.getName(), value); + } + + public void setHeaderValue(final String type, final String value) { + headers.put(type, value); + } + + public Map getHeaders() { + return headers; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java new file mode 100644 index 0000000000..e0a3b573cd --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequest.java @@ -0,0 +1,40 @@ +package org.apache.coyote.http11; + +import java.util.Map; + +public class HttpRequest { + + private final RequestLine requestLine; + private final Map header; + private final Map body; + + private HttpRequest(final RequestLine requestLine, final Map header, final Map body) { + this.requestLine = requestLine; + this.header = header; + this.body = body; + } + + public static HttpRequest of(final RequestLine requestLine, final Map header, final Map body) { + return new HttpRequest(requestLine, header, body); + } + + public String getMethod() { + return requestLine.getMethod(); + } + + public String getTarget() { + return requestLine.getTarget(); + } + + public String getVersion() { + return requestLine.getVersion(); + } + + public Map getHeader() { + return header; + } + + public Map getBody() { + return body; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java new file mode 100644 index 0000000000..bdc19d6c67 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpRequestParser.java @@ -0,0 +1,62 @@ +package org.apache.coyote.http11; + +import java.io.BufferedReader; +import java.io.IOException; +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.Map; + +public class HttpRequestParser { + + private final BufferedReader reader; + + public HttpRequestParser(final BufferedReader reader) { + this.reader = reader; + } + + public HttpRequest parse() { + try { + final RequestLine requestLine = parseRequestLine(); + final Map requestHeader = parseRequestHeader(); + if (requestHeader.get("Content-Length") != null) { + final Map body = parseRequestBody(requestHeader.get("Content-Length")); + return HttpRequest.of(requestLine, requestHeader, body); + } + return HttpRequest.of(requestLine, requestHeader, new HashMap<>()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private RequestLine parseRequestLine() throws IOException { + final String line = reader.readLine(); + String[] s = line.split(" "); + return RequestLine.from(s[0], s[1], s[2]); + } + + private Map parseRequestHeader() throws IOException { + // TODO: Change to MultiValueMap + final Map header = new HashMap<>(); + String line; + while (!"".equals(line = reader.readLine())) { + String[] value = line.split(": "); + header.put(value[0], value[1]); + } + return header; + } + + private Map parseRequestBody(final String contentLengthHeader) throws IOException { + // TODO: Change to MultiValueMap + final Map body = new HashMap<>(); + int contentLength = Integer.parseInt(contentLengthHeader); + char[] buffer = new char[contentLength]; + reader.read(buffer, 0, contentLength); + + // TODO: Query Parse + for (String temp : new String(buffer).split("&")) { + String[] value = temp.split("="); + body.put(value[0], URLDecoder.decode(value[1], "UTF-8")); + } + return body; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java new file mode 100644 index 0000000000..ae1c03f091 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpResponse.java @@ -0,0 +1,64 @@ +package org.apache.coyote.http11; + +import java.util.Map; + +import static org.apache.coyote.http11.HttpStatusCode.NOT_FOUND; + +public class HttpResponse { + + private HttpStatusCode statusCode; + private final HttpHeaders headers; +// private final Map header; + private String body; + + private HttpResponse(final HttpStatusCode statusCode, final HttpHeaders headers, final String body) { + this.statusCode = statusCode; + this.headers = headers; + this.body = body; + } + + public static HttpResponse init() { + return new HttpResponse(NOT_FOUND, new HttpHeaders(), ""); + } + + public void addHeader(final String key, final String value) { + headers.setHeaderValue(key, value); + } + + public void addHeader(final HttpHeaderType key, final String value) { + headers.setHeaderValue(key, value); + } + + public void setStatusCode(final HttpStatusCode statusCode) { + this.statusCode = statusCode; + } + + public void setBody(final String body) { + this.body = body; + } + + public String stringify() { + return String.join( + "\r\n", + "HTTP/1.1 " + statusCode.stringify() + " ", + stringifyHeaders(), + "", + body + ); + } + + private String stringifyHeaders() { + final StringBuilder stringBuilder = new StringBuilder(); + for (Map.Entry entry : headers.getHeaders().entrySet()) { + stringBuilder.append(entry.getKey()) + .append(": ") + .append(entry.getValue()) + .append(" \r\n"); + } + stringBuilder.append("Content-Length: ") + .append(body.getBytes().length) + .append(" "); + + return stringBuilder.toString(); + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/HttpStatusCode.java b/tomcat/src/main/java/org/apache/coyote/http11/HttpStatusCode.java new file mode 100644 index 0000000000..634bf44e1e --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/HttpStatusCode.java @@ -0,0 +1,20 @@ +package org.apache.coyote.http11; + +public enum HttpStatusCode { + + OK(200, "OK"), + FOUND(302, "Found"), + NOT_FOUND(404, "Not Found"); + + private final int code; + private final String message; + + HttpStatusCode(final int code, final String message) { + this.code = code; + this.message = message; + } + + public String stringify() { + return code + " " + message; + } +} diff --git a/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java b/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java new file mode 100644 index 0000000000..a27f61c3b7 --- /dev/null +++ b/tomcat/src/main/java/org/apache/coyote/http11/RequestLine.java @@ -0,0 +1,30 @@ +package org.apache.coyote.http11; + +public class RequestLine { + + private final String method; + private final String target; + private final String version; + + private RequestLine(final String method, final String target, final String version) { + this.method = method; + this.target = target; + this.version = version; + } + + public static RequestLine from(final String method, final String target, final String version) { + return new RequestLine(method, target, version); + } + + public String getMethod() { + return method; + } + + public String getTarget() { + return target; + } + + public String getVersion() { + return version; + } +} diff --git a/tomcat/src/main/resources/static/login.html b/tomcat/src/main/resources/static/login.html index f4ed9de875..bc933357f2 100644 --- a/tomcat/src/main/resources/static/login.html +++ b/tomcat/src/main/resources/static/login.html @@ -20,7 +20,7 @@

로그인

-
+