SpringBoot에서 Websocket 사용하기

Websocket 이란?

서버와 클라이언트 사이에 양방향 통신 채널을 구축할 수 있는 통신 프로토콜이다. 동작 방식은 먼저 HTTP 통신을 연결하고 이후 Upgrade 헤더를 보내 양방향 연결로 업그레이드한다. Websocket은 최신 브라우저에서는 대부분 지원한다.

전체 소스는 참고 내역에 있는 소스를 확인하면 된다.

주요 설정은 다음과 같다.

1. WebSocket Configuration

package com.example.websocketdemo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.*;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/app");
        registry.enableSimpleBroker("/topic");
    }
}

@EnableWebSocketMessageBroker 은 websocket 서버를 사용한다는 설정이다. 또한 WebSocketMessageBrokerConfigure를 상속 받아 몇몇 메소드를 구현하여 websocket 연결 속성을 설정한다. registerStompEndpoints를 이용하여 클라이언트에서 websocket에 접속하는 endpoint를 등록한다. withSockJS()를 이용시에는 브라우져에서 websocket을 지원하지 않을 경우 fallback 옵션을 활성화 하는데 사용됩니다.

메소드 이름에 STOMP(Simple Text Oriented Messaging Protocol)라는 단어가 있다. 이는 스프링프레임워크의 STOMP 구현체를 사용한다는 의미다. STOMP가 필요 한 이유는 websocket은 통신 프로토콜이지 특정 주제에 가입한 사용자에게 메시지를 전송하는 기능을 제공하지 않는다. 이를 쉽게 사용하기 위해 STOMP를 사용한다.

두변째 메소드configureMessageBroker는 한 클라이언트에서 다른 클라이언트로 메시지를 라우팅 할 때 사용하는 브로커를 구성한다. 첫번째 라인에서 정의된 /app로 시작하는 메시지만 메시지 헨들러로 라우팅한다고 정의한다. 두번째 라인에서 정의된 /topic로 시작하는 주제를 가진 메시지를 핸들러로 라우팅하여 해당 주제에 가입한 모든 클라이언트에게 메시지를 방송한다.

2. ChatController

package com.example.websocketdemo.controller;

import com.example.websocketdemo.model.ChatMessage;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.stereotype.Controller;

@Controller
public class ChatController {

    @MessageMapping("/chat.sendMessage")
    @SendTo("/topic/public")
    public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {
        return chatMessage;
    }

    @MessageMapping("/chat.addUser")
    @SendTo("/topic/public")
    public ChatMessage addUser(@Payload ChatMessage chatMessage, 
                               SimpMessageHeaderAccessor headerAccessor) {
        // Add username in web socket session
        headerAccessor.getSessionAttributes().put("username", chatMessage.getSender());
        return chatMessage;
    }

}

@MessageMapping는 클라이언트에서 보내는 메시지를 매핑한다. 호출 되는 주소는 /app/chart.addUer/app/chat.sendMessage가 된다.

3. main.js

javascript 에서 실제 사용은 다음 같이 사용한다.

function connect(event) {
    username = document.querySelector('#name').value.trim();

    if(username) {
        usernamePage.classList.add('hidden');
        chatPage.classList.remove('hidden');

        var socket = new SockJS('/ws');
        stompClient = Stomp.over(socket);

        stompClient.connect({}, onConnected, onError);
    }
    event.preventDefault();
}


function onConnected() {
    // Subscribe to the Public Topic
    stompClient.subscribe('/topic/public', onMessageReceived);

    // Tell your username to the server
    stompClient.send("/app/chat.addUser",
        {},
        JSON.stringify({sender: username, type: 'JOIN'})
    )
    connectingElement.classList.add('hidden');
}

.... 중략

connect를 통해 클라이언트는 websocket을 연결 합니다. 연결에 성공하면 /topic/public 주제에 가입하여 메시지를 주고 받습니다.

참고 내역


Posted by lahuman

댓글을 달아 주세요

Spring Controller에 대하여 표 형식의 문서를 만들어 주는 프로그램입니다.


Source 바로가기


기본 사항 

JRE 1.8 이상이 설치되어 있어야 합니다.

실행은 junit으로 GeneratorSpringControllerDoc.java 을 실행합니다.

docx 형식의 샘플 템플릿은 resource 에 있습니다.


주요 기능

* Spring Controller 에 연결 정보를 표 형식으로 표출


샘플 출력 결과

Posted by lahuman

댓글을 달아 주세요

Spring MVC 에서 ResponsBody로 String 을 전달시 한글 깨짐 현상 해결

Controller에서 단순한 문자열(String)을 ResponseBody로 전달 할 경우, 깨지는 현상이 발생할수 있습니다. 코드는 다음과 같습니다.

@RequestMapping(value="/preview/{id}", method=RequestMethod.GET)
public @ResponseBody String getContent(@PathVariable("id") long id) {
    return service.getContent(id);
}

한글이깨지는 원인은 브라우져에서 해당 요청에 대한 응답의 헤더 값을 보면 다음과 같이 표현 되어 있습니다.

Content-Type:application/json;charset=ISO-8859-1

위의 문제를 해결 하기 위해서는 다음과 같이 spring servlet xml 설정을 추가 해야 합니다.

<mvc:annotation-driven>
    <mvc:message-converters>
        <!-- @ResponseBody Content-Type:application/json;charset=UTF-8  -->
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="supportedMediaTypes">
                <list>
                    <value>text/html;charset=UTF-8</value>
                </list>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

위와 같이 설정을 한 후 응답의 해더 값을 확인 하면 다음과 같습니다.

Content-Type:application/json;charset=UTF-8

이후 Content 값을 확인하면, 한글이 깨지지 않고 제대로 표출 되는 것을 확인 할 수 있습니다.

Posted by lahuman

댓글을 달아 주세요

XSS를 네이버에서 만든 Lucy의 servlet-filter 를 이용하여 지금까지 쉽게 처리 해 왔습니다.


XSS 관련해서 POST로 처리 하는 부분이 제대로 동작 하지 않는 것을 확인하여 체크해본 결과 Lucy는 RequestParameter관련한 지원만 합니다.


따라서 Spring에서 @RequestBody를 이용한 부분은 처리할 수 없습니다.


이에 구글링으로 검색해본 결과 MessageConverter를 이용하여 처리 하는 방법을 확인 하였습니다.


처리 방법은 spring 4.2.5 기준으로 다음과 같습니다.


먼저 다음과 같은 ObjectMapper를 가진 FactoryBean을 생성 합니다.


package kr.pe.lahuman;

import org.apache.commons.lang3.StringEscapeUtils;

import org.springframework.beans.factory.FactoryBean;


import com.fasterxml.jackson.core.SerializableString;

import com.fasterxml.jackson.core.io.CharacterEscapes;

import com.fasterxml.jackson.core.io.SerializedString;

import com.fasterxml.jackson.databind.DeserializationFeature;

import com.fasterxml.jackson.databind.ObjectMapper;


public class HtmlEscapingObjectMapperFactory implements FactoryBean<ObjectMapper> {


    private final ObjectMapper objectMapper;


public HtmlEscapingObjectMapperFactory() {

        objectMapper = new ObjectMapper();

        objectMapper.getFactory().setCharacterEscapes(new HTMLCharacterEscapes());

        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    }


    @Override

    public ObjectMapper getObject() throws Exception {

        return objectMapper;

    }


    @Override

    public Class<?> getObjectType() {

        return ObjectMapper.class;

    }


    @Override

    public boolean isSingleton() {

        return true;

    }


    public static class HTMLCharacterEscapes extends CharacterEscapes {


        private final int[] asciiEscapes;


        public HTMLCharacterEscapes() {

            // start with set of characters known to require escaping (double-quote, backslash etc)

            asciiEscapes = CharacterEscapes.standardAsciiEscapesForJSON();

            // and force escaping of a few others:

            asciiEscapes['<'] = CharacterEscapes.ESCAPE_CUSTOM;

            asciiEscapes['>'] = CharacterEscapes.ESCAPE_CUSTOM;

            asciiEscapes['&'] = CharacterEscapes.ESCAPE_CUSTOM;

            asciiEscapes['"'] = CharacterEscapes.ESCAPE_CUSTOM;

            asciiEscapes['\''] = CharacterEscapes.ESCAPE_CUSTOM;

        }



        @Override

        public int[] getEscapeCodesForAscii() {

            return asciiEscapes;

        }


        // and this for others; we don't need anything special here

        @Override

        public SerializableString getEscapeSequence(int ch) {

            return new SerializedString(StringEscapeUtils.escapeHtml4(Character.toString((char) ch)));

        }

    }

}



그리고 spring-servlet.xml 설정에 다음과 같이 처리 합니다.

<bean id="htmlEscapingObjectMapper" class="kr.pe.lahuman.HtmlEscapingObjectMapperFactory" />


<mvc:annotation-driven>

   <mvc:message-converters>

       <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" >

        <property name="objectMapper" ref="htmlEscapingObjectMapper"></property>

       </bean>

   </mvc:message-converters>

</mvc:annotation-driven>




출처 : http://stackoverflow.com/questions/25403676/initbinder-with-requestbody-escaping-xss-in-spring-3-2-4



방법 2: RequestWrapper를 이용하여 처리하는 방법


request의 값을 변조할 경우 많이 사용 하는 방법으로 Request를 Wrapping 하여 사용자가 원하는 데이터를 가공 하는 방식이다.


처리 코드는 다음과 같다.


먼저 Request를 변환 하기 위한 Filter를 생성 한다.

package kr.pe.lahuman;


import java.io.IOException;


import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;


public class RequestBodyXSSFIleter implements Filter {

@Override

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)

throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest)req;

  HttpServletResponse response = (HttpServletResponse)res;

  RequestWrapper requestWrapper = null;

  try{

  requestWrapper = new RequestWrapper(request);

  }catch(Exception e){

  e.printStackTrace();

  }

  chain.doFilter(requestWrapper, response);

}

@Override

public void init(FilterConfig filterConfig) throws ServletException {}

@Override

public void destroy() {}

}



RequestWrapper를 생성한다.

package kr.pe.lahuman;


import java.io.BufferedReader;

import java.io.ByteArrayInputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.InputStreamReader;


import javax.servlet.ServletInputStream;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletRequestWrapper;


import org.apache.commons.io.IOUtils;


import com.nhncorp.lucy.security.xss.XssFilter;


public class RequestWrapper extends HttpServletRequestWrapper {

private byte[] b;

public RequestWrapper(HttpServletRequest request) throws IOException {

super(request);

  XssFilter filter = XssFilter.getInstance("lucy-xss-sax.xml");

  b = new String(filter.doFilter(getBody(request))).getBytes();

}

public ServletInputStream getInputStream() throws IOException {

  final ByteArrayInputStream bis = new ByteArrayInputStream(b);

 

  return new ServletInputStreamImpl(bis);

  }

 

  class ServletInputStreamImpl extends ServletInputStream{

  private InputStream is;

 

  public ServletInputStreamImpl(InputStream bis){

  is = bis;

  }

 

  public int read() throws IOException {

  return is.read();

  }

 

  public int read(byte[] b) throws IOException {

  return is.read(b);

  }

  }


 

  public static String getBody(HttpServletRequest request) throws IOException {


     String body = null;

     StringBuilder stringBuilder = new StringBuilder();

     BufferedReader bufferedReader = null;


     try {

         InputStream inputStream = request.getInputStream();

         if (inputStream != null) {

             bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

             char[] charBuffer = new char[128];

             int bytesRead = -1;

             while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {

                 stringBuilder.append(charBuffer, 0, bytesRead);

             }

         } else {

             stringBuilder.append("");

         }

     } catch (IOException ex) {

         throw ex;

     } finally {

         if (bufferedReader != null) {

             try {

                 bufferedReader.close();

             } catch (IOException ex) {

                 throw ex;

             }

         }

     }


     body = stringBuilder.toString();

     return body;

  }

}


마지막으로 web.xml에 해당 filter를 추가 한다.

  <filter>

  <filter-name>RequestBodyXSSFilter</filter-name>

  <filter-class>kr.pe.lahuman.RequestBodyXSSFIleter</filter-class>

  </filter>

    <filter-mapping>

    <filter-name>RequestBodyXSSFilter</filter-name>

    <url-pattern>/*</url-pattern>

  </filter-mapping>


출처 : http://shonm.tistory.com/549

Posted by lahuman

댓글을 달아 주세요

  1. 도움맨 2016.10.24 17:26  댓글주소  수정/삭제  댓글쓰기

    밑에 BodyWrapper 로 하면 @RequestBody는 잘 필터링이 되기 시작하나,
    일반적인 application/x-www-form-urlencoded 등의 파라미터를 스프링이 @ModelAttribute 로 파싱하지 못하는 현상이 생기는데요..
    혹시 주인장님께선 이런증상이 없으신지요.

  2. 2017.12.14 17:55  댓글주소  수정/삭제  댓글쓰기

    contentType json 인 경우만 되게 하면 되지 않을까요?

Aspect 를 이용한 공통 BindingResult 처리 방법

validation 을 다음과 같이 처리를 한다.

VO에서 Vaildation 관련 설정
@Data
@JsonInclude(Include.NON_NULL)
public class RolesVO {
    @NotNull(groups={ValidationGroup.Update.class})
    private Long roleSeq;
    @NotBlank(groups={ValidationGroup.Insert.class, ValidationGroup.Update.class})
    private String roleId;
    @NotBlank(groups={ValidationGroup.Insert.class, ValidationGroup.Update.class})
    private String roleName;
    private String roleComment;
    private String regUserId;
    private Date regDate;
    private String modUserId;
    private Date modDate;
}
Controller 에서 Vaild 처리
    @RequestMapping(method = RequestMethod.POST)
    public ResponseEntity add(@RequestBody @Validated(ValidationGroup.Insert.class) RolesVO vo, BindingResult result) throws Exception {
        /* validation */
         if(result.hasErrors()){
            ErrorResponse errorResponse = new ErrorResponse();
            errorResponse.setMessage("Wrong request!");
            errorResponse.setCode("bad.request");
            return new ResponseEntity(errorResponse, HttpStatus.BAD_REQUEST);
        }
        /* validation */
        return new ResponseEntity(modelMapper.map(service.addtRoles(vo), RolesVO.class), HttpStatus.CREATED);
    }

/ validation / 사이에 보이는 값은 대부분 중복 처리가 된다.
이 경우 중복된 코드가 너무 많이 반복되어 다음과 같이 처리 하였다.

Aspect를 이용한 공통 validat 처리

ValidCheckingInterceptor

@Aspect
@Component
public class ValidCheckingInterceptor {

    @Around("execution(* kr.pe.lahuman.*.controller.*.add*(..)) or execution(* kr.pe.lahuman.*.controller.*.modify*(..))")
    public Object anyMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] objs = joinPoint.getArgs();
        for(Object obj : objs){
            if(obj instanceof BindingResult){
                BindingResult result = (BindingResult)obj;
                if(result.hasErrors()){
                    Map<String, Map<String, String>> userdata = new HashMap<String, Map<String,String>>();
                    Map<String, String> errors = new LinkedHashMap<String, String>();
                    for (FieldError error : result.getFieldErrors()) {
                        errors.put(error.getField(), error.getDefaultMessage());
                    }
                    userdata.put("userdata", errors);
                    return new ResponseEntity(userdata, HttpStatus.BAD_REQUEST);
                }                 
            }
        }
        return joinPoint.proceed(joinPoint.getArgs());
    }
}

Around 어노테이션에서 add와 modify로 시작되는 메소드 중 인자로 BindingResult 가 있을 경우 유효성 체크를 하여 에러가 있을 경우 문제 필드와 기본 메시지를 Map 형식으로 return 한다.

변경후 Controller에서 Vaild 제거 된 소스

    @RequestMapping(method = RequestMethod.POST)
    public ResponseEntity add(@RequestBody @Validated(ValidationGroup.Insert.class) RolesVO vo, BindingResult result) throws Exception {
        return new ResponseEntity(modelMapper.map(service.addtRoles(vo), RolesVO.class), HttpStatus.CREATED);
    }

반복해서 작성 되었던 유효성 처리가 제거 되었다. 이후 Vaild 관련 처리는 ValidCheckingInterceptor에서만 진행 된다.


MD FILE : 

Aspect_BindingResult.md



Posted by lahuman

댓글을 달아 주세요

[TIP]SpringBoot 포트 및 ERROR 페이지 관련

원본 문서

1. HTTP Port 변경 방법

SpringBoot를 이용해서 서비스를 구축 하는 경우, PORT를 변경하고 싶을때 다음과 같이 하시면 됩니다.

  • application.properties 파일에서 server.port=8888 를 추가
  • Main(SpringBootApplication) Run 시 VM options 에 -Dserver.port=8888 를 추가

원본 문서에서는 management.port를 사용하면 된다고 하지만, 테스트 결과 server.port 만 동작하였습니다.

2. 사용자 정의 ‘whitelable’ 에러 페이지를 생성 하는 방법

SpringBoot는 서버에서 에러(클라이언트에서 JSON 형식이나 다른 미디어 타입으로 요청했을 경우 해당 타입에 맞는 올바른 에러 코드)가 발생할 경우 사용자에게 whitelabe 에러 페이지를 제공한다.
또한 server.error.whitelabel.enabled=false를 application.properties에 추가 하면 해당 에러 페이지를 사용하지 않을 수도 있다.
그러나 보통의 경우 사용자 정의 whilelabel 에러 페이지를 추가 하거나 대체 합니다. 정확하게 사용하는 템플릿 기술에 따라 달라집니다.
예를 들면 만약 Thymeleaf를 사용한다면 error.html 템플릿을 FreeMarker를 사용한다면 error.ftl 템플릿을 추가 합니다.
일반적으로 Viewerror이름으로 정의된 것과 @Controller에서 /error 주소가 필요합니다.
어떤 기본 구성은 당신의 ApplicationContext안에 BeanNameViewResolver를 찾아서 error id를 가진 @Bean 을 변경하는 하여야 됩니다.
더 많은 옵션은 ErrorMvcAutoConfiguration을 확인하셔요.

또한 서블릿 컨테이너에서 어떻게 Error Handling을 등록 하는 지를 보세요.

간단한 prototype을 작성할 경우는 기본 에러 페이지를 사용하는 것을 추천 드립니다.

참고 자료

- 포트 변경 관련 참고


MD FILE :

TIP_SpringBoot_port_error_change.md



Posted by lahuman

댓글을 달아 주세요

참고 URL : http://docs.spring.io/spring-boot/docs/current/reference/html/howto-hotswapping.html

72.6.2. Spring Loaded를 Gradle와 IntelliJ에서 설정하기

몇가지 단계를 지나면 Spring Loaded를 Gradle와 IntelliJ 에 결합하여 사용 하고 싶을 것이다. 기본적으로 Spring Loaded가 바라보는 classes의 컴파일되는 위치가 IntelliJ에서 Gradle일 경우 달라서 실패 할 것이다.

IntellJ에서 idea를 사용하여 Gradle plugin을 정확하게 설정 할 수 있다.

buildscript {
    repositories { jcenter() }
    dependencies {
        classpath "org.springframework.boot:spring-boot-gradle-plugin:1.2.7.RELEASE"
        classpath 'org.springframework:springloaded:1.2.0.RELEASE'
    }
}

apply plugin: 'idea'

idea {
    module {
        inheritOutputDirs = false
        outputDir = file("$buildDir/classes/main/")
    }
}

// ...

IntelliJ 는 Gradle task의 명령에서 동일한 Java 버젼을 사용하도록 설정 해야 하고, springloadedbuildscript 의 의존성을 포함 하고 있어야 한다.

또한 Make Project Automatically 가 사용하도록 하여 IntelliJ 안에서 파일이 저장되어 코드가 자동으로 컴파일 되게 해야 한다.

IntelliJ 설정 해보기

  1. 프로젝트 생성 하기
    • Gradle Project 생성
      • 생성시 디렉토리 자동 생성 설정
  2. 간단한 Spring-boot web 만들기

    • build.gradle 설정
      compile('org.springframework.boot:spring-boot-starter-web:1.2.7.RELEASE')
      
    • Application 만들기
      @SpringBootApplication
      public class Application {
      public static void main(String[] args){
         SpringApplication.run(Application.class, args);
      }
      }
      
    • IndexController 만들기
      @RestController
      public class IndexController {
      @RequestMapping("/")
      public String index(){
         return "Hello World";
      }
      }
      
    • 테스트
      http://localhost:8080/
      
  3. Spring Loaded 설정하기

    • build.gradle 설정 추가하기
      dependencies {
      testCompile group: 'junit', name: 'junit', version: '4.11'
      compile('org.springframework.boot:spring-boot-starter-web:1.2.7.RELEASE')
      compile('org.springframework:springloaded:1.2.4.RELEASE') //추가하여 javaagent 옵션 추가시 위치 가져옴 변경
      }
      buildscript {
      repositories { jcenter() }
      dependencies {
         classpath "org.springframework.boot:spring-boot-gradle-plugin:1.2.7.RELEASE"
         classpath 'org.springframework:springloaded:1.2.0.RELEASE'
      }
      }
      apply plugin: 'idea'
      idea {
      module {
         inheritOutputDirs = false
         outputDir = file("$buildDir/classes/main/")
      }
      }
      
    • Make Project Automatically 설정하기
      File > Settings > Build, Execution,Deployment > Commpiler > check MakeProject automatically
      Settings [이미지 중간에 Make Project automatically 체크]
    • Controller Method 추가 하기
      @RequestMapping("/addMethod")
      public String addMethod(){
         return "Add Method";
      }
      
    • 실행 설정에 javagent 추가 하기 참조정보
      javaagent [VM options: 추가]
      -javaagent:<pathTo>/springloaded-{VERSION}.jar -noverify
      
    • 추가한 내역 반영하기(아주 중요합니다!)
      Alt + F9 를 눌러 주세요.
    • 테스트
      http://localhost:8080/addMethod
  4. 추가 테스트 내용

    • method의 추가 삭제 변경에 대하여 재반영 됩니다.
    • 일반 class를 추가하는 것은 재반영 되지만, Controller Class를 추가 하는 것은 annotation 이 인식을 하지 않아서 재반영 되지 않습니다.
      • 이부분은 ApplicationContext에서 refresh() 를 호출할 경우 해결 할 수 있습니다.(Spring-boot 제외) 자세한 내용은 추후 다루겠습니다. 테스트만 해봐서, 사실은 저도 잘 몰라서 봐야 합니다.
    • 기존 class에 annotation 추가 수정 삭제는 동작 하였습니다.

설정 관련 동영상



Spring-Loaded Eclipse 설정 - WINDOWS

  1. Spring-loaded DOWNLOAD
  2. Tomcat VM 설정에 다음과 같이 추가
    -javaagent:<pathTo>\springloaded-1.2.5.RELEASE.jar -noverify
    
  3. 반영시 Servers 에서 Tomcat 서버를 선택하고 Ctrl + Alt + P 클릭



MD 파일 :


HotSwapping.md


Spring-Loaded_설정.md


Posted by lahuman

댓글을 달아 주세요

  1. lahuman 2016.02.21 23:20 신고  댓글주소  수정/삭제  댓글쓰기

    IDE에서는 꼭 ALT+ F9 (MAC: COMMAND + F9)를 이용하거나, build > make project 를 이용해아 정상적으로 된다.

    http://stackoverflow.com/questions/24371111/spring-boot-spring-loaded-intellij-gradle

원본 : https://github.com/spring-projects/spring-loaded

Spring Loaded

Spring Loaded 란?

JVM이 동작 중에 class 파일의 변경을 재반영해주는 JVM 대행자이다. 이는 로딩 시간에 class들을 늦은 재반영을 하도록 변형해줍니다.
hot code replace와 다르게 JVM이 단 한 번 기동 중에 쉽게 변경을 반영한다. (유래:Method 내용의 변경)
Spring Loaded는 메소드/필드/생성자에 대한 추가/변경/삭제를 지원합니다.
또한, 어노테이션은 타입/메소드/필드/생성자에 대한 변경을 지원하고 Enum 타입은 값에 대한 추가/수정/삭제가 가능합니다.

Spring Loaded 는 JVM에서 실행할 수 있는 bytecode를 사용한다. 또한, 정확하게는 재반영 체계는 Grails 2를 사용한다.

설치

agent jar를 내려받아 사용한다. 또한, 사용하기 전에 압출을 풀 필요가 없다.

동작 중 재반영

java -javaagent:<pathTo>/springloaded-{VERSION}.jar -noverify SomeJavaClass

증명자에 대한 옵션은 꺼야 합니다. (-noverify 옵션 관련) bytecode는 재작성 중 일부는 bytecode의 일부의 의미로 늘어나기 때문이다(번역 주: bytecode의 변경을 의미 하는 듯합니다.) 일단 시작하고 동작 중에 재반영되도록 대우 되지 않은 jar파일들에서(의존성을가진), 로드되는 모든 .class 파일들이 디스크에서 재반영할 수 있도록 만들어 효과적으로 클래스들이 로드되도록 한다. 한번 로드된 .class 파일은 감시될 것이다(초당 한 번씩) 그리고 새로운 버전이 나타나면 SpringLoaded는 그 것을 반영할 것이다. 모든 살아있는 클래스의 인스턴스는 즉시 새로운 객체를 바라볼 것이다, 인스턴스는 소멸과 재생성이 필요하지 않다.

의심의 여지가 없이 많은 질문과 희망적인 적절한 FAQ를 조만간 내놓을 것이다. 그러는 동안에 여기에서 얼마간 기본적인 것의 질의와 답변을 남긴다.

Q. class 파일이 변경되면 무조건 재반영 되는 것인지요?
A. 아니요, 당신은 유형의 계층 구조를 변경할 수 없습니다. 또한, 실제로 특정한 생성자 패턴을 사용하면 지금 처리 할 수 없습니다.

Q. 객체의 형태가 변경될 때 반영과는 어떤 관계가 있습니까?
A. 변경 결과는 시간이 지남에 따라 변경된 객체가 재반영 됩니다. 예를 들면, 새로운 메소드를 가진 변경된 클래스가 getDeclaredMethods()를 호출하기 전에 새로운 메소드를 바라보도록 합니다. 그러나 이 의미는 만약 당신의 시스템에서 캐시가 존재할 경우 그것을 제거하지 않으면 반영되지 않습니다. 이 경우는 지우고 다시 로드 해야 합니다.

Q. 어떻게 재반영이 실행 되었을 때 내가 알고 내 상태를 지우나요?
A. 플러그인을 설치해서 재반영이 일어났을 때 해당 활동을 확인할 수 있다. ReloadEventProcessPlugin을 상속받고 SpringLoadedPreProcessor.registerGlobalPlugin(plugin)을 통해 등록하면 된다. (희망적으로 다른 문서를 보면 등록하는 플러그인의 다른 방법이 있을 것이다.)

Q. 코드의 기본이 되는 것은 어떤 것입니까?
A. 이 기술은 Grails를 이용하여 성공적으로 재반영 되도록 하는 것입니다. 이것은 약간의 성능이 필요하고, 몇 가지 방법의 리펙토링 망치가 필요합니다. 이것은 여기저기에 invokedynamic 명령을 허용하는 것에 대한 향상이 필요하고 JAVA7의 새로운 정수 풀 항목과 연합합니다.

이 코드 동작시키기

git clone https://github.com/SpringSource/spring-loaded

한번 이 프로젝트를 eclipse에 적당한 곳에 복제해야 합니다. 메인 프로젝트와 몇 가지 테스트 프로젝트들을요. 테스트 프로젝트 중 하나는 AspectJ 프로젝트(Java와 AspectJ 코드 두 개에 포함됨) 와 다른 하나는 Groovy 프로젝트입니다. 이 테스트 프로젝트들을 Eclipse에서 컴파일하려면, 관련된 Eclipse 플러그인이 필요합니다.

AJDT : update site http://download.eclipse.org/tools/ajdt/42/dev/update
Groovy-Eclipse : update site : http://dist.springsource.org/snapshot/GRECLIPSE/e4.2/

이것을 가지고 온후 테스트를 동작시키세요. 이것은 두 가지 손으로 하는 방법과 생성하는 방법의 테스트가 있습니다. 모든 테스트를 포함하여 생성할 경우 오랜 시간이 걸릴 수 있습니다. 손으로 하는 방법은 테스트 JVM 기동 시 다음을 추가합니다.

-Dspringloaded.tests.generatedTests=false

NOTE : 또한 테스트 기동시도 -noverify 옵션이 JVM에서 필요합니다.

만약 이 프로젝트를 eclipse에 가져왔으면 실행되거나 실행되지 않을 경우의 생성된 테스트들이 이미 포함 되어 있습니다.

gradle 빌드 스크립트는, 에이전트를 다시 빌드하는 ./gradlew build 실행이 포함되어 있습니다. - 이것은 다음과 같이 생성됩니다. : springloaded/build/libs/springloaded-1.1.5.BUILD-SNAPSHOT.jar

내가 도움을 줄 수 있을까요?

물론이지요! 그냥 최상위의 github 페이지에서 Fork 버튼을 눌러 코드를 가져가셔요. 우리는 끌어오기 요청을 수락하기 전에 당신이 간단한 기여자의 계약서에 서명해야 합니다. - 여기를 확인하셔요. 기여자의 사인은 메인 저장소에 대한 권한을 위탁하지 않습니다, 그러나 이것은 우리가 당신의 공헌을 받아들일 수 있고, 우리가 필요할 경우 저자의 신용을 얻을 것입니다. 열정적인 기여자에게 우리의 코어 팀에서 함께 하기를 물을 것이며 풀 병합을 할 수 있는 권한을 줄 것입니다.


MD 파일 : 

SpringLoaded.md



Posted by lahuman

댓글을 달아 주세요


H2 Dababase Console


Spring 기반의 어플리케이션을 개발할때, 개발 환경에서 H2 메모리 데이터베이스를 사용할 것이다. 

(왜냐면, 이것은 가볍고, 빠르며, 또한 사용이 쉽다.)

일반적으로 큰 업무를 진행하는 운영 서버에서는 다른 RDMBS(Oracle, Mysql, Postgres)를 사용 한다.

Spring 어플리케이션과 함께 개발을 한다면, JAP/Hibernate 그리고 Hibernate의 스키마 생성 기능을 사용 할것이다.

어플리케이션이 시작할때, H2 DB와 함께라면, Hibernate에서는 DBMS를 항상 생성한다.

따라서 DB는 일관된 상태를 유지하게 된다. 또한 개발과 테스트에서 JPA mapping을 허용한다.


H2는 어플리케이션을 개발하는 동안, 웹 기반의 데이터베이스 콘솔을 함께 제공한다.

이것으로 Hibernate로 생성된 테이블이나 query를 통한 결과 값을 확인 수 있다.



[H2 데이터베이스 콘솔 예제]


Spring Boot에서 H2 데이터베이스를 사용하기 위한 설정


- H2 Maven 의존성


Spring boot 는 H2 데이터베이스를 지원한다. 만약 H2를 Spring 초기화에 추가하고 싶다면 Maven 의존성에 H2 의존성을 추가해야 한다.

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

이 설정은 Spring Boot 어플리케이션이 기동중에만 H2 데이터베이스가 동작한다. 그러나, H2 데이터베이스 콘솔을 사용하고 싶다면, Maven의 runtime scope를 complie로 변경해야한다.이것은 Spring Boot 어플리케이션의 설정을 변경하여 지원하도록 한다. 그냥, scope 에 대당하는 값을 지우면, Maven의 기본 값인 compile로 변경 될 것이다.

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>

- Spring 설정


보통은 web.xml 파일에 servlet 을 통해 H2 데이터베이스를 설정한다. 그러나 Spring Boot는 내부에 포함된 Tomcat 인스턴스로 동작하여, 우리는 web.xml파일에 접근 할수 없다. Spring Boot는ServletRegistationBean을 통하여 서빌릿을 선한하는데 사용하는 메커니즘을 제공한다.


다음은 Spring 설정을 통해 H2 데이터베이스 콘솔을 /console 위치로 servlet선언을 한다.

WebConfiguration.java

설명 - 적절한 WebServlet 클래스에서 설정하면 됩니다.(H2에서)

import org.h2.server.web.WebServlet;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class WebConfiguration {
    @Bean
    ServletRegistrationBean h2servletRegistration(){
        ServletRegistrationBean registrationBean = new ServletRegistrationBean( new WebServlet());
        registrationBean.addUrlMappings("/console/*");
        return registrationBean;
    }
}

만약 Spring Security를 사용하지 않는다면, H2 데이터베이스 콘솔을 위한 설정은 완료 되었다. Spring Boot 어플리케이션을 시작하고 H2 데이터베이스 콘솔에 접근하고 싶다면, http://localhost:8080/console 로 접근 하면 확인 할 수 있다.


- Spring Security 설정


Spring Boot 어플리케이션에서 Spring Security을 사용한다면, H2 데이터베이스 콘솔에 접근 할 수 없을 것이다. 

Spring Boot의 기본 설정 아래서는 Spring Security 에서 H2 데이터 베이스 콘솔에 접근을 차단 할 것이다.


H2 데이터베이스 콘솔에 대한 접근을 허용하려면 Spring Security의 아래의 세개를 변경 해야 한다.

  • /console/* 의 URL접근 허용
  • CRSF(Cross-Site Request Forgery) 중지. 기본 값이고, CRSF 공격에 대하여 방어
  • H2 데이터베이스 콘솔이 frame 안에서 동작 하도록 하는 Spring Security 옵션


다음의 Spring Security 설정을 진행 : 

  • root url("/") 에 대한 요청 허용(Line 12)
  • H2 데이터베이스 콘솔 url("/console/*") 요청에 대한 허용(Line 13)
  • CSRF 중지(Line 15)
  • X-Frame-Options in Spring Security 중지(Line 16)


경고 : 웹사이트에 대한 방어를 원한다면 다음의 Spring Security 설정을 사용하지 않기를 권장한다. 이 설정은 오직 Spring Boot 어플리케이션을 개발하고 H2 데이터베이스 콘솔의 접근을 지원 한다.

실운영 에서 H2 데이터 베이스 콘솔을 사용하는 데이터베이스의 예를 생각할 수 없다.

SecurityConfiguration.java


package guru.springframework.configuration;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests().antMatchers("/").permitAll().and()
                .authorizeRequests().antMatchers("/console/**").permitAll();

        httpSecurity.csrf().disable();
        httpSecurity.headers().frameOptions().disable();
    }

}


H2 데이터베이스 콘솔 사용


Spring Boot 웹 어플리케이션을 간단하게 시작하고 http://localhost:8080/console 을 URL에 입력 하면 다음과 같은 H2 데이터베이스 콘솔 화면을 볼수 있을 것이다.


[H2 데이터베이스 콘솔 로그인]


- Spring Boot에서 H2 데이터베이스 기본 설정


로그인 하기 전에, H2 데이터베이스 설정이 되어야 한다.

나는 Spring Boot를 이용한 기본 설정을 찾는것에 힘든 시간을 보냈고, Hibernate 로그를 이용해서 Spring Boot에서 사용되는 JDBC URL을 찾을 수 있었다.


Value 

Setting 

Driver Class 

org.h2.Driver 

JDBC URL 

jdbc:h2:mem:testdb 

User Name 

sa 

Password 

<blank> 



맺음말


나는 많은 프로젝트를 Grails framework를 이용해서 완료 하였다. Grails 팀은 H2 데이터베이스 콘솔을 Grails 2에 포함하여 배포 하였다. 나는 빠르게 이 기능에 빠져들었다. 물론 사랑은 아니지만, 내가 자주 사용하는 Grails의 특징이 되었다. 당신이 Spring /Hibernate(Grails과 함께) 사용해서 어플리케이션을 개발한다면, 당신에게 데이터베이스가 필요 할 것이다. H2 데이터베이스 콘솔은 당신의 업무처리에 도움이 되는 좋은 도구이다.


H2 데이터베이스 콘솔을 이용하지 않는다면, 데이터를 확인하는 것이 매우 어려운 일이 될 것이다.




PS : 좋은 자료를 공유 해주시고 또한, 번역을 허락 해준 John Thompson 님에게 감사드립니다.

Thank you for John Thompson.



원본 출처 : https://springframework.guru/using-the-h2-database-console-in-spring-boot-with-spring-security/

Posted by lahuman

댓글을 달아 주세요

Spring-Loaded 란?


JVM 기동중 class 파일의 변경을 반영해주도록 해주는 JVM 에이전트이다.

'hot code replace' 같이 JVM이 한번 기동된 이후 간단한 변경에 대한 허용(메소드 내용 변경)과 다르게 Spring Loaded는 메소드, 필드, 구조등에 대한 추가/변경/삭제를 허용한다.

types/methods/fields/constructors 어노테이션 또한 변경이 가능하고, enum타입들에 대한 추가/변경/삭제 가 가능하다.


설치 방법


현재(2015.8.20)기준으로 1.2.3 이 배포 : springloaded-1.2.3.RELEASE.jar


1.2.4 스냅샷 버젼이 배포 : repo.spring.io


실행 방법


java -javaagent:<pathTo>/springloaded-{VERSION}.jar -noverify SomeJavaClass


어떤 바이트 코드들 중에 재작성 운동을 하는 어떤 바이트 코드 때문에 verifier를 끈다.


사이트 : https://github.com/spring-projects/spring-loaded

Posted by lahuman

댓글을 달아 주세요