3. Filter > Controller, Css

배고픈 징징이 ㅣ 2023. 2. 6. 17:21

1. 설명

Css파일을 가져오는 로직을 만들기 전에, Css는 CssController에서 내려주게끔 개발할 것이다.

그러기위해 Controller를 먼저 개발한다.

Controller 구현을 위해, ControllerFinder와 ControllerProfile을 만든다.

ControllerFinder는 패키지내에 있는 Controller 클래스들을 찾아주고,

ControllerProfile은 Request로 넘어온 파라미터들을 /[Controller]/[Method]/[Parameter] 형식으로 분석해준다.

 

여기서 요청은 "/css/all" 이다

 

2. ControllerFinder

모든 Controller들을 찾아서 controllers라는 Map에 담아준다.

class ControllerFinder {
    static final ControllerFinder INSTANCE = new ControllerFinder();
    private Map<String, Class<?>> controllers = new HashMap<>();

    private ControllerFinder() {
        File[] files = Option.ROOT.getFile("/WEB-INF/classes/com/simms").listFiles();
        for (File file : files) {
            loadControllers(file);
        }
    }

    private void loadControllers(File file) {
        String directoryName = null;

        if (!file.isDirectory()) return;

        if (!file.getName().equals("controller")) {
            //controller 디렉토리가 아니라 같은 레벨의 디렉토리인 경우(/user/controller)
            //directoryName을 user.controller로 명명
            //파라미터로 받은 file을 file경로안의 controller 디렉토리로 바꿈 
            directoryName = file.getName() + ".controller";
            file = new File(file, "controller");
        } else {
            //controller 디렉토리일 경우, 그냥 controller로 명명
            //file이 정상적으로 controller 디렉토리를 보고있음으로 file 생략
            directoryName = "controller";
        }

        if (!file.exists()) return;

        //file안에는 각각의 controller 디렉토리가 들어있다.
        for(File each : file.listFiles()) {
            if (each.isFile() && each.getName().endsWith("Controller.class")) {
                try {
                    String className = String.format("com.simms.%s.%s", directoryName, each.getName().replace(".class", ""));
                    controllers.put(
                        each.getName().replace("Controller.class", "").toLowerCase(),
                        Class.forName(className)
                    );
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    Object find(ControllerProfile profile) {
        if(controllers.containsKey(profile.controllerName)) {
            try {
                return controllers.get(profile.controllerName).newInstance();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return null;
    }
}

 

3. ControllerProfile

Request에서 규칙대로 controllerName과 methodName을 분리하여 가져온다.

class ControllerProfile {
    int index = 3;
    String[] split;
    String controllerName;
    String methodName;

    ControllerProfile(String servletPath) {
        split = camelCaseConverter(servletPath).split("/");

        if(split.length == 2) {
            controllerName = split[1];
            methodName = split[1];
        } else {
            controllerName = split[1];
            methodName = split[2];
        }
    }

    String next() {
        if (split.length < index) {
            return null;
        }

        return split[index++];
    }

    int nextInt() {
        String next = next();
        System.out.println("next = " + next);

        if (next != null) {
            return Integer.parseInt(next);
        } else {
            return -1;
        }
    }

    String camelCaseConverter(String string){
        Matcher matcher = Pattern.compile("[_|-](\\w)").matcher(string);
        StringBuffer stringBuffer = new StringBuffer();
        while (matcher.find()){
            matcher.appendReplacement(stringBuffer, matcher.group(1).toUpperCase());
        }

        return matcher.appendTail(stringBuffer).toString();
    }
}

 

4. FrontFilter

Controller의 Response를 넘겨주기위해 responseHandlerMap을 만들어주고,

Filter의 init에서 각각의 Response들을 responseHandlerMap에 넣어 준다.

각각의 Response들은 response.setContentType과 response.getWriter.println()을 가진다.

responseHandlerMap은 로직의 끝에서 Controller의 Response와 맞는 ContentType을 가져올때 쓰인다.

Filter는 controllers와 profile을 비교하여 요청에 맞는 Controller와 Method를 찾아 요청을 수행한다.

 

FrontFilter.java

private Map<Class<?>, ResponseHandler> responseHandlerMap = new HashMap<>();

@Override
public void init(FilterConfig filterConfig) {
    responseHandlerMap.put(String.class, new StringResponseHandler());
    responseHandlerMap.put(Text.class, new TextResponseHandler());
    responseHandlerMap.put(JObject.class, new JsonResponseHandler());
    responseHandlerMap.put(JArray.class, new JsonResponseHandler());
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    HttpServletResponse response = (HttpServletResponse) servletResponse;
    response.setCharacterEncoding("UTF-8");
    String servletPath = request.getServletPath();

    ...(Js 로직)

    int index = servletPath.lastIndexOf(".");
    //servletPath에 . 이 있다면 제외
    if(index != -1) { 
        filterChain.doFilter(servletRequest, servletResponse);
    } else {
        ControllerFinder controllerFinder = ControllerFinder.INSTANCE;
        ControllerProfile profile = new ControllerProfile(servletPath);
        //Controller들과 profile.controllerName을 비교하여 요청과 일치하는 Controller를 가져온다.
        Object controller = controllerFinder.find(profile); 

        if (controller == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
        } else {
            Method[] methods = controller.getClass().getMethods();
            for (Method method : methods) {
            	//Controller의 Method들과 profile.methodName을 비교하여 일치하는 Method를 가져온다.
                Method method = getMethod(controller, profile);
                
                if(method == null) response.sendError(HttpServletResponse.SC_NOT_FOUND);
                else run(response, controller, method);
            }
        }
    }
}

private Method getMethod(Object controller, ControllerProfile profile) {
    Method[] methods = controller.getClass().getMethods();
    for (Method method : methods) {
        if (method.getName().equals(profile.methodName)) return method;
    }

    return null;
}

private boolean run(HttpServletResponse response, Object controller, Method method){
    try{
        //찾은 Method의 메소드(controller)를 실행
        Object result = method.invoke(controller, parameters);
        
        //실행된 메소드의 Response를 가지고 responseHandlerMap과 매핑하여 Response에 맞는 contentType을 받아온다.
        if(result != null) responseHandlerMap.get(result.getClass()).handle(response, result);
    }catch(Throwable e){
        e.printStackTrace();
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }
}

 

StringResponseHandler.java

public class StringResponseHandler implements ResponseHandler{
    @Override
    public void handle(HttpServletResponse response, Object object) throws IOException {
        response.setContentType("text/plain; charset=utf-8;");
        response.getWriter().println(object);
    }
}

 

TextResponseHandler.java

public class TextResponseHandler implements ResponseHandler{
    @Override
    public void handle(HttpServletResponse response, Object object) throws Exception {
        Text text = (Text) object;
        response.setContentType(text.type.contentType);
        response.getWriter().println(text.content);
    }
}

 

5. CssController

/ROOT/css 디렉토리 안에있는 모든 Css파일들을 StringBuilder에 @import문으로 이어붙인다.

이로써 자동으로 모든 Css들을 불러올 수 있게 되었다.

 

CssController.java

public class CssController {
    Text text = new Text(TextType.CSS);
    @Access(isPublic = true)
    public Text all() {
        File[] csses = Option.ROOT.getFile("css").listFiles();
        getCss(csses);

        return text;
    }

    public void getCss(File[] csses){
        for (File css : csses) {
            if (css.getName().endsWith(".css")){
                text.write("@import url(/css/%s/%s);", css.getParentFile().getName(), css.getName());
            }else if(css.isDirectory()){
                getCss(css.listFiles());
            }
        }
    }
}

 

Text.java

public class Text {
    TextType type;
    StringBuilder content = new StringBuilder();

    public Text(TextType type) {
        this.type = type;
    }

    public void write(String format, Object... args) {
        content.append(String.format(format, args));
    }
}
반응형

'Java > - Pure Java Project' 카테고리의 다른 글

6. Annotation  (0) 2023.02.09
5. JNDI : DB Connect  (0) 2023.02.08
4. Router (Dynamic Import & Create Class)  (0) 2023.02.07
2. Filter > Js  (0) 2023.02.06
1. 시작  (0) 2023.02.06