-
Notifications
You must be signed in to change notification settings - Fork 309
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[톰캣 구현하기 - 3, 4단계] 민트(유재민) 미션 제출합니다. #462
Changes from 16 commits
3f0294c
8305190
0d43834
cb4b214
e0a48ae
3ac7fc4
49c7bc1
f7f4b30
68e6b8b
f93afb6
a4e4859
75cc474
0c52760
33143c2
14f4173
f10daee
e9eb960
47be420
b9c41d4
ce51f43
a734474
5fe3478
632d976
eb08f63
7da6384
c09577b
d0be660
4ce0385
718dc00
281c806
f27518e
405a942
f252d96
f8b86c9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,6 @@ handlebars: | |
server: | ||
tomcat: | ||
accept-count: 1 | ||
max-connections: 1 | ||
max-connections: 2 | ||
threads: | ||
max: 2 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,13 @@ | ||
package org.apache.coyote.http11; | ||
|
||
import nextstep.jwp.db.InMemoryUserRepository; | ||
import nextstep.jwp.exception.UncheckedServletException; | ||
import nextstep.jwp.model.User; | ||
import org.apache.coyote.Processor; | ||
import org.apache.coyote.http11.controller.AuthController; | ||
import org.apache.coyote.http11.controller.RegisterController; | ||
import org.apache.coyote.http11.request.HttpHeaders; | ||
import org.apache.coyote.http11.request.HttpRequest; | ||
import org.apache.coyote.http11.request.RequestLine; | ||
import org.apache.coyote.http11.response.HttpResponse; | ||
import org.apache.coyote.http11.response.HttpStatus; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
|
@@ -16,22 +20,18 @@ | |
import java.nio.file.Files; | ||
import java.nio.file.Paths; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Map.Entry; | ||
import java.util.Objects; | ||
import java.util.Optional; | ||
import java.util.UUID; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
public class Http11Processor implements Runnable, Processor { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); | ||
private static final String SAFARI_CHROME_ACCEPT_HEADER_DEFAULT_VALUE = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"; | ||
|
||
private final Socket connection; | ||
private final SessionManager sessionManager = new SessionManager(); | ||
|
||
private final AuthController authController = new AuthController(); | ||
private final RegisterController registerController = new RegisterController(); | ||
|
||
public Http11Processor(final Socket connection) { | ||
this.connection = connection; | ||
|
@@ -51,182 +51,65 @@ public void process(final Socket connection) { | |
InputStreamReader inputStreamReader = new InputStreamReader(inputStream); | ||
BufferedReader bufferedReader = new BufferedReader(inputStreamReader); | ||
|
||
List<String> headers = new ArrayList<>(); | ||
HttpRequest httpRequest = readHttpRequest(bufferedReader); | ||
HttpResponse httpResponse = new HttpResponse(httpRequest.httpVersion()); | ||
|
||
String header = ""; | ||
while (!(header = bufferedReader.readLine()).equals("")) { | ||
headers.add(header); | ||
} | ||
String[] splitStatusLine = Objects.requireNonNull(headers.get(0)).split(" "); | ||
String requestMethod = splitStatusLine[0]; | ||
String requestUri = splitStatusLine[1]; | ||
|
||
String requestAcceptHeader = findHeader("Accept", headers); | ||
String contentTypeHeader = getContentTypeHeaderFrom(requestAcceptHeader); | ||
|
||
RequestHandler requestHandler; | ||
if (requestMethod.equalsIgnoreCase("POST")) { | ||
int contentLength = getContentLength(headers); | ||
String requestBody = readRequestBody(bufferedReader, contentLength); | ||
requestHandler = handlePostRequest(requestUri, requestBody); | ||
if (httpRequest.path().equals("/login") || httpRequest.path().equals("/login.html")) { | ||
authController.service(httpRequest, httpResponse); | ||
} else if (httpRequest.path().equals("/register") || httpRequest.path().equals("/register.html")) { | ||
registerController.service(httpRequest, httpResponse); | ||
} else { | ||
String cookieHeader = findHeader("Cookie", headers); | ||
requestHandler = handleGetRequest(requestMethod, requestUri, cookieHeader); | ||
httpResponse.setHttpStatus(HttpStatus.OK).setResponseFileName(httpRequest.path()); | ||
} | ||
|
||
String responseBody = readFile(requestHandler.getResponseFilePath()); | ||
|
||
List<String> responseHeaders = new ArrayList<>(); | ||
responseHeaders.add("HTTP/1.1 " + requestHandler.getHttpStatus() + " "); | ||
responseHeaders.add(contentTypeHeader); | ||
responseHeaders.add("Content-Length: " + responseBody.getBytes().length + " "); | ||
for (Entry<String, String> headerEntry : requestHandler.getHeaders().entrySet()) { | ||
responseHeaders.add(headerEntry.getKey() + ": " + headerEntry.getValue()); | ||
} | ||
String responseHeader = String.join("\r\n", responseHeaders); | ||
httpResponse.setBody(readFile(httpResponse.getResponseFileName())); | ||
httpResponse.addHeader("Content-Length", String.valueOf(httpResponse.getBody().getBytes().length)); | ||
httpResponse.addHeader("Content-Type", getContentTypeHeaderFrom(httpRequest)); | ||
|
||
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream); | ||
|
||
var response = String.join("\r\n", responseHeader, "", responseBody); | ||
|
||
bufferedOutputStream.write(response.getBytes()); | ||
bufferedOutputStream.write(httpResponse.format().getBytes()); | ||
bufferedOutputStream.flush(); | ||
} catch (IOException | UncheckedServletException e) { | ||
} catch (Exception e) { | ||
log.error(e.getMessage(), e); | ||
} | ||
} | ||
|
||
private static String findHeader(String key, List<String> headers) { | ||
return headers.stream() | ||
.filter(it -> it.startsWith(key + ": ")) | ||
.findFirst() | ||
.orElse("Accept: " + SAFARI_CHROME_ACCEPT_HEADER_DEFAULT_VALUE); | ||
private HttpRequest readHttpRequest(BufferedReader bufferedReader) throws IOException { | ||
List<String> lines = readRequesteHeaders(bufferedReader); | ||
RequestLine requestLine = RequestLine.from(lines.get(0)); | ||
HttpHeaders httpHeaders = HttpHeaders.from(lines.subList(1, lines.size())); | ||
String requestBody = readRequestBody(bufferedReader, httpHeaders.contentLength()); | ||
return new HttpRequest(requestLine, httpHeaders, requestBody); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 메서드부터 시작해서 요청을 읽고 요청라인, 헤더, 바디를 읽는 부분은 HttpRequest가 갖고있는게 어떨까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 리뷰를 보고 생각하니 확실히 책임이 분리되는 것도 괜찮을 거 같더라고요. |
||
|
||
private static String getContentTypeHeaderFrom(String requestAcceptHeader) { | ||
String[] splitAcceptHeader = requestAcceptHeader.split(" "); | ||
String headerValue = splitAcceptHeader[1]; | ||
String[] acceptTypes = headerValue.split(";"); | ||
String[] splitAcceptTypes = acceptTypes[0].split(","); | ||
|
||
if (Arrays.asList(splitAcceptTypes).contains("text/css")) { | ||
return "Content-Type: text/css;charset=utf-8 "; | ||
private List<String> readRequesteHeaders(BufferedReader bufferedReader) throws IOException { | ||
List<String> lines = new ArrayList<>(); | ||
String line = ""; | ||
while (!(line = bufferedReader.readLine()).equals("")) { | ||
lines.add(line); | ||
} | ||
return "Content-Type: text/html;charset=utf-8 "; | ||
return lines; | ||
} | ||
yujamint marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private int getContentLength(List<String> headers) { | ||
Optional<String> contentLengthHeader = headers.stream() | ||
.filter(it -> it.startsWith("Content-Length")) | ||
.findFirst(); | ||
|
||
if (contentLengthHeader.isEmpty()) { | ||
return -1; | ||
private static String getContentTypeHeaderFrom(HttpRequest httpRequest) { | ||
List<String> acceptHeaderValues = httpRequest.header("Accept"); | ||
if (acceptHeaderValues != null && acceptHeaderValues.contains("text/css")) { | ||
return "text/css;charset=utf-8"; | ||
} | ||
yujamint marked this conversation as resolved.
Show resolved
Hide resolved
|
||
int index = contentLengthHeader.get().indexOf(" "); | ||
return Integer.parseInt(contentLengthHeader.get().substring(index + 1)); | ||
return "text/html;charset=utf-8"; | ||
} | ||
|
||
private static String readRequestBody(BufferedReader bufferedReader, int contentLength) throws IOException { | ||
if (contentLength <= 0) { | ||
return ""; | ||
} | ||
char[] buffer = new char[contentLength]; | ||
bufferedReader.read(buffer, 0, contentLength); | ||
return new String(buffer); | ||
} | ||
yujamint marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private RequestHandler handlePostRequest(String requestUri, String requestBody) { | ||
String[] splitRequestBody = requestBody.split("&"); | ||
if (requestUri.equals("/login")) { | ||
return handleLoginRequest(splitRequestBody); | ||
} | ||
if (requestUri.equals("/register")) { | ||
return handleRegisterRequest(splitRequestBody); | ||
} | ||
return RequestHandler.of("GET", "404 Not Found", "static/404.html"); | ||
} | ||
|
||
private RequestHandler handleLoginRequest(String[] splitQueryString) { | ||
Optional<String> account = getValueOf("account", splitQueryString); | ||
Optional<String> password = getValueOf("password", splitQueryString); | ||
|
||
if (account.isEmpty() || password.isEmpty()) { | ||
return RequestHandler.of("GET", "400 Bad Request", "static/401.html"); | ||
} | ||
|
||
Optional<User> findUser = InMemoryUserRepository.findByAccount(account.get()); | ||
if (findUser.isPresent() && findUser.get().checkPassword(password.get())) { | ||
User user = findUser.get(); | ||
log.info(user.toString()); | ||
Session session = new Session(UUID.randomUUID().toString()); | ||
session.setAttribute("user", user); | ||
sessionManager.add(session); | ||
RequestHandler requestHandler = RequestHandler.of("GET", "302 Found", "static/index.html"); | ||
requestHandler.addHeader("Set-Cookie", "JSESSIONID=" + session.getId()); | ||
return requestHandler; | ||
} | ||
return RequestHandler.of("GET", "401 Unauthorized", "static/401.html"); | ||
} | ||
|
||
private Optional<String> getValueOf(String key, String[] splitQueryString) { | ||
return Arrays.stream(splitQueryString) | ||
.filter(it -> equalsKey(key, it)) | ||
.map(it -> it.substring(it.indexOf("=") + 1)) | ||
.findFirst(); | ||
} | ||
|
||
private boolean equalsKey(String expected, String actual) { | ||
String[] splitActual = actual.split("="); | ||
return splitActual[0].equals(expected); | ||
} | ||
|
||
private RequestHandler handleRegisterRequest(String[] splitQueryString) { | ||
Optional<String> account = getValueOf("account", splitQueryString); | ||
Optional<String> email = getValueOf("email", splitQueryString); | ||
Optional<String> password = getValueOf("password", splitQueryString); | ||
|
||
if (account.isEmpty() || email.isEmpty() || password.isEmpty()) { | ||
return RequestHandler.of("GET", "400 Bad Request", "static/register.html"); | ||
} | ||
|
||
InMemoryUserRepository.save(new User(account.get(), password.get(), email.get())); | ||
return RequestHandler.of("GET", "302 Found", "static/index.html"); | ||
} | ||
|
||
private RequestHandler handleGetRequest(String requestMethod, String requestUri, String cookie) throws IOException { | ||
if (!requestMethod.equalsIgnoreCase("GET")) { | ||
throw new IllegalArgumentException("GET 요청만 처리 가능합니다."); | ||
} | ||
|
||
if (requestUri.equals("/login.html") || requestUri.equals("/login")) { | ||
return handleLoginPageRequest(cookie); | ||
} | ||
|
||
String fileName = "static" + requestUri; | ||
return RequestHandler.of("GET", "200 OK", fileName); | ||
} | ||
|
||
private RequestHandler handleLoginPageRequest(String cookie) throws IOException { | ||
Optional<String> sessionId = getSessionId(cookie); | ||
if (sessionId.isEmpty()) { | ||
return RequestHandler.of("GET", "200 OK", "static/login.html"); | ||
} | ||
Session session = sessionManager.findSession(sessionId.get()); | ||
User user = getUser(session); | ||
if (InMemoryUserRepository.existsByAccount(user.getAccount())) { | ||
return RequestHandler.of("GET", "302 Found", "static/index.html"); | ||
} | ||
return RequestHandler.of("GET", "200 OK", "static/login.html"); | ||
} | ||
|
||
private Optional<String> getSessionId(String cookieHeader) { | ||
String[] splitCookie = cookieHeader.split(" "); | ||
return getValueOf("JSESSIONID", splitCookie); | ||
} | ||
|
||
private User getUser(Session session) { | ||
return (User) session.getAttribute("user"); | ||
} | ||
|
||
private String readFile(String filePath) { | ||
private String readFile(String fileName) { | ||
String filePath = this.getClass().getClassLoader().getResource("static" + fileName).getPath(); | ||
try (Stream<String> lines = Files.lines(Paths.get(filePath))) { | ||
return lines.collect(Collectors.joining("\n", "", "\n")); | ||
} catch (IOException | UncheckedIOException e) { | ||
yujamint marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
왜 Exception으로 변경하셨을까용?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Controller.service()에서 발생할 수 있는 에러로 여러 종류가 있을 거라고 생각했습니다.
발생할 수 있는 에러를 커스텀 예외로 추상화해서 catch 했다면 좋았을 것 같지만, 당장은 모든 예외를 잡을 수 있는 Exception으로 변경해서 개발을 빠르게 하고자 했네요 ㅋㅋㅋ..