저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의

ip-filter 구현 이야기 발표 자료:



관련 글:





Posted by 최범균 madvirus

댓글을 달아 주세요

ip-filter-core 모듈은 아래와 같이 Config 객체를 이용해서 차단/허용 목록을 설정한다. (ip-filter의 사용법은 https://github.com/madvirus/ip-filter/wiki/HOME_kr 문서를 참고하기 바란다.)


Config config = new Config();

config.setAllowFirst(true);

config.setDefaultAllow(false);

config.allow("1.2.3.4"); // 허용 IP 추가

config.allow("10.20.30.40");

config.deny("101.102.103.104"); // 차단 IP 추가


IpFilter ipFilter = IpFilters.create(config);


ipFilter.accept("1.2.3.4"); // true

ipFilter.accept("101.102.103.104"); // false


코드로 설정하는 것이 필요할 때가 있지만, 아래와 같은 문자열을 이용할 수 있다면 파일이나 운영툴 등에서 쉽게 설정할 수 있을 것이다.


# 주석

order allow,deny

default true

allow from 1.2.3.4

allow from 1.2.5.* # 뒤에 주석

allow from 201.202.203.10/64

deny from all


위 문자열을 읽어와서 Config 객체를 생성하려면 문자열을 알맞게 파싱해 주어야 하는데, 파싱을 어떻게 할까 고민하다가 최근에 학습하고 있는 스카라(Scala)의 콤비네이터 파서(Conbinator parser)를 사용해 보기로 했다. 스카라는 자체적으로 Context-free grammer 에 따른 파싱 기능을 제공하고 있기 때문에, ANTLR과 같은 별도의 파서 생성기를 사용하지 않아도 된다.


스카라를 이용한 문자열 파서


음,, 먼저 스카라에 익숙하지 않은 분들은 아래 코드가 잘 이해되지 않을 것이다. 그래도 그냥 읽어보기 바란다. 설정 문자열을 파싱하기 위해 작성한 스카라 코드는 아래와 같다. (아래 코드는 order나 default를 여러 줄 입력해도 에러를 발생시키진 않는데, order나 default를 여러번 설정할 경우 오류를 발생시킬지의 여부는 고민 중에 있다.)


class Conf extends JavaTokenParsers {

  override val whiteSpace = """[ \t]+""".r


  def conf: Parser[Config] = repsep(confPart, eol) ^^ (

    x => {

      val config = new Config

      x.foreach(part =>

        part match {

          case ("order", firstAllow: Boolean) => config.setAllowFirst(firstAllow)

          case ("default", defaultAllow: Boolean) => config.setDefaultAllow(defaultAllow)

          case ("allow", ip: String) => config.allow(ip)

          case ("deny", ip: String) => config.deny(ip)

          case _ =>

        })

      config

    }

    )


  def confPart: Parser[Any] = commentPart | orderPart | defaultPart | allowOrDenyPart | emptyLine


  def commentPart: Parser[String] = """#(.*)""".r ^^ (x => x)


  def orderPart: Parser[Tuple2[String, Boolean]] =

    "order" ~ orderValue ~ opt(commentPart) ^^ (x => ("order", x._1._2))


  def orderValue: Parser[Boolean] = {

    "allow" ~ "," ~ "deny" ^^ (x => true) |

      "deny" ~ "," ~ "allow" ^^ (x => false)

  }


  def defaultPart: Parser[Tuple2[String, Boolean]] = 

      "default" ~ booleanValue ^^ (x => ("default", x._2))


  def booleanValue: Parser[Boolean] = "true" ^^ (x => true) | "false" ^^ (x => false)


  def allowOrDenyPart: Parser[Tuple2[String, String]] =

    allow ^^ (x => ("allow", x)) | deny ^^ (x => ("deny", x))


  def allow: Parser[String] = "allow" ~ "from" ~ ipPattern ~ opt(commentPart) ^^ (x => x._1._2)


  def deny: Parser[String] = "deny" ~ "from" ~ ipPattern ~ opt(commentPart) ^^ (x => x._1._2)


  def ipPattern: Parser[String] =

    "all" ^^ (x => "*") |

      """(\d+\.){1,3}(\*)""".r ^^ (x => x) |

      """(\d+\.\d+\.\d+\.\d+\/\d+)""".r ^^ (x => x) |

      """(\d+\.\d+\.\d+\.\d+)""".r ^^ (x => x)


  def emptyLine: Parser[String] = ""


  def eol: Parser[String] = """(\r?\n)+""".r

}


class ConfParser extends Conf {

  def parse(confText: String): Config = {

    val result = parseAll(conf, confText)

    if (result.successful)

      result.get

    else

      throw new ConfParserException(result.toString)

  }

}


Conf 클래스는 문자열을 파싱해서 Config 객체를 생성해주는 기능을 제공하며, ConfParser 클래스는 외부에 파싱 기능을 제공하는 parse() 메서드를 제공한다.


자바 코드에서는 ConfParser 클래스를 이용해서 문자열로부터 Config 객체를 생성할 수 있다.


public class UsingConfParserTestInJava {

    @Test

    public void useConfParser() {

        String confValue =

                "order deny,allow\n" +

                        "allow from 1.2.3.4\n" +

                        "deny from 10.20.30.40\n" +

                        "allow from 101.102.103.*\n" +

                        "allow from 201.202.203.10/64";


        Config config = new ConfParser().parse(confValue);

        assertFalse(config.isAllowFirst());

        assertEquals(config.getAllowList().size(), 3);

    }


응용


문자열로부터 Config 객체를 생성하는 파서를 만들었으니, 이제 파일이나 다른 곳에서 설정 데이터를 읽어와 Config 객체를 만들 수 있게 되었다. 실제 응용은 웹 어플리케이션에서 특정 IP 차단을 위해 작성한 ip-filter-web-simple 에서 사용하였다. (관련 코드는 https://github.com/madvirus/ip-filter/tree/master/ip-filter-web-simple 에서 확인 가능)


package org.chimi.ipfilter.web.impl;


import org.chimi.ipfilter.Config;

import org.chimi.ipfilter.parser.ConfParser;


import java.io.FileReader;

import java.io.IOException;


public class FileConfigFactory extends ConfigFactory {


    @Override

    public Config create(String value) {

        return new ConfParser().parse(readFromFile(value));

    }


    private String readFromFile(String fileName) {

        try {

            return IOUtil.read(new FileReader(fileName));

        } catch (IOException e) {

            throw new ConfigFactoryException(e);

        }

    }


    @Override

    public boolean isReloadSupported() {

        return true;

    }


}



Posted by 최범균 madvirus

댓글을 달아 주세요

Scala 언어가 재미있지만 실제 프로젝트에 적용하려면 콘솔에서 컴파일하거나 실행해 볼 수는 없다. 실제 프로젝트에 Scala를 적용하려면 먼저 편리한 개발 환경을 구축해 주어야 한다. 필자의 경우 개발시 주로 이클립스와 메이븐을 사용하기 때문에, Scala를 이 환경에서 개발하기 위한 내용을 찾아보았으며, 각 내용들은 한 곳에 모아 보았다. (나중에 또 돌아다니면서 찾는 건 귀찮기에 이곳에 정리한다.)


이클립스와 메이븐(m2e 플러그인) 환경을 이용해서 Scala를 적용하려면 다음과 같은 준비를 하면 된다.

  • Scala IDE 설치
  • M2Eclipse Scala 플러그인 설치
  • Maven 프로젝트에 pom.xml 파일에 maven-scala-plugin 설정

Scala IDE 설치하기


먼저 Scala IDE를 설치한다. 이클립스 업데이트 사이트는 아래와 같다. 참고로 아래 주소는 Scala 2.9 버전 기준이다. (참고 사이트는 http://scala-ide.org/index.html 이다.)

  • http://download.scala-ide.org/releases-29/stable/site

M2Eclipse Scala 플러그인 설치


그 다음 할 작업은 Maven 프로젝트와 Scala IDE를 연결해 줄 플러그인을 설치해주는 것이다. 업데이트 주소는 아래와 같다. (참고 사이트는 https://github.com/sonatype/m2eclipse-scala 이다.)


  • http://alchim31.free.fr/m2e-scala/update-site/

pom.xml 파일에 maven-scala-plugin 설정하기


이제 남은 작업은 이클립스에서 실행할 메이븐 프로젝트의 pom.xml 파일에 maven-scala-plugin 설정을 하는 것이다. pom.xml 파일의 설정 예는 https://github.com/sonatype/m2eclipse-scala 사이트에서 찾아볼 수 있다. pom.xml 파일을 알맞게 작성한 뒤에 Scala 소스 코드를 작성하면, 알맞게 컴파일되고, 실행해 볼 수 있고,  자바 코드에서 Scala가 생성한 클래스를 사용하는 등의 작업을 할 수 있다.


참고로, 그럼 내가 왜 Scala를 사용해보려고 하느냐? 여러 이유가 있겠지만 다음이 주요 이유다.

  • 언어 자체의 재미: 언어가 다소 복잡하지만 재미는 있다.
  • Combinator Parser: 뭔가 규칙에 기반해서 텍스트를 파싱해야 할 때, 언어 차원에서 Parser를 지원하기 때문에, ANTLR 등을 사용하지 않아도 쉽게 파싱할 수 있다.
  • DSL(Domain Specific Language): 위의 내용과 더불어 DSL을 만들기에 Ruby나 Groovy 등의 언어들 만큼 좋다.
  • 자바와의 호환성: JVM 기반으로 동작하며 자바와의 호환이 당연히 잘 된다.
  • 함수형 언어이고 Clousure 등을 지원하기에 테스트 코드에 적용하면 테스트 코드에서 발생하는 중복을 많이 줄일 수 있으므로, 그것만으로도 가치가 있다.



Posted by 최범균 madvirus

댓글을 달아 주세요