Skip to content
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

[MVC 구현하기 - 3단계] 하마드(이건회) 미션 제출합니다. #548

Merged
merged 11 commits into from
Sep 26, 2023
Merged
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./gradlew build sonar --info -x :study:build
run: ./gradlew clean build codeCoverageReport --info -x :study:build
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,5 @@ Temporary Items

tomcat.*
tomcat.*/**

**/WEB-INF/classes/**
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dependencies {
implementation "org.apache.tomcat.embed:tomcat-embed-jasper:10.1.13"
implementation "ch.qos.logback:logback-classic:1.2.12"
implementation "org.apache.commons:commons-lang3:3.13.0"
implementation "org.reflections:reflections:0.10.2"

testImplementation "org.assertj:assertj-core:3.24.2"
testImplementation "org.junit.jupiter:junit-jupiter-api:5.7.2"
Expand Down
24 changes: 0 additions & 24 deletions app/src/main/java/com/techcourse/AppWebApplicationInitializer.java

This file was deleted.

33 changes: 16 additions & 17 deletions app/src/main/java/com/techcourse/Application.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package com.techcourse;

import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.stream.Stream;

public class Application {
Expand All @@ -16,22 +14,11 @@ public class Application {

public static void main(final String[] args) throws Exception {
final int port = defaultPortIfNull(args);

final var tomcat = new Tomcat();
tomcat.setConnector(createConnector(port));
final var docBase = new File("app/src/main/webapp/").getAbsolutePath();
tomcat.addWebapp("", docBase);
log.info("configuring app with basedir: {}", docBase);
final var tomcat = new TomcatStarter(port);
log.info("configuring app with basedir: {}", TomcatStarter.WEBAPP_DIR_LOCATION);

tomcat.start();
tomcat.getServer().await();
}

private static Connector createConnector(final int port) {
final var connector = new Connector();
connector.setPort(port);
connector.setProperty("bindOnInit", "false");
return connector;
stop(tomcat);
}

private static int defaultPortIfNull(final String[] args) {
Expand All @@ -40,4 +27,16 @@ private static int defaultPortIfNull(final String[] args) {
.map(Integer::parseInt)
.orElse(DEFAULT_PORT);
}

private static void stop(final TomcatStarter tomcat) {
try {
// make the application wait until we press any key.
System.in.read();
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
log.info("web server stop.");
tomcat.stop();
}
}
}
76 changes: 76 additions & 0 deletions app/src/main/java/com/techcourse/DispatcherServlet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.techcourse;

import com.techcourse.handlerMapper.ManualHandlerMapping;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.View;
import webmvc.org.springframework.web.servlet.mvc.tobe.handlerMapper.AnnotationHandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.tobe.handlerMapper.AnnotationHandlerMapping;
import webmvc.org.springframework.web.servlet.mvc.tobe.handlerMapper.HandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.tobe.handlerMapper.HandlerMapping;
import webmvc.org.springframework.web.servlet.mvc.tobe.handlerMapper.ManualHandlerAdapter;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class DispatcherServlet extends HttpServlet {

private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class);
private List<HandlerMapping> handlerMappings = new ArrayList<>();
private List<HandlerAdapter> handlerAdapters = new ArrayList<>();

public DispatcherServlet() {
}

@Override
public void init() {
initHandlerMapping();
initHandlerAdapter();
}

private void initHandlerMapping() {
handlerMappings.add(new ManualHandlerMapping());
handlerMappings.add(new AnnotationHandlerMapping("com.techcourse.controller"));
for (HandlerMapping handlerMapping : handlerMappings) {
handlerMapping.initialize();
}
}

private void initHandlerAdapter() {
handlerAdapters.add(new AnnotationHandlerAdapter());
handlerAdapters.add(new ManualHandlerAdapter());
}

@Override
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException {
final String requestURI = request.getRequestURI();
log.debug("Method : {}, Request URI : {}", request.getMethod(), requestURI);

try {
Object handler = handlerMappings.stream()
.map(handlerMapping -> handlerMapping.getHandler(request))
.filter(Objects::nonNull)
.findFirst()
.orElseThrow(() -> new RuntimeException());

HandlerAdapter handlerAdapter = handlerAdapters.stream()
.filter(adapter -> adapter.supports(handler))
.findAny()
.orElseThrow(() -> new RuntimeException());

ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);
View view = modelAndView.getView();
view.render(modelAndView.getModel(), request, response);
} catch (Throwable e) {
log.error("Exception : {}", e.getMessage(), e);
throw new ServletException(e.getMessage());
}
}
}
33 changes: 33 additions & 0 deletions app/src/main/java/com/techcourse/DispatcherServletInitializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.techcourse;

import jakarta.servlet.ServletContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import web.org.springframework.web.WebApplicationInitializer;

/**
* Base class for {@link WebApplicationInitializer}
* implementations that register a {@link DispatcherServlet} in the servlet context.
*/
public class DispatcherServletInitializer implements WebApplicationInitializer {

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

private static final String DEFAULT_SERVLET_NAME = "dispatcher";

@Override
public void onStartup(final ServletContext servletContext) {
final var dispatcherServlet = new DispatcherServlet();

final var registration = servletContext.addServlet(DEFAULT_SERVLET_NAME, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + DEFAULT_SERVLET_NAME + "'. " +
"Check if there is another servlet registered under the same name.");
}

registration.setLoadOnStartup(1);
registration.addMapping("/");

log.info("Start AppWebApplication Initializer");
}
}
83 changes: 83 additions & 0 deletions app/src/main/java/com/techcourse/TomcatStarter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.techcourse;

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Tomcat;
import org.apache.tomcat.util.scan.StandardJarScanner;

import java.io.File;

public class TomcatStarter {

public static final String WEBAPP_DIR_LOCATION = "app/src/main/webapp/";

private final Tomcat tomcat;

public TomcatStarter(final int port) {
this(WEBAPP_DIR_LOCATION, port);
}

public TomcatStarter(final String webappDirLocation, final int port) {
this.tomcat = new Tomcat();
tomcat.setConnector(createConnector(port));

final var docBase = new File(webappDirLocation).getAbsolutePath();
final var context = (StandardContext) tomcat.addWebapp("", docBase);
skipJarScan(context);
skipClearReferences(context);
}

public void start() {
try {
tomcat.start();
} catch (LifecycleException e) {
throw new UncheckedServletException(e);
}
}

public void stop() {
try {
tomcat.stop();
tomcat.destroy();
} catch (LifecycleException e) {
throw new UncheckedServletException(e);
}
}

private Connector createConnector(final int port) {
final var connector = new Connector();
connector.setPort(port);
return connector;
}

private void skipJarScan(final Context context) {
final var jarScanner = (StandardJarScanner) context.getJarScanner();
jarScanner.setScanClassPath(false);
}

private void skipClearReferences(final StandardContext context) {
/**
* https://tomcat.apache.org/tomcat-10.1-doc/config/context.html
*
* setClearReferencesObjectStreamClassCaches 번역
* true인 경우 웹 응용 프로그램이 중지되면 Tomcat은 직렬화에 사용되는
* ObjectStreamClass 클래스에서 웹 응용 프로그램에 의해 로드된
* 클래스에 대한 SoftReference를 찾고 찾은 모든 SoftReference를 지웁니다.
* 이 기능은 리플렉션을 사용하여 SoftReference를 식별하므로 Java 9 이상에서
* 실행할 때 명령줄 옵션 -XaddExports:java.base/java.io=ALL-UNNAMED를 설정해야 합니다.
* 지정하지 않으면 기본값인 true가 사용됩니다.
*
* ObjectStreamClass와 관련된 메모리 누수는 Java 19 이상, Java 17.0.4 이상 및
* Java 11.0.16 이상에서 수정되었습니다.
* 수정 사항이 포함된 Java 버전에서 실행할 때 확인이 비활성화됩니다.
*
* Amazon Corretto-17.0.6은 경고 메시지가 나옴.
* 학습과 관련 없는 메시지가 나오지 않도록 관련 설정을 끈다.
*/
context.setClearReferencesObjectStreamClassCaches(false);
context.setClearReferencesRmiTargets(false);
context.setClearReferencesThreadLocals(false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.techcourse;

public class UncheckedServletException extends RuntimeException {

public UncheckedServletException(Exception e) {
super(e);
}
}
44 changes: 24 additions & 20 deletions app/src/main/java/com/techcourse/controller/LoginController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,41 @@

import com.techcourse.domain.User;
import com.techcourse.repository.InMemoryUserRepository;
import context.org.springframework.stereotype.Controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import nextstep.mvc.controller.asis.Controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import web.org.springframework.web.bind.annotation.RequestMapping;
import web.org.springframework.web.bind.annotation.RequestMethod;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.view.JspView;

public class LoginController implements Controller {
@Controller
public class LoginController {

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

@Override
public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception {
if (UserSession.isLoggedIn(req.getSession())) {
return "redirect:/index.jsp";
@RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView showPage(HttpServletRequest request, HttpServletResponse response) {
if (UserSession.isLoggedIn(request.getSession())) {
return new ModelAndView(new JspView("redirect:/index.jsp"));
}
return new ModelAndView(new JspView("/login.jsp"));
}

return InMemoryUserRepository.findByAccount(req.getParameter("account"))
.map(user -> {
log.info("User : {}", user);
return login(req, user);
})
.orElse("redirect:/401.jsp");
@RequestMapping(value = "/login", method = RequestMethod.POST)
public ModelAndView login(HttpServletRequest request, HttpServletResponse response) {
if (UserSession.isLoggedIn(request.getSession())) {
return new ModelAndView(new JspView("redirect:/index.jsp"));
}
return InMemoryUserRepository.findByAccount(request.getParameter("account"))
.map(user -> login(request, user))
.orElse(new ModelAndView(new JspView("redirect:/401.jsp")));
}

private String login(final HttpServletRequest request, final User user) {
private ModelAndView login(final HttpServletRequest request, final User user) {
if (user.checkPassword(request.getParameter("password"))) {
final var session = request.getSession();
session.setAttribute(UserSession.SESSION_KEY, user);
return "redirect:/index.jsp";
} else {
return "redirect:/401.jsp";
return new ModelAndView(new JspView("redirect:/index.jsp"));
}
return new ModelAndView(new JspView("redirect:/401.jsp"));
}
}

This file was deleted.

Loading