Skip to content

Commit

Permalink
[1 -2단계 - Tomcat 구현하기] 카피(김상혁) 미션 제출합니다 (#536)
Browse files Browse the repository at this point in the history
전체적으로 잘 해주셨습니다 👍
수정 필요한 부분은 다음단계 진행하면서 고쳐주세요
404나 500 같은 페이지도 띄워주시면 좋을것 같습니다.
Http11Processor 리팩토링 기대할게요!
  • Loading branch information
tkdgur0906 authored Sep 9, 2024
1 parent 0b698a2 commit e622b01
Show file tree
Hide file tree
Showing 17 changed files with 367 additions and 44 deletions.
1 change: 0 additions & 1 deletion study/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'ch.qos.logback:logback-classic:1.5.7'
implementation 'org.apache.commons:commons-lang3:3.14.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.4.1'
Expand Down
7 changes: 3 additions & 4 deletions study/src/main/java/cache/com/example/GreetingController.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package cache.com.example;

import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import jakarta.servlet.http.HttpServletResponse;

@Controller
public class GreetingController {

@GetMapping("/")
public String index() {
return "index";
return "index.html";
}

/**
Expand All @@ -30,7 +29,7 @@ public String cacheControl(final HttpServletResponse response) {

@GetMapping("/etag")
public String etag() {
return "index";
return "index.html";
}

@GetMapping("/resource-versioning")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
package cache.com.example.cachecontrol;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.WebContentInterceptor;

@Configuration
public class CacheWebConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(final InterceptorRegistry registry) {
WebContentInterceptor webContentInterceptor = new WebContentInterceptor();
webContentInterceptor.addCacheMapping(
CacheControl.noCache().cachePrivate(),
"/**"
);
registry.addInterceptor(webContentInterceptor);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
package cache.com.example.etag;

import cache.com.example.version.ResourceVersion;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.ShallowEtagHeaderFilter;

import static cache.com.example.version.CacheBustingWebConfig.PREFIX_STATIC_RESOURCES;

@Configuration
public class EtagFilterConfiguration {

// @Bean
// public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() {
// return null;
// }
private final ResourceVersion resourceVersion;

public EtagFilterConfiguration(ResourceVersion resourceVersion) {
this.resourceVersion = resourceVersion;
}

@Bean
public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() {
FilterRegistrationBean<ShallowEtagHeaderFilter> filterFilterRegistrationBean
= new FilterRegistrationBean<>(new ShallowEtagHeaderFilter());
filterFilterRegistrationBean.addUrlPatterns("/etag");
filterFilterRegistrationBean.addUrlPatterns(PREFIX_STATIC_RESOURCES + "/" + resourceVersion.getVersion() + "/*");
return filterFilterRegistrationBean;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.time.Duration;

@Configuration
public class CacheBustingWebConfig implements WebMvcConfigurer {
Expand All @@ -20,6 +22,7 @@ public CacheBustingWebConfig(ResourceVersion version) {
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**")
.addResourceLocations("classpath:/static/");
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(Duration.ofDays(365)).cachePublic());
}
}
4 changes: 4 additions & 0 deletions study/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ server:
accept-count: 1
max-connections: 1
threads:
min-spare: 2
max: 2
compression:
enabled: true
min-response-size: 10
File renamed without changes.
35 changes: 14 additions & 21 deletions tomcat/src/main/java/org/apache/catalina/Manager.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
package org.apache.catalina;

import jakarta.servlet.http.HttpSession;
import org.apache.coyote.http11.Session;

import java.io.IOException;

/**
* A <b>Manager</b> manages the pool of Sessions that are associated with a
* particular Container. Different Manager implementations may support
* value-added features such as the persistent storage of session data,
* as well as migrating sessions for distributable web applications.
* A <b>Manager</b> manages the pool of Sessions that are associated with a particular Container. Different Manager
* implementations may support value-added features such as the persistent storage of session data, as well as migrating
* sessions for distributable web applications.
* <p>
* In order for a <code>Manager</code> implementation to successfully operate
* with a <code>Context</code> implementation that implements reloading, it
* must obey the following constraints:
* In order for a <code>Manager</code> implementation to successfully operate with a <code>Context</code> implementation
* that implements reloading, it must obey the following constraints:
* <ul>
* <li>Must implement <code>Lifecycle</code> so that the Context can indicate
* that a restart is required.
Expand All @@ -29,28 +27,23 @@ 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
* specified session id (if any); otherwise return <code>null</code>.
* Return the active Session, associated with this Manager, with the specified session id (if any); otherwise return
* <code>null</code>.
*
* @param id The session id for the session to be returned
*
* @exception IllegalStateException if a new session cannot be
* instantiated for any reason
* @exception IOException if an input/output error occurs while
* processing this request
*
* @return the request session or {@code null} if a session with the
* requested ID could not be found
* @return the request session or {@code null} if a session with the requested ID could not be found
* @throws IllegalStateException if a new session cannot be instantiated for any reason
* @throws IOException if an input/output error occurs while processing this request
*/
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);
}
96 changes: 86 additions & 10 deletions tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package org.apache.coyote.http11;

import com.techcourse.db.InMemoryUserRepository;
import com.techcourse.exception.UncheckedServletException;
import com.techcourse.model.User;
import org.apache.coyote.Processor;
import org.apache.coyote.http11.controller.ResourceLoader;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.Socket;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.UUID;

public class Http11Processor implements Runnable, Processor {

Expand All @@ -29,19 +36,88 @@ public void process(final Socket connection) {
try (final var inputStream = connection.getInputStream();
final var outputStream = connection.getOutputStream()) {

final var responseBody = "Hello world!";
HttpRequest httpRequest = HttpRequest.from(inputStream);

String version = httpRequest.getVersion();
Map<String, String> httpRequestHeaders = httpRequest.getHeaders();
String httpMethod = httpRequest.getHttpMethod();
String page = httpRequest.getPath();
HttpResponse httpResponse;

String responseBody;
if (page.equals("/")) {
responseBody = "Hello world!";
httpResponse = HttpResponse.of(version, 200, "text/html", responseBody);
} else if (page.startsWith("/login") && httpMethod.equals("POST")) {
String requestBody = httpRequest.getBody();
String account = requestBody.split("&")[0].split("=")[1];
String password = requestBody.split("&")[1].split("=")[1];

User user = InMemoryUserRepository.findByAccount(account).get();

if (user.checkPassword(password)) {
log.info("user : {}", user);
SessionManager sessionManager = new SessionManager();
UUID jSessionId = UUID.randomUUID();
Session session = new Session(jSessionId.toString());
session.setAttribute("user", user);
sessionManager.add(session);

responseBody = new String(ResourceLoader.loadResource("static/index.html"));
httpResponse = HttpResponse.of(version, 200, "text/html", responseBody);
httpResponse.addHeader("Location", "/index.html");
httpResponse.addHeader("Set-Cookie", "JSESSIONID=" + jSessionId);
} else {
responseBody = new String(ResourceLoader.loadResource("static/401.html"));
httpResponse = HttpResponse.of(version, 200, "text/html", responseBody);
httpResponse.addHeader("Location", "/401.html");
}
} else if (page.startsWith("/login") && httpMethod.equals("GET")) {
SessionManager sessionManager = new SessionManager();

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);
if (httpRequestHeaders.containsKey("Cookie") &&
httpRequestHeaders.get("Cookie").startsWith("JSESSIONID=")) {
String jSessionId = httpRequestHeaders.get("Cookie").split("=")[1];
Session session = sessionManager.findSession(jSessionId);

outputStream.write(response.getBytes());
outputStream.flush();
if (session != null && session.getAttribute("user") != null) {
responseBody = new String(ResourceLoader.loadResource("static/index.html"));
httpResponse = HttpResponse.of(version, 200, "text/html", responseBody);
} else {
responseBody = new String(ResourceLoader.loadResource("static" + page + ".html"));
httpResponse = HttpResponse.of(version, 200, "text/html", responseBody);
}
} else {
responseBody = new String(ResourceLoader.loadResource("static" + page + ".html"));
httpResponse = HttpResponse.of(version, 200, "text/html", responseBody);
}
} else if (page.equals("/register") && httpMethod.equals("POST")) {
String requestBody = httpRequest.getBody();
String account = requestBody.split("&")[0].split("=")[1];
String email = requestBody.split("&")[1].split("=")[1];
String password = requestBody.split("&")[2].split("=")[1];
InMemoryUserRepository.save(new User(account, email, password));
responseBody = new String(ResourceLoader.loadResource("static/index.html"));
httpResponse = HttpResponse.of(version, 200, "text/html", responseBody);
httpResponse.addHeader("Location", "/index.html");
} else if (page.startsWith("/css/")) {
responseBody = new String(ResourceLoader.loadResource("static" + page));
httpResponse = HttpResponse.of(version, 200, "text/css", responseBody);
} else if (page.contains(".js")) {
responseBody = new String(ResourceLoader.loadResource("static" + page));
httpResponse = HttpResponse.of(version, 200, "text/javascript", responseBody);
} else if (page.endsWith(".html")) {
responseBody = new String(ResourceLoader.loadResource("static" + page));
httpResponse = HttpResponse.of(version, 200, "text/html", responseBody);
} else {
responseBody = new String(ResourceLoader.loadResource("static" + page + ".html"));
httpResponse = HttpResponse.of(version, 200, "text/html", responseBody);
}
httpResponse.send(outputStream);
} catch (IOException | UncheckedServletException e) {
log.error(e.getMessage(), e);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
30 changes: 30 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/http11/Session.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.apache.coyote.http11;

import java.util.HashMap;
import java.util.Map;

public class Session {

private static final Map<String, Object> values = new HashMap<>();
private final String id;

public Session(final String id) {
this.id = id;
}

public String getId() {
return id;
}

public Object getAttribute(final String name) {
return values.get(name);
}

public void setAttribute(final String name, final Object value) {
values.put(name, value);
}

public void removeAttribute(final String name) {
values.remove(name);
}
}
29 changes: 29 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/http11/SessionManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.apache.coyote.http11;

import org.apache.catalina.Manager;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class SessionManager implements Manager {

private static final Map<String, Session> SESSIONS = new HashMap<>();

public SessionManager() {
}

@Override
public void add(Session session) {
SESSIONS.put(session.getId(), new Session(session.getId()));
}

@Override
public Session findSession(String id) throws IOException {
return SESSIONS.get(id);
}

@Override
public void remove(Session session) {
SESSIONS.remove(session.getId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.apache.coyote.http11.controller;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;

public class ResourceLoader {

public static byte[] loadResource(String resourceName) throws URISyntaxException, IOException {
URL url = ResourceLoader.class.getClassLoader().getResource(resourceName);
if (url == null) {
throw new IllegalArgumentException("존재하지 않는 리소스 입니다." + resourceName);
}

Path path = Path.of(url.toURI());
return Files.readAllBytes(path);
}
}
Loading

0 comments on commit e622b01

Please sign in to comment.