Skip to content

[톰캣 구현하기 - 3, 4단계] 푸우(백승준) 미션 제출합니다. #446

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

Merged
merged 29 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7cfe069
chore: HttpResponse -> ResponseEntity 변경
BGuga Sep 10, 2023
58a60ff
feat: StatusLine 객체 생성
BGuga Sep 10, 2023
6a56819
feat: ResponseHeaders 객체 생성
BGuga Sep 10, 2023
0fb84e6
feat: HttpResponse 객체 생성
BGuga Sep 10, 2023
93fa845
feat: HttpResponse 가 Response 를 만든다.
BGuga Sep 10, 2023
f3f2cc1
chore: Headers 네이밍 변경
BGuga Sep 10, 2023
90c7469
feat: AbstractController 생성
BGuga Sep 10, 2023
80c54d5
refactor: login 요청에 대한 LoginController 생성
BGuga Sep 10, 2023
3784039
refactor: register 요청에 대한 SignUpController 생성
BGuga Sep 10, 2023
65eba7e
feat: Thread Pool 적용
BGuga Sep 10, 2023
473c173
feat: SessionManager 에 ConcurrentHashMap 적용
BGuga Sep 10, 2023
4aae70f
fix: Content-Type, Content-Length 헤더 값에 : 제거
BGuga Sep 10, 2023
6f2a563
test: HttpResponse 테스트 추가
BGuga Sep 10, 2023
b672c07
test: 통합 테스트 추가
BGuga Sep 10, 2023
d3804c5
feat: ResponseEntity 에 redirect 메서드 생성
BGuga Sep 10, 2023
fdae1b2
fix: notAllowed ResponseEntity 에 빈 Map 추가
BGuga Sep 10, 2023
df7b7ae
refactor: AbstractController 에서 notAllowed 를 정의하도록 변경
BGuga Sep 10, 2023
1ff493e
refactor: AbstractController 메서드 protected 설정
BGuga Sep 10, 2023
264a343
chore: 상수와 인스턴스 변수 간의 공백 추가
BGuga Sep 10, 2023
cd6d5d9
refactor: CoreSize, maxThread 개수 변경
BGuga Sep 11, 2023
9786a30
refactor: Controller 인터페이스 수정
BGuga Sep 13, 2023
6e883bd
feat: Connector 코어사이즈, keep-alive 시간 변경
BGuga Sep 13, 2023
eb26d4a
refactor: ResponseBuilder location, sessionCookie 기능 추가 및 상수 분리
BGuga Sep 13, 2023
867bc28
refactor: RequestHeaders Content-Length 추가
BGuga Sep 13, 2023
6791dbf
refactor: Http 표준 스펙에 맞춘 응답을 생성하는 HttpREsponseConverter 생성
BGuga Sep 13, 2023
e861aa3
refactor: Http 표준 스펙에 맞춘 응답을 생성하는 HttpREsponseConverter 생성
BGuga Sep 13, 2023
5813104
refactor: getter 대신 메시지를 던지도록 변경
BGuga Sep 13, 2023
21e6e10
fix: Content-Length 상수 은닉
BGuga Sep 14, 2023
cb1c4f6
refactor: 사용하지 않는 getter 삭제 및 내부 필드 은닉화
BGuga Sep 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions tomcat/src/main/java/org/apache/catalina/connector/Connector.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,44 @@
package org.apache.catalina.connector;

import org.apache.coyote.http11.Http11Processor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.coyote.http11.Http11Processor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Connector implements Runnable {

private static final Logger log = LoggerFactory.getLogger(Connector.class);

private static final int DEFAULT_PORT = 8080;
private static final int DEFAULT_ACCEPT_COUNT = 100;
private static final int DEFAULT_MAX_THREAD = 250;
private static final int CORE_POOL_SIZE = 100;

private final ServerSocket serverSocket;
private final ExecutorService executorService;
private boolean stopped;

public Connector() {
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT);
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, DEFAULT_MAX_THREAD);
}

public Connector(final int port, final int acceptCount) {
public Connector(final int port, final int acceptCount, final int maxThreads) {
this.serverSocket = createServerSocket(port, acceptCount);

this.executorService = new ThreadPoolExecutor(
CORE_POOL_SIZE,
maxThreads,
5L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(acceptCount)
);
this.stopped = false;
}

Expand Down Expand Up @@ -67,7 +81,7 @@ private void process(final Socket connection) {
return;
}
var processor = new Http11Processor(connection);
new Thread(processor).start();
executorService.execute(processor);
}

public void stop() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.apache.coyote.Processor;
import org.apache.coyote.http11.controller.HandlerMapper;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -22,7 +23,7 @@ public class Http11Processor implements Runnable, Processor {
public Http11Processor(final Socket connection) {
this.connection = connection;
this.resourceProvider = new ResourceProvider();
this.handlerMapper = new HandlerMapper(this.resourceProvider);
this.handlerMapper = new HandlerMapper();
}

@Override
Expand All @@ -36,20 +37,22 @@ public void process(final Socket connection) {
try (final var inputReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
final var outputStream = connection.getOutputStream()) {
HttpRequest httpRequest = HttpRequest.makeRequest(inputReader);
String response = getResponse(httpRequest);
String response = getResponse(httpRequest, new HttpResponse());
outputStream.write(response.getBytes());
outputStream.flush();
} catch (IOException | UncheckedServletException e) {
log.error(e.getMessage(), e);
}
}

private String getResponse(HttpRequest httpRequest) {
if (resourceProvider.haveResource(httpRequest.getRequestLine().getPath())) {
return resourceProvider.staticResourceResponse(httpRequest.getRequestLine().getPath());
private String getResponse(HttpRequest httpRequest, HttpResponse httpResponse) {
String path = httpRequest.getRequestLine().getPath();
if (resourceProvider.haveResource(path)) {
return resourceProvider.staticResourceResponse(path);
}
if (handlerMapper.haveAvailableHandler(httpRequest)) {
return handlerMapper.controllerResponse(httpRequest);
handlerMapper.process(httpRequest, httpResponse);
return HttpResponseConverter.convert(httpResponse);
}
return String.join("\r\n",
"HTTP/1.1 200 OK ",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.apache.coyote.http11;

import java.util.Map;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import org.apache.coyote.http11.response.HttpResponse;
import org.apache.coyote.http11.response.HttpResponseHeaders;
import org.apache.coyote.http11.response.HttpStatusCode;
import org.apache.coyote.http11.response.ResponseBody;
import org.apache.coyote.http11.response.StatusLine;

public class HttpResponseConverter {

private static final String HTTP_VERSION = "HTTP/1.1";

private HttpResponseConverter() {
}

public static String convert(HttpResponse httpResponse) {
StringJoiner responseJoiner = new StringJoiner(System.lineSeparator());
responseJoiner.add(extractStatusLine(httpResponse.getStatusLine()));
responseJoiner.add(extractHeaders(httpResponse.getHeaders()));
responseJoiner.add(System.lineSeparator());
addBody(responseJoiner, httpResponse.getBody());
return responseJoiner.toString();
}

private static String extractStatusLine(StatusLine statusLine) {
HttpStatusCode httpStatusCode = statusLine.getHttpStatusCode();
return HTTP_VERSION + " " + httpStatusCode.getStatusCode() + " " + httpStatusCode.name();
}

private static String extractHeaders(HttpResponseHeaders headers) {
Map<String, String> headerValues = headers.getHeaders();
return headerValues.keySet()
.stream()
.map(headerName -> makeHeader(headerName, headerValues.get(headerName)))
.collect(Collectors.joining(System.lineSeparator()));
}

private static String makeHeader(String headerName, String value) {
return headerName + ": " + value;
}

private static void addBody(StringJoiner responseJoiner, ResponseBody body) {
Optional<String> responseBody = body.getValue();
if (responseBody.isPresent()) {
responseJoiner.add(responseBody.get());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ public String contentTypeOf(String resourcePath) {
File file = getFile(resourcePath);
String fileName = file.getName();
if (fileName.endsWith(".js")) {
return "Content-Type: text/javascript ";
return "text/javascript ";
}
if (fileName.endsWith(".css")) {
return "Content-Type: text/css;charset=utf-8";
return "text/css;charset=utf-8";
}
if (fileName.endsWith(".html")) {
return "Content-Type: text/html;charset=utf-8 ";
return "text/html;charset=utf-8 ";
}
return "Content-Type: text/plain";
return "text/plain";
}

private File getFile(String resourcePath) {
Expand All @@ -61,7 +61,7 @@ public String staticResourceResponse(String resourcePath) {
String responseBody = resourceBodyOf(resourcePath);
return String.join("\r\n",
"HTTP/1.1 200 OK ",
contentTypeOf(resourcePath),
"Content-Type: " + contentTypeOf(resourcePath),
"Content-Length: " + responseBody.getBytes().length + " ",
"",
responseBody);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.apache.coyote.http11.controller;

import org.apache.coyote.http11.request.HttpMethod;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;
import org.apache.coyote.http11.response.ResponseEntity;

public abstract class AbstractController implements Controller {


@Override
public void service(HttpRequest httpRequest, HttpResponse httpResponse) {
if (httpRequest.isRequestOf(HttpMethod.GET)) {
doGet(httpRequest, httpResponse);
return;
}
if (httpRequest.isRequestOf(HttpMethod.POST)) {
doPost(httpRequest, httpResponse);
return;
}
if (httpRequest.isRequestOf(HttpMethod.PUT)) {
doPut(httpRequest, httpResponse);
return;
}
if (httpRequest.isRequestOf(HttpMethod.DELETE)) {
doDelete(httpRequest, httpResponse);
return;
}
throw new UnsupportedOperationException(
"해당 HttpMethod 는 아직 지원하지 않습니다." + httpRequest.getRequestLine().getMethod());
}

protected void doGet(HttpRequest httpRequest, HttpResponse httpResponse) {
httpResponse.responseFrom(ResponseEntity.notAllowed());
}

protected void doPost(HttpRequest httpRequest, HttpResponse httpResponse) {
httpResponse.responseFrom(ResponseEntity.notAllowed());
}

protected void doPut(HttpRequest httpRequest, HttpResponse httpResponse) {
httpResponse.responseFrom(ResponseEntity.notAllowed());
}

protected void doDelete(HttpRequest httpRequest, HttpResponse httpResponse) {
httpResponse.responseFrom(ResponseEntity.notAllowed());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@

public interface Controller {

HttpResponse<? extends Object> handle(HttpRequest httpRequest);
void service(HttpRequest httpRequest, HttpResponse httpResponse);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,26 @@

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import org.apache.coyote.http11.ResourceProvider;
import org.apache.coyote.http11.request.HttpMethod;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;
import org.apache.coyote.http11.response.HttpStatusCode;
import org.apache.coyote.http11.service.LoginService;

public class HandlerMapper {

private final Map<Mapper, Controller> controllerByMapper = new HashMap<>();
private final ResourceProvider resourceProvider;

public HandlerMapper(ResourceProvider resourceProvider) {
public HandlerMapper() {
enrollHandler();
this.resourceProvider = resourceProvider;
}

private void enrollHandler() {
controllerByMapper.put(
request -> "/login".equals(request.getRequestLine().getPath()) &&
HttpMethod.POST.equals(request.getRequestLine().getMethod()),
request -> "/login".equals(request.getRequestLine().getPath()),
new LoginController(new LoginService()));

controllerByMapper.put(
request -> "/register".equals(request.getRequestLine().getPath()) &&
HttpMethod.POST.equals(request.getRequestLine().getMethod()),
request -> "/register".equals(request.getRequestLine().getPath()),
new SignUpController(new LoginService()));

controllerByMapper.put(
request -> "/login".equals(request.getRequestLine().getPath()) &&
HttpMethod.GET.equals(request.getRequestLine().getMethod()),
new LoginViewController());

controllerByMapper.put(
request -> "/register".equals(request.getRequestLine().getPath()) &&
HttpMethod.GET.equals(request.getRequestLine().getMethod()),
new SignUpViewController());
}

public boolean haveAvailableHandler(HttpRequest httpRequest) {
Expand All @@ -57,65 +37,9 @@ private Controller getHandler(HttpRequest httpRequest) {
return controllerByMapper.get(mapper);
}

public String controllerResponse(HttpRequest httpRequest) {
public void process(HttpRequest httpRequest, HttpResponse httpResponse) {
Controller handler = getHandler(httpRequest);
HttpResponse<Object> httpResponse = (HttpResponse<Object>) handler.handle(httpRequest);
return makeResponse(httpResponse);
}

private String makeResponse(HttpResponse<Object> httpResponse) {
StringBuilder response = new StringBuilder();
response.append(requestLine(httpResponse));
Optional<String> body = bodyOf(httpResponse);
if (body.isPresent()) {
return response.append(responseWithBody(httpResponse, body.get())).toString();
}
String str = responseWithoutBody(httpResponse);
response.append("\r\n");
return response.append(str).toString();
}


private String requestLine(HttpResponse<Object> httpResponse) {
HttpStatusCode httpStatusCode = HttpStatusCode.of(httpResponse.getStatusCode());
return "HTTP/1.1 " + httpStatusCode.getStatusCode() + " " + httpStatusCode.name() + " ";
}

private Optional<String> bodyOf(HttpResponse<Object> httpResponse) {
if (httpResponse.isViewResponse()) {
return Optional.of(resourceProvider.resourceBodyOf(httpResponse.getViewPath()));
}
return Optional.empty();
}

private String responseWithBody(HttpResponse<Object> httpResponse, String body) {
Map<String, String> headers = httpResponse.getHeaders();
StringJoiner stringJoiner = new StringJoiner("\r\n");
stringJoiner.add(headerResponse(headers));
stringJoiner.add(resourceProvider.contentTypeOf(httpResponse.getViewPath()));
stringJoiner.add("Content-Length: " + body.getBytes().length + " ");
stringJoiner.add("");
stringJoiner.add(body);
return stringJoiner.toString();
}

private String responseWithoutBody(HttpResponse<Object> httpResponse) {
Map<String, String> headers = httpResponse.getHeaders();
StringJoiner stringJoiner = new StringJoiner("\r\n");
stringJoiner.add(headerResponse(headers));
stringJoiner.add("");
return stringJoiner.toString();
}

private String headerResponse(Map<String, String> headers) {
return headers.keySet()
.stream()
.map(headerName -> makeHeader(headerName, headers.get(headerName)))
.collect(Collectors.joining("\r\n"));
}

private String makeHeader(String headerName, String value) {
return headerName + ": " + value;
handler.service(httpRequest, httpResponse);
}

@FunctionalInterface
Expand Down
Loading