Web/Spring 2018. 5. 7. 19:47

Spring - Jsoup을 이용한 웹크롤링


채팅을 개발하던 도중에 간단히 챗봇 기능을 만들어보면 어떨까 하는 생각에 오늘 날씨를 물어보면 오늘 날씨에 대한 정보를 답장으로 보내주는 채팅을 구현하기 위해 Jsoup을 이용한 웹크롤링을 진행해보았습니다.




pom.xml



1
2
3
4
5
<dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.8.3</version>
</dependency>
cs





웹 크롤링 코드



1
2
3
4
5
String URL = "https://weather.naver.com/rgn/cityWetrMain.nhn";
Document doc = Jsoup.connect(URL).get();
Elements elem = doc.select(".tbl_weather tbody>tr:nth-child(1)");
String[] str = elem.text().split(" ");
Elements elem2=doc.select(".tbl_weather tbody>tr:nth-child(1) img");
cs


1. Document doc=Jsoup.connect(URL).get(); => 지정한 url의 html태그를 모두 가져옵니다. html태그를 문자열 형태로 다 가져옵니다.


2.Elements elem=doc.select("~"); => 지정한 url에서 가져온 html태그 중에 원하는 태그를 가져옵니다. 괄호 안에는 css 선택자를 지정하여 원하는 태그를 가져옵니다. 결과는 문자열로 html태그들을 모두 가져옵니다. 만약 태그들을 그대로 적용할 생각이라면 elem2를 jsp로 리턴하여 사용하면 됩니다.


3.String str=elem.text(); =>elem안에 담긴 html태그에서 문자열을 모두 가져오는 메소드입니다. 단순히 태그가 아니라 문자열들만 가져오고 싶다면 이메소드를 이용해서 문자열들을 가져오면 됩니다.


String str=elem.text( ); 가 가져온 텍스트

=>서울 경기 구름많음 기온 15.0℃ 강수확률 20% 구름조금 기온 25.0℃ 강수확률 10%


Elements elem=doc.select("~"); 가 가져온 텍스트

=>

<tr> 

 <th scope="row"> <a href="/rgn/cityWetrWarea.nhn?cityRgnCd=CT001000"> 서울<br> 경기 </a> </th> 

 <td> <p class="icon"><img src="https://ssl.pstatic.net/static/weather/images/w_icon/w_l21.gif" width="64" height="46" alt="구름많음"></p> 

  <ul class="text"> 

   <li class="nm">구름많음</li> 

   <li>기온 <span class="temp"><strong>15.0</strong>℃</span><br> 강수확률 <span class="rain"><strong>20</strong>%</span></li> 

  </ul> </td> 

 <td class="line"> <p class="icon"><img src="https://ssl.pstatic.net/static/weather/images/w_icon/w_l2.gif" width="64" height="46" alt="구름조금"></p> 

  <ul class="text"> 

   <li class="nm">구름조금</li> 

   <li>기온 <span class="temp"><strong>25.0</strong>℃</span><br> 강수확률 <span class="rain"><strong>10</strong>%</span></li> 

  </ul> </td> 

</tr>


Elements elem2=doc.select("~"); 가 가져온 텍스트

=>

<img src="https://ssl.pstatic.net/static/weather/images/w_icon/w_l21.gif" width="64" height="46" alt="구름많음">

<img src="https://ssl.pstatic.net/static/weather/images/w_icon/w_l2.gif" width="64" height="46" alt="구름조금">






결과 



텍스트들은 elem.text()를 이용하여 가져온 것들이고 사진은 elem2를 이용해 img태그를 그대로 이용한 것입니다.

posted by 여성게
:
Web/Spring 2018. 5. 6. 22:39

Spring AOP를 이용한 Xss 공격 방지


lucy-xss를 이용하여 간단하게 게시글이나 댓글에 XSS공격을 방지하는 기능을 구현하였습니다. 그런데 보통 홈페이지는 게시판과 댓글 기능이 하나만 들어가있는 경우는 드뭅니다. 그말은 즉, xss관련 코드가 2개 이상이 중복되어 구현됩니다. 그래서 spring AOP를 이용하여 XSS 관련 코드를 하나의 클래스 파일에 구현해 게시물,댓글 등의 코드에 중복되어 구현되지 않게 하였습니다.






pom.xml



1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- lucy xss  -->
<dependency>
    <groupId>com.navercorp.lucy</groupId>
    <artifactId>lucy-xss</artifactId>
    <version>1.6.3</version> 
</dependency>
 
<!-- aop weaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.8</version>
</dependency>
cs




lucy-xss-superset.xml




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="UTF-8"?>
 
<config xmlns="http://www.nhncorp.com/lucy-xss"
    extends="lucy-xss-default.xml">
 
    <elementRule>
        <element name="body" disable="true" />
        <element name="embed" disable="true" />
        <element name="iframe" disable="true" />
        <element name="meta" disable="true" />
        <element name="object" disable="true" />
        <element name="script" disable="true" />
        <element name="style" disable="true" />
    </elementRule>
    
    <attributeRule>
        <attribute name="data" base64Decoding="true">
            <notAllowedPattern><![CDATA[(?i:s\\*c\\*r\\*i\\*p\\*t)]]></notAllowedPattern>
            <notAllowedPattern><![CDATA[&[#\\%x]+[\da-fA-F][\da-fA-F]+]]></notAllowedPattern>
        </attribute>
        <attribute name="src" base64Decoding="true">
            <notAllowedPattern><![CDATA[(?i:s\\*c\\*r\\*i\\*p\\*t)]]></notAllowedPattern>
            <notAllowedPattern><![CDATA[&[#\\%x]+[\da-fA-F][\da-fA-F]+]]></notAllowedPattern>
        </attribute>
        <attribute name="style">
            <notAllowedPattern><![CDATA[(?i:e\\*x\\*p\\*r\\*e\\*s\\*s\\*i\\*o\\*n)]]></notAllowedPattern>
            <notAllowedPattern><![CDATA[&[#\\%x]+[\da-fA-F][\da-fA-F]+]]></notAllowedPattern>
        </attribute>
    </attributeRule>
 
</config>
cs

차단 할 댓글,게시글의 패턴을 설정하는 파일입니다. 파일 위치는 src/main/resources 디렉토리 밑에 넣어주면 됩니다.






BeforeAdvice.java



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Service
@Aspect
public class BeforeAdvice {
    private static final Logger logger = LoggerFactory.getLogger(BeforeAdvice.class);
    
    @Before("PointcutCommon.insertReplyPointCut()")
    public void beforeInsertReplyAdvice(JoinPoint jp) {
        logger.info("[beforeAdviceLog] : "+jp.getSignature().getName()+"()호출");
        Object[] obj=jp.getArgs();
        for(int i=0;i<obj.length;i++) {
            if(obj[i] instanceof ReplyDTO) {
                ReplyDTO rdto=(ReplyDTO)obj[i];
                String commentContent=rdto.getR_content();
                XssFilter xssFilter=XssFilter.getInstance("lucy-xss-superset.xml");
                String filterCommentContent=xssFilter.doFilter(commentContent);
                Pattern p=Pattern.compile("<!-- Not Allowed Tag Filtered -->");
                Matcher m=p.matcher(filterCommentContent);
                String xssResult="";
                if(m.find()){
                    xssResult="비정상적인 댓글입니다.";
                }else{
                    xssResult=commentContent;
                }
                if(!commentContent.equals(xssResult)) {
                    logger.info("[beforeAdviceLog] : "+commentContent+"에서 "+xssResult+"로 댓글 내용 변경");
                }
                rdto.setR_content(xssResult);
            }else if(obj[i] instanceof ReplyPhotoDTO) {
                ReplyPhotoDTO rpdto=(ReplyPhotoDTO)obj[i];
                String commentContent=rpdto.getR_content();
                XssFilter xssFilter=XssFilter.getInstance("lucy-xss-superset.xml");
                String filterCommentContent=xssFilter.doFilter(commentContent);
                Pattern p=Pattern.compile("<!-- Not Allowed Tag Filtered -->");
                Matcher m=p.matcher(filterCommentContent);
                String xssResult="";
                if(m.find()){
                    xssResult="비정상적인 댓글입니다.";
                }else{
                    xssResult=commentContent;
                }
                if(!commentContent.equals(xssResult)) {
                    logger.info("[beforeAdviceLog] : "+commentContent+"에서 "+xssResult+"로 댓글 내용 변경");
                }
                rpdto.setR_content(xssResult);
            }
        }
    }
}
cs


@Before 어노테이션을 적용하여 프록시가 중간에 과정을 가로채게 됩니다. (Pointcut설정은 insertBoard,insertReply 등 게시물,댓글의 삽입 메소드의 이름을 통일하여 모두 적용되게 합니다. pointcut 소스는 생략합니다. 필요하시면 댓글 남겨주세요!) 만약 댓글을 삽입하던 게시물을 삽입하던 pointcut에 걸리게 되면 모두 이 AOP 메소드가 먼저 실행되게 됩니다.(@Before 어노테이션에 의해서 진짜 메소드가 실행되기 전에 먼저 aop의 메소드가 실행되게 된다.)


그렇다면 어떻게 사용자가 작성한 content를 가져오게 되는가? 보통 게시물을 쓰거나 댓글을 쓰게 되면 DB에 삽입하는 메소드의 매개변수로 작성한 content가 들어오게 됩니다. 그 매개변수를 JoinPoint의 객체의 getArgs()라는 메소드로 메소드의 매개변수를 가져오게 됩니다. getArgs( )는 object 형태로 매개변수를 가져오게 되므로 반복문을 통해 instanceof 로 원하는 매개변수를 얻어와서 만약 xss공격이 포함된 content 면 @Before클래스의 메소드 내에서 해당 매개변수를 바꿔서 다시 비즈니스 로직을 실행하는 것입니다. ex)<script>alert('xss attack !')</script>라는 댓글을 작성하면 이 클래스에서 '비정상적인 댓글입니다." 라고 매개변수를 바꿔줍니다. 그러면 비즈니스 로직에서는 변경된 댓글을 이용해 DB에 삽입되게 됩니다.



<실행과정>


1. XssFilter xssFilter=XssFilter.getInstance("lucy-xss-superset.xml"); => (싱글톤 패턴이 적용된) XssFilter 객체를 가져옵니다. resources 밑에 xml파일                                                                                             을 이용

2. String filterCommentContent=xssFilter.doFilter(commentContent); => 사용자가 작성한 댓글 내용에 doFilter( )를 이용하여 XSS코드가 포함 되어있는지                                                                                               filtering 합니다. 여기에서 '<' 같은 태그문자들은 '&lt','&gt' 등으로 변환됩니다.                                                                                               여기서 filtering되면 해당 content내에 <!-- Not Allowed Tag Filtered-->라는 문                                                                                               자가 포함된 문자열로 바뀌게 됩니다.

3. Pattern p=Pattern.complie("<!-- Not Allowed Tag Filtered -->"); => 정규식 패턴식을 정의


4. Matcher m=p.matcher(filterCommentContent); => 정의한 정규식패턴이 들어가 있다면 m.find( )는 true를 리턴하게 됩니다.




마치며..


혹시라도 읽어 보시다가 틀린 내용이 있다면 지적해주세요.. 혹시 이번 구현코드가 필요하시다면 여기서 생략한 pointcut코드 비즈니스 로직 코드등 댓글로 남겨주시면 보내드리겠습니다~!



posted by 여성게
:


SOLID 원칙이란?



S : SRP(Single Responsibility Principle) - 단일 책임 원칙


O : OCP(Open Closed Principle) - 개방-폐쇄 원칙


L : LSP(Liskov Substitution Principle) - 리스코프 치환 원칙


I : ISP(Interface Segregation Principle) - 인터페이스 분리 원칙


D : DIP(Dependency Inversion Principle) - 의존 역전 원칙




S : SRP(Single Responsibility Principle) - 단일 책임 원칙



단일 책임 원칙이란 말 그대로, 하나의 객체는 하나의 책임만 가져야 한다는 원칙이다. 만약 많은 기능을 한 객체에 다 쑤셔 넣는다면? 그만큼 그 객체와 강하게 결합된 객체들이 많아질 것이다. 또한 많은 기능들이 과연 변경이 하나도 없을 수가 있을까? 아니다. 변경이 있을 때마다 객체에 변경이 요해지는 것이다. 결론적으로 단일 책임 원칙을 지킨다면 변경에 있어서 유연한 대처가 가능 해진다.(결합도가 낮아진다.) 그러면서 회귀 테스트 비용 또한 줄일 수 있다. 여기서 회귀 테스트란(regression test) 시스템에 변경이 발생하였을 때, 기존 기능에 영향을 주는지 평가하는 테스트이다. 만약 단일 책임 원칙을 지켜서 변경에 유연한 코드를 만든다면 회귀 테스트 비용을 대폭 줄일 수 있는 효과까지 나타난다. 


단일 책임 원칙과 연관되는 단어에 산탄총 수술이라는 것이 있다. 만약 하나의 기능(책임)이 여러개의 클래스들로 분산된 경우 그 책임에 변경이 있다면 그 책임을 의존하고 있는 여러 클래스에 대해 변경이 요해진다. 여기서 나온 것이 산탄총 수술이라는 용어이다. 만약 산탄총을 이용해 동물을 쐈다면? 하나의 총알(책임)에서 여러개의 총알이 산탄되어 박힐 것이다. 그렇다면 그 동물을 수술하려면 흩어진 모든 총알이 박힌 환부(책임을 의존하고 있는 클래스들)를 치료해야 할것이다.(여기에서 산탄총 수술이라는 용어가 나왔다.) 여기서 해결 할 방법이란? 하나의 책임을 한 클래스로 분리해서 흩어진 공통 책임을 한 곳에 모아 응집도를 높히는 일이다. 이것은 관점 지향 프로그래밍 기법으로 해결한다. 여러 조인포인트(쉽게 이벤트발생시점) 중에서 특정 포인트 컷에서(특정 이벤트)  실행하는 어드바이스(책임)를 가진 애스팩트(포인트컷 + 어드바이스)를 만들어 위빙(특정 시점에 어드바이스를 실행시키는 역할)하여 해결하는 것이다. 





O : OCP(Open Closed Principle) - 개방-폐쇄 원칙


개방-폐쇄 원칙이란 간단히 확장에는 열려있고 변경에는 닫혀있는 형태로 개발하는 원칙이다. 즉, 기존 코드에는 변경이 없으면서 기능을 추가 할 수 있도록 설계하는 것이다. 여기서 이용하는 것이 인터페이스이다. 어떠한 기능을 사용하는 클라이언트(여기서 말하는 클라이언트는 사용자가 아닌 기능을 사용하는 클래스)는 인터페이스 타입으로 의존 주입을 받는다. 그렇다면 클라이언트 코드에는 변경 없이 인터페이스의 메소드를 이용해 구체적인 구현 클래스를 이용하고 개발자 입장에서는 인터페이스를 실체화한 클래스를 만들어 계속 해서 기능을 확장하면 되므로 확장에는 열려있고(기능추가) 변경에는 닫혀있는(클라이언트코드) 형태의 원칙을 지키게 되는 것이다. 이런 개방-폐쇄 원칙은 단위 테스트에도 많이 이용된다. 테스트 더블(테스트를 위한 가짜객체)을 구현하기 위해 실제 인터페이스를 상속받아서 더미 객체, 테스트스텁, 테스트 스파이, 가짜 객체, 목객체 등을 간단히 구현하여 객체간의 관계, 정보전달 여부등을 확인 할때 사용한다.   





L : LSP(Liskov Substitution Principle) - 리스코프 치환 원칙



리스코프 치환 원칙이란 일반화관계(is a kind)에서 "자식클래스는 최소한 자신의 부모클래스에서 가능한 행위는 수행할 수 있어야한다."라는 원칙이다. 즉, 코드에서 부모클래스 인스턴스에서 자식 클래스 인스턴스로 변경이 되어도 프로그램의 의미는 변화되지 않아야 한다. 여기서 중요한 것이 "일관성"이다. 일관성을 지키는 코드를 작성하는 가장 쉬운 방법이 무엇일까? 바로 슈퍼클래스에서 상속받은 메소드들이 서브 클래스에서 오버라이드, 즉 재정이 되지 않도록 하면 되는 것이다. 추가 기능이 필요할 때는 절대 부모클래스에서 상속받은 메소드를 오버라이드하는 것이 아니라 별도의 메소드로 정의해 사용하는 것이다. 






I : ISP(Interface Segregation Principle) - 인터페이스 분리 원칙


인터페이스 분리 원칙이란 클라이언트는 여러 기능을 가진 클래스를 사용할 때 특정 기능만을 사용하는 경우가 많기 때문에 특정 기능만을 위한 인터페이스를 만들어 놓고 분리해 다른 기능이 변경되도 사용자의 기능에 아무런 영향을 받지 않도록 하는 방법이다. 


만약 한 객체에 많은 기능이 들어가 있다면 기능들 사이에 연관이 있을 확률이 아주 높아지게 된다. 그렇다면 만약 팩스를 사용하는 클라이언트가 copy()메소드의 변경으로 인해 fax()에 이상이 생겨서 팩스를 사용하는데 오류가 발생할 수도 있게 되는 것이다. 여기서 나온 원칙이 인터페이스 분리 원칙이다.


이런 식으로 인터페이스로 핵심 기능을 분리하게 되면 프린트를 사용하는 클라이언트는 다른 기능의 변경에 대해 아무런 영향을 받지 않고 자신이 사용할 기능을 안전히 사용가능 하게된다.






D : DIP(Dependency Inversion Principle) - 의존 역전 원칙


의존 역전 원칙이란 의존 관계를 맺을 때 변화하기 쉬운 것보다는 변화하기 어려운 것에 의존하라는 원칙이다. 여기서 변화하기 쉬운 것은 실체화된 클래스를 이야기하면 변화하기 어려운 것은 추상적인 인터페이스를 이야기 한다. 



이런식으로 아이가 가지고 놀 장난감을 구체적인 구현 클래스로 두는 것이 아니라 인터페이스 타입으로 두어서 구체적으로 가지고 놀 장난감을 외부에서 의존 주입받는 방식으로 개발하는 원칙이다. 즉, 변화를 의존주입으로 쉽게 받아 드릴 수 있는 것이다.


1
2
3
4
5
6
7
8
9
10
11
12
public class kid{
    private Toy toy;
 
    public void setToy(Toy toy){
        this.toy=toy;
    }
    public void playing(){
        toy.play();
    }    
}
 
 
cs


이렇게 인터페이스 타입의 인스턴스 변수를 선언하면 결합도도 느슨해지고 변화에 유연하게 대처할 수 있는 코드가 될 수 있다. 이렇게 외부에서 의존주입을 받는 형식이 "역전"이 되었다고 표현한다.

posted by 여성게
:

JSON객체의 배열로 넘어온 데이터 출력하기


단순히 JSON 객체로 넘어온 것이 아니라 JSON 객체 배열로 넘어온 데이터를 javascript로 출력하는 반복문을 작성하는 예제입니다.  Spring으로 프로젝트를 하는 도중에 ajax로 리스트를 받아야 하는 기능이 있었는데, 그것이 @Response 어노테이션을 이용하여 자동으로 json객체의 배열로 넘겨받는 상황이었습니다. 순간 json객체 단위로 받는 것은 많이 했었지만 json객체의 배열? 생각하니 혼란이 와서 정리해봅니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 var i=[{"t_no":"120","t_content":"test11","t_writer":"임광빈","obtain":"0","t_date":"2018-         04-27"},
            {"t_no":"119","t_content":"test10","t_writer":"임광빈","obtain":"0","t_date":"2018-04-27"},
            {"t_no":"118","t_content":"test9","t_writer":"임광빈","obtain":"0","t_date":"2018-04-27"},
            {"t_no":"117","t_content":"test8","t_writer":"임광빈","obtain":"0","t_date":"2018-04-27"},
            {"t_no":"114","t_content":"test7","t_writer":"임광빈","obtain":"0","t_date":"2018-04-27"},
            {"t_no":"113","t_content":"test6","t_writer":"임광빈","obtain":"0","t_date":"2018-04-26"},
            {"t_no":"110","t_content":"hi","t_writer":"임광빈","obtain":"0","t_date":"2018-04-26"},
            {"t_no":"109","t_content":"test5","t_writer":"임광빈","obtain":"0","t_date":"2018-04-26"},
            {"t_no":"107","t_content":"test4","t_writer":"임광빈","obtain":"0","t_date":"2018-04-26"},
            {"t_no":"76","t_content":"test3","t_writer":"임광빈","obtain":"1","t_date":"2018-04-25"},
            {"t_no":"75","t_content":"test2","t_writer":"임광빈","obtain":"0","t_date":"2018-04-25"},
            {"t_no":"74","t_content":"test1","t_writer":"임광빈","obtain":"1","t_date":"2018-04-25"},
            {"t_no":"72","t_content":"hello","t_writer":"임광빈","obtain":"0","t_date":"2018-04-25"},
            {"t_no":"49","t_content":"spring create","t_writer":"임광 빈","obtain":"1","t_date":"2018-04-25"},
            {"t_no":"29","t_content":"도전과제","t_writer":"임광빈","obtain":"1","t_date":"2018-04-25"},
            {"t_no":"28","t_content":"서블릿공부","t_writer":"임광빈","obtain":"0","t_date":"2018-04-25"},
            {"t_no":"8","t_content":"헬로우","t_writer":"임광빈","obtain":"1","t_date":"2018-04-24"},
            {"t_no":"7","t_content":"안녕하세요.","t_writer":"임광빈","obtain":"0","t_date":"2018-04-24"},
            {"t_no":"5","t_content":"오늘할일4","t_writer":"임광빈","obtain":"1","t_date":"2018-04-24"},
            {"t_no":"1","t_content":"오늘할일1","t_writer":"임광빈","obtain":"1","t_date":"2018-04-24"}];
cs

상황은 위의 변수처럼 json type의 객체들을 배열형태로 받는 상황이었습니다.


1
2
3
4
5
6
7
$.ajax({
    url:"toDoList.do",
    success:function(data){
    //json type array 처리구문
    }
});
 
cs
 
위와 같이 toDoList.do url 요청을 통해 controller 단에서 toDoList를 비동기로 전달 받는 상황이었습니다.(dataType은 default로 json 형태이기 때문에 명시하지 않았습니다. 그리고 controller단에서 @ResponseBody 형태로 달아놨기에 자동으로 json 형태로 데이터를 파싱해서 보낸 상황입니다.)

보통 function(data) 에서 data가 json 객체 형태로 넘어온다면 단순히 data.name(요소) 로 출력하면 되었습니다. 하지만 배열은 이와는 조금 다른 형태로 진행됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<script>
    var i=[{"t_no":"120","t_content":"test11","t_writer":"여성게","obtain":"0","t_date":"2018-         04-27"},
            {"t_no":"119","t_content":"test10","t_writer":"여성게","obtain":"0","t_date":"2018-04-27"},
            {"t_no":"118","t_content":"test9","t_writer":"여성게","obtain":"0","t_date":"2018-04-27"},
            {"t_no":"117","t_content":"test8","t_writer":"여성게","obtain":"0","t_date":"2018-04-27"},
            {"t_no":"114","t_content":"test7","t_writer":"여성게","obtain":"0","t_date":"2018-04-27"},
            {"t_no":"113","t_content":"test6","t_writer":"여성게","obtain":"0","t_date":"2018-04-26"},
            {"t_no":"110","t_content":"hi","t_writer":"여성게","obtain":"0","t_date":"2018-04-26"},
            {"t_no":"109","t_content":"test5","t_writer":"여성게","obtain":"0","t_date":"2018-04-26"},
            {"t_no":"107","t_content":"test4","t_writer":"여성게","obtain":"0","t_date":"2018-04-26"},
            {"t_no":"76","t_content":"test3","t_writer":"여성게","obtain":"1","t_date":"2018-04-25"},
            {"t_no":"75","t_content":"test2","t_writer":"여성게","obtain":"0","t_date":"2018-04-25"},
            {"t_no":"74","t_content":"test1","t_writer":"여성게","obtain":"1","t_date":"2018-04-25"},
            {"t_no":"72","t_content":"hello","t_writer":"여성게","obtain":"0","t_date":"2018-04-25"},
            {"t_no":"49","t_content":"spring create","t_writer":"여성게","obtain":"1","t_date":"2018-04-25"},
            {"t_no":"29","t_content":"도전과제","t_writer":"여성게","obtain":"1","t_date":"2018-04-25"},
            {"t_no":"28","t_content":"서블릿공부","t_writer":"여성게","obtain":"0","t_date":"2018-04-25"},
            {"t_no":"8","t_content":"헬로우","t_writer":"여성게","obtain":"1","t_date":"2018-04-24"},
            {"t_no":"7","t_content":"안녕하세요.","t_writer":"여성게","obtain":"0","t_date":"2018-04-24"},
            {"t_no":"5","t_content":"오늘할일4","t_writer":"여성게","obtain":"1","t_date":"2018-04-24"},
            {"t_no":"1","t_content":"오늘할일1","t_writer":"여성게","obtain":"1","t_date":"2018-04-24"}];
    
   for(var ele in i){ for(var ele2 in i[ele]){ console.log(i[ele][ele2]); } }
</script>
 
cs


배열 형태로 넘어오기 때문에 for문을 사용하여 출력해 줍니다. for(var ele in i) 에서 만약 i가 단순 배열이라면 ele에 할당되는 데이터는 배열의 인덱스 숫자입니다.(0,1,2.....i.length) 하지만 이번에 전달 받은 data 변수는 일반 배열이 아닌 json객체의 배열이므로 ele에 할당되는 데이터는 조금 다릅니다. 하지만 로그를 남기는 부분의 코드를 보면 배열과 흡사하다 못해 아주 똑같은 형식입니다. 즉 ele2는 json 객체의 key값이 할당되는 것입니다. 

i[ele][ele2] -> i[0,1...i.length][t_no,t_content......t_date] 이런식으로 값이 할당 됩니다. 


1
2
3
4
5
6
7
8
9
10
for(var ele in i){
        /*for(var ele2 in i[ele]){
            console.log(i[ele][ele2]);
        } */
        console.log(i[ele].t_no);
        console.log(i[ele].t_content);
        console.log(i[ele].t_writer);
        console.log(i[ele].obtain);
        console.log(i[ele].t_date);
    }
cs

그리고 두번째로는 이렇게 단일 for문을 이용해 각각의 키값으로 참조해서 출력도 가능합니다.!!!  혹시 틀렸거나 부족한 부분이 있었다면 지적부탁드립니다...... :)

posted by 여성게
:
Web/Spring 2018. 4. 22. 18:28

스프링 + 웹소켓을 이용한 간단한 실시간 채팅


우선 웹소켓이란 간단히 이야기하면 서버와 양방향 통신이 가능한 통신 방법이다. 그럼으로써 실시간 채팅 등이 구현이 가능한 것이다. 여기서 그러면 "ajax로 구현하면 되잖아? ajax도 서버와 통신이 되는데?" 생각을 하게된다. 나도 처음에는 그렇게 생각했는데, 생각해보면 ajax는 클라이언트가 서버로 데이터를 요청을 한다. 하지만 서버가 클라이언트에게 요청할 수 있는 방법이 없다. 하지만 웹소켓은 가능하다라는 것 ! ajax로 채팅을 구현한다면 클라이언트가 보낸 메시지를 서버가 받아서 그 메시지를 모든 사람에게 전송한다? ajax는 예를 들어 10초에 한번씩 서버에서 메시지를 뿌려주는 기능을 구현해야 할것이다. 왜냐하면 서버가 클라이언트에게 요청을 할수 있는 방법이 없기때문이다.


https://developer.mozilla.org/ko/docs/WebSockets/Writing_WebSocket_client_applications -> 참고




1. 시작전 설정


pom.xml


1
2
3
4
5
<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>${org.springframework-version}</version>
</dependency>
cs



web.xml

1
2
3
4
5
6
7
8
9
10
11
12
<servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
                /WEB-INF/config/presentation-layer.xml
            </param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported> <!-- 웹소켓을 위한 설정(비동기지원) -->
</servlet>
cs




2.WebSocketChat


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package com.web.rnb.controller;
 
import java.util.ArrayList;
import java.util.List;
 
import javax.websocket.server.ServerEndpoint;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
 
import javax.websocket.RemoteEndpoint.Basic;
 
@Controller
@ServerEndpoint(value="/echo.do")
public class WebSocketChat {
    
    private static final List<Session> sessionList=new ArrayList<Session>();;
    private static final Logger logger = LoggerFactory.getLogger(WebSocketChat.class);
    public WebSocketChat() {
        // TODO Auto-generated constructor stub
        System.out.println("웹소켓(서버) 객체생성");
    }
    @RequestMapping(value="/chat.do")
    public ModelAndView getChatViewPage(ModelAndView mav) {
        mav.setViewName("chat");
        return mav;
    }
    @OnOpen
    public void onOpen(Session session) {
        logger.info("Open session id:"+session.getId());
        try {
            final Basic basic=session.getBasicRemote();
            basic.sendText("Connection Established");
        }catch (Exception e) {
            // TODO: handle exception
            System.out.println(e.getMessage());
        }
        sessionList.add(session);
    }
    /*
     * 모든 사용자에게 메시지를 전달한다.
     * @param self
     * @param message
     */
    private void sendAllSessionToMessage(Session self,String message) {
        try {
            for(Session session : WebSocketChat.sessionList) {
                if(!self.getId().equals(session.getId())) {
                    session.getBasicRemote().sendText(message.split(",")[1]+" : "+message);
                }
            }
        }catch (Exception e) {
            // TODO: handle exception
            System.out.println(e.getMessage());
        }
    }
    @OnMessage
    public void onMessage(String message,Session session) {
        logger.info("Message From "+message.split(",")[1+ ": "+message.split(",")[0]);
        try {
            final Basic basic=session.getBasicRemote();
            basic.sendText("to : "+message);
        }catch (Exception e) {
            // TODO: handle exception
            System.out.println(e.getMessage());
        }
        sendAllSessionToMessage(session, message);
    }
    @OnError
    public void onError(Throwable e,Session session) {
        
    }
    @OnClose
    public void onClose(Session session) {
        logger.info("Session "+session.getId()+" has ended");
        sessionList.remove(session);
    }
}
 
cs

@ServerEndpoint(value="/echo.do") 는 /echo.do 라는 url 요청을 통해 웹소켓에 들어가겠다라는 어노테이션입니다.

@onOpen 는 클라이언트가 웹소켓에 들어오고 서버에 아무런 문제 없이 들어왔을때 실행하는 메소드입니다.

@onMessage 는 클라이언트에게 메시지가 들어왔을 때, 실행되는 메소드입니다.

@onError 

@onClose 는 클라이언트와 웹소켓과의 연결이 끊기면 실행되는 메소드입니다.

sendAllSessionToMessage()는 어떤 누군가에게 메시지가 왔다면 그 메시지를 보낸 자신을 제외한 연결된 세션(클라이언트)에게 메시지를 보내는
메소드입니다.





3.jsp



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<%@ page language="java" contentType="text/html; charset=EUC-KR"
    pageEncoding="EUC-KR"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">
<title>Insert title here</title>
</head>
<body>
    <div>
        <input type="text" id="sender" value="${sessionScope.member.m_id }" style="display: none;">
        <input type="text" id="messageinput">
    </div>
    <div>
        <button type="button" onclick="openSocket();">Open</button>
        <button type="button" onclick="send();">Send</button>
        <button type="button" onclick="closeSocket();">Close</button>
    </div>
    <!-- Server responses get written here -->
    <div id="messages"></div>
    <!-- websocket javascript -->
    <script type="text/javascript">
        var ws;
        var messages=document.getElementById("messages");
        
        function openSocket(){
            if(ws!==undefined && ws.readyState!==WebSocket.CLOSED){
                writeResponse("WebSocket is already opened.");
                return;
            }
            //웹소켓 객체 만드는 코드
            ws=new WebSocket("ws://localhost:9080/rnb/echo.do");
            
            ws.onopen=function(event){
                if(event.data===undefined) return;
                
                writeResponse(event.data);
            };
            ws.onmessage=function(event){
                writeResponse(event.data);
            };
            ws.onclose=function(event){
                writeResponse("Connection closed");
            }
        }
        
        function send(){
            var text=document.getElementById("messageinput").value+","+document.getElementById("sender").value;
            ws.send(text);
            text="";
        }
        
        function closeSocket(){
            ws.close();
        }
        function writeResponse(text){
            messages.innerHTML+="<br/>"+text;
        }
  </script>
</body>
</html>
cs







posted by 여성게
:

1. 그래프 다익스트라 - 네트워크 복구(백준 2211번)






2. 다익스트라 알고리즘을 이용한 문제풀이



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package _317324;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Scanner;
 
public class Baekjoon2211NetworkRepairDijkstra {
    static ArrayList<String>[] vertextList;
    static ArrayList<Integer>[] result;
    static int[] distance;
    static int count=0;
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
        int m=sc.nextInt();
        
        result=new ArrayList[n+1];
        vertextList=new ArrayList[n+1];
        distance=new int[n+1];
        
        PriorityQueue<String> pq=new PriorityQueue<>(new Comparator<String>() {
 
            @Override
            public int compare(String o1, String o2) {
                // TODO Auto-generated method stub
                String[] strArr1=o1.split(" ");
                String[] strArr2=o2.split(" ");
                
                String a=distance[Integer.parseInt(strArr1[0])]+"";
                String b=distance[Integer.parseInt(strArr2[0])]+"";
                return a.compareTo(b);
            }
        });
        for(int i=1;i<n+1;i++) {
            result[i]=new ArrayList<Integer>();
            vertextList[i]=new ArrayList<String>();
            distance[i]=Integer.MAX_VALUE;
        }
        distance[1]=0;
        for(int i=0;i<m;i++) {
            int from=sc.nextInt();
            int to=sc.nextInt();
            int weight=sc.nextInt();
            vertextList[from].add(to+" "+weight);
            vertextList[to].add(from+" "+weight);
        }
        pq.offer(1+" "+distance[1]);
        
        while(!pq.isEmpty()) {
            String[] vertexInfo=pq.poll().split(" ");
            Iterator<String> it=vertextList[Integer.parseInt(vertexInfo[0])].iterator();
            while(it.hasNext()) {
                String adVertex=it.next();
                String[] adVertexArr=adVertex.split(" ");
                if(distance[Integer.parseInt(adVertexArr[0])]>distance[Integer.parseInt(vertexInfo[0])]
                        +Integer.parseInt(adVertexArr[1])) {
                    distance[Integer.parseInt(adVertexArr[0])]=distance[Integer.parseInt(vertexInfo[0])]
                            +Integer.parseInt(adVertexArr[1]);
                    count++;
                    for(int i=1;i<result.length;i++) {
                        Iterator<Integer> it1=result[i].iterator();
                        int index=0;
                        while(it1.hasNext()) {
                            if(it1.next()==Integer.parseInt(adVertexArr[0])) {
                                result[i].remove(index);
                                count--;
                                break;
                            }
                            index++;
                        }
                    }
                    result[Integer.parseInt(vertexInfo[0])].add(Integer.parseInt(adVertexArr[0]));
                    pq.offer(adVertex);
                }
            }
        }
        System.out.println(count);
        for(int i=1;i<result.length;i++) {
            Iterator it=result[i].iterator();
            while(it.hasNext()) {
                System.out.println(i+" "+it.next());
            }
        }
    }
 
}
 
cs


다익스트라 알고리즘을 이용하여 최소 경로를 구하는 문제이다. 하지만 여기에서 최소경로만 구하는 것이 아니라 최소 경로에 해당하는 노드들의 연결까지 출력하는 문제이다.(최소 경로 값만 출력하면 쉬운문제이다. 앞에서 다루었던 최소경로,최소비용 문제를 참고하면 된다. 다익스트라 알고리즘 설명은 따로 하지 않는다.) 위의 코드는 조금은 비효율적인 코드라서 나중에 수정하기는 하겠지만, 이 코드를 설명하면 무한대 값으로 초기화되있는 노드들의 값을 최단 경로로 변경될때 마다 result라는 리스트 배열에 두 노드의 값을 넣어 준다. 하지만 값을 넣어주기 전에 이전에 같은 노드의 값이 들어와있는지 반복문을 통해 검사해 만약 이전에 이 노드 값이 들어오지 않았다면 그냥 result 리스트 배열에 값을 넣어주고 만약 이전에 같은 노드가 들어왔었다면 count--해주고 이전에 들어온 값을 제거한 후에 최단경로가 변경된 노드의 값을 리스트 배열에 넣어준다. 그리고 모든 알고리즘이 종료된 후에는 최단 경로에 해당하는 노드들의 연결이 배열 리스트에 저장이 되어 있게된다.

posted by 여성게
:

1. 다이나믹 프로그래밍 - 동전1 (백준 2293번)






2. 다이나믹 프로그래밍을 이용한 문제풀이



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package _317324;
 
import java.util.Scanner;
 
public class Baekjoon2293Coin1 {
    static int dp(int[] coin,int[] memo,int n,int k) {
        memo[0]=1;
        for(int i=0;i<n;i++) {
            for(int j=1;j<k+1;j++) {
                if(j-coin[i]>=0) memo[j]+=memo[j-coin[i]];
            }
        }
        return memo[k];
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
        int k=sc.nextInt();
        int[] coin=new int[n];
        int[] memo=new int[k+1];
        for(int i=0;i<n;i++) {
            coin[i]=sc.nextInt();
        }
        System.out.println(dp(coin,memo,n,k));
    }
 
}
 
cs



이 문제를 예를 들어 설명한다면, dp메소드에서 반복문에 들어가게 되면 우선 memo[0]=1 이라고 초기화를 한다. 이 것은 동전을 아무 것도 선택하지 않는 경우를 한 경우로 보고 1로 초기화하는 것이다.(확률에서 선택하지 않는 것도 하나를 1로 생각하는 것과 같다 보면된다.) 그리고 반복문을 들어가게 된다. 여기서 i=0 일때를 보면 j-coin[i]>=0 에서 j=1부터 성립을 하게 된다. 그래서 memo[1]=memo[1]+memo[0] 에서 memo[1]=1 이 되게 된다. 여기서는 coin[1]이 1이므로 k+1까지 모두 1가지 경우로 값이 대입되게 된다. 하지만 2,3 혹은 어느 값이 들어오더라도 그 값의 배수에만 경우의 수 1이 들어가게 된다. 예를 들어 coin[1]=2일때 j가 2부터 j-coin[i]>=0 이 성립하고 memo[j]=memo[j]+memo[j-coin[i]] 값은 2의 배수인 경우에만 1 값이 들어오게 된다. 여기서 보면 특이한 것이 coin[1]=2 이고 j=2면 memo[j-2=0] 의 경우를 더해주고 j=4 이면 memo[j-2=2]의 경우의 수를 더해주고 규칙이 보일 것이다. 그럼 문제의 조건으로 돌아와서 coin[2]일때를 보면 j=2 일경우부터 조건문이 성립하고 memo[2]=memo[2]+memo[2-2] 이 된다. 이 말은 1과 2를 이용해 2를 만드는 경우의 수는 1->2개(1을 2개이용) , 1->0 && 2->1(2를 1개이용) 두가지의 경우의 수가 나온다. 여기서 1->2는 i=0일때 1로 2를 만들수 있는 경우의 수(이전 경우의수,이값은 빨간색으로) 1->0 && 2->1은 1을 0개 선택하고 2를 하나 선택한 경우(파란색)이다. 이 경우의 수를 색으로 표현한다면 

memo[2]=memo[2]+memo[0] 이 된다. 여기서의 이해가 중요하다. 앞의 memo[2]는 현재의 동전이 아니라 이전의 동전으로 2를 만들수 있는 방법, memo[0]은 2라는 동전을 하나 선택해서 2라는 값을 만들었으므로 이전의 동전으로 0이라는 값을 만드는 경우의 수를 더하는 것이다. 그러면 더 나아가서 memo[6]=memo[6]+memo[4] 라는 값을 보자 앞의 memo[6]은 이전까지의 동전들로 6을 만드는 경우이다. 즉, 1 1 1 1 1 1, 그리고 나머지 경우를 보게되면 1 1 1 1 2, 1 1 2 2, 2 2 2 라는 경우가 남아있다. 여기서 맨위 2를 빼면 1 1 1 1, 1 1 2, 2 2라는 경우는 어느 경우인가? 2까지의 동전을 사용해 memo[4]를 만든 경우가 된다. 이런식으로 마지막 동전까지 반복을 하게 된다.


posted by 여성게
:

1. 문자열 처리 - 명령 프롬프트(백준 1032번)






2.문제 풀이


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package _317324;
 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
 
public class Baekjoon1032Cmd {
    static String[] strArr;
    static int count;
    static String str="";
    static boolean isSame=true;
    public static void main(String[] args) throws NumberFormatException, IOException {
        // TODO Auto-generated method stub
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        
        int n=Integer.parseInt(br.readLine().trim());
        strArr=new String[n];
        
        for(int i=0;i<n;i++) {
            strArr[i]=br.readLine();
        }
        for(int i=0;i<strArr[0].length();i++) {
            for(int j=1;j<n;j++) {
                if(strArr[0].charAt(i)==strArr[j].charAt(i)) {
                    isSame=true;
                }else {
                    isSame=false;
                    break;
                }
            }
            if(isSame==false) {
                str+="?";
            }else {
                str+=strArr[0].charAt(i);
            }
        }
        System.out.println(str);
        br.close();
    }
 
}
 
cs



이 문제는 간단히 주어진 모든 문자열에서 같은 위치에 같은 문자를 갖는 다면 그 문자를 그대로, 만약 하나의 문자열이라도 그 위치의 문자가 다르다면 '?'를 출력하는 문제이다. 물론 위의 코드보다 더욱 효율적인 코드는 있겠지만 실행이 되는 코드이고 코드를 직관적으로 분석하기 편하다. 모든 문자열의 길이는 같으므로(문제풀기가 수월한 이유이다.) 첫번째 배열의 문자열을 기준으로 하여 그 외의 모든 문자열을 반복문으로 불러와서 각 위치의 문자가 같은지만 비교하면 된다. 만약 같다면 그 문자를 스트링에 넣고 만약 한 글자라도 다르다면 isSame에 false가 들어가 바로 반복문을 벗어나 str에 '?'를 넣게 된다. 이렇게 문자열의 길이만큼 반복문을 실행하면 되는 것이다.

posted by 여성게
: