Skip to content

Commit

Permalink
[톰캣 구현하기 - 3, 4단계] 푸우(백승준) 미션 제출합니다. (#446)
Browse files Browse the repository at this point in the history
  • Loading branch information
BGuga authored Sep 14, 2023
1 parent e5a610c commit de5105e
Show file tree
Hide file tree
Showing 25 changed files with 710 additions and 277 deletions.
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

0 comments on commit de5105e

Please sign in to comment.