Clean Code that Works.

Spring Rest 사용할 때.

Spring2010. 11. 19. 10:20
Ajax 요청으로 Spring Rest 사용할 때 주의 해야 할 점이 있다.
일단 기본적으로 web.xml에 HiddenHttpMethodFilter는 당연히 설정 해야 되고.

만약에 ajax로 DELETE 요청을 날릴 때(ajax가 아닌 경우 method="DELETE"로 해주면 히든 필드(_method)에 DELETE가 담겨서 처리) 파라미터로 _method:'DELETE'를 같이 포함해서 넘겨 줘야 한다.

또, 주의 할 점이 ajax요청의 경우(jquery) 디폴트 type이 GET 이기 때문에 type : 'POST'로 지정을 해 줘야
HiddenHttpMethodFilter가 이를 인식하고 히든 필드에 담긴 method로 바꿔준다.
url : '${study.id}/board/imagePost/' + $this.attr('id'),
data : {_method: 'DELETE'},
type : 'POST',

HiddenHttpMethodFilter 코드를 보면 아래와 같이 POST로 넘어와야지 wrapper에서 메서드를 변경 해 준다.
@_@

@Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        String paramValue = request.getParameter(this.methodParam);
        if ("POST".equals(request.getMethod()) && StringUtils.hasLength(paramValue)) {
            String method = paramValue.toUpperCase(Locale.ENGLISH);
            HttpServletRequest wrapper = new HttpMethodRequestWrapper(request, method);
            filterChain.doFilter(wrapper, response);
        }
        else {
            filterChain.doFilter(request, response);
        }
    }



스트럿츠2 액션 요청을 전부다 ajax로 처리 하기 위하여 jquery를 도입했다.
파일 업로드 같은 경우는 스트럿츠2에 인터셉터로 통해 파일 업로드를 지원하는데.
멀티 파일 업로드는 ajax로 폼을 전송하였을 때는 값이 넘어 가지 않아서 처리하기가 곤란하다.

이전에는 프로토타입을 도입하여 ajax요청을 처리 하였는데.
멀티파일 업로드를 ajax로 처리하기 위하여 프로토타입을 걷어 내고 jquery로 ajax요청을 처리 하였다.

필요한 라이브러리
jquery.js : jquery core 라이브러리
jquery.form.js : form 을 ajax로 submit 하기 위한 라이브러리 
jquery.MultiFile.js : 멀티파일 업로드 구현을 위한 라이브러리

간단한 구현 시나리오를 살펴보면
멀티파일 업로드 뷰를 구현하고(html)
이를 전송하기 위한 폼 전송 메서드를 구현하고(javascript)
전송한걸 처리하는 actionclass를 구현하면 된다.

그럼 html 부분 부터 살펴보자.

<s:form action="fileUpload" id="fileUpload" method="post" enctype="multipart/form-data">
    <s:file name="file" label="File" id="file" cssClass="multi"/>
    <s:submit/>
</s:form>

아주 간단하다.
그냥 type="file" 인 태그에 class를 multi로 지정해 주면 된다.
일반 html 태그는 <input type="file" name="file" id="file" class="multi">로 지정해 주면 된다.

옵션으로는 class에 지정해 주거나, 속성으로 지정 가능하다.
class="multi max-4 accept-jpg|gif" => 업로드 4개로 제한, jpg, gif 제한
속성으로 지정 하는 방법은 위의 다운로드 사이트를 참고 하길 바란다.
다양한 예제가 준비 되어 있다.

여기까지는 단순히 뷰만 구현되어 있고.
이를 처리 하기 위한 ajax와 액션 클래스를 구성해야 한다.

그럼 ajax 부분 부터 살펴보자.

$(document).ready( function() {
 var options = {
         target:        '#output2',   // target element(s) to be updated with server response
         beforeSubmit:  showRequest,  // pre-submit callback
         success:       showResponse,  // post-submit callback
 
         // other available options:
         url:       'fileUpload.action'         // override for form's 'action' attribute
         type:      'post'        // 'get' or 'post', override for form's 'method' attribute
         //dataType:  null        // 'xml', 'script', or 'json' (expected server response type)
         //clearForm: true        // clear all form fields after successful submit
         //resetForm: true        // reset the form after successful submit
 
         // $.ajax options can be used here too, for example:
         //timeout:   3000
     };
 
     // bind to the form's submit event
     $('#fileUpload').submit(function() {
         // inside event callbacks 'this' is the DOM element so we first
         // wrap it in a jQuery object and then invoke ajaxSubmit
         $(this).ajaxSubmit(options);
 
         // !!! Important !!!
         // always return false to prevent standard browser submit and page navigation
         return false;
     });
});

 
// pre-submit callback
function showRequest(formData, jqForm, options) {
    // formData is an array; here we use $.param to convert it to a string to display it
    // but the form plugin does this for you automatically when it submits the data
    var queryString = $.param(formData);
 
    // jqForm is a jQuery object encapsulating the form element.  To access the
    // DOM element for the form do this:
    // var formElement = jqForm[0];
 
    alert('About to submit: \n\n' + queryString);
 
    // here we could return false to prevent the form from being submitted;
    // returning anything other than false will allow the form submit to continue
    return true;
}
 
// post-submit callback
function showResponse(responseText, statusText)  {
    // for normal html responses, the first argument to the success callback
    // is the XMLHttpRequest object's responseText property
 
    // if the ajaxSubmit method was passed an Options Object with the dataType
    // property set to 'xml' then the first argument to the success callback
    // is the XMLHttpRequest object's responseXML property
 
    // if the ajaxSubmit method was passed an Options Object with the dataType
    // property set to 'json' then the first argument to the success callback
    // is the json data object returned by the server
 
    alert('status: ' + statusText + '\n\nresponseText: \n' + responseText +
        '\n\nThe output div should have already been updated with the responseText.');
}

컥 길다.. -_-..showRequest, showResponse는 각각 콜백 함수임으로 별다른 설명은 하지 않겠다.
동작 원리를 살펴보면 폼의 submit을 새로운 함수로 바인딩해서
submit이 jQuery.form 에서 제공하는 ajaxSubmit()으로 실행 되도록 하는것이다.
jQuery.form 역시 해당 다운로드 사이트에 보면 완벽한 예제를 제공 하고 있다.

$(this).ajaxSubmit(options);
를 통해 ajax를 요청한다.

이제 파일 업로드를 구현 해야 한다.
fileUpload.action으로 액션을 요청 하는데.
스트럿츠2에서는 파일 업로드를 위한 인터셉터(fileUpload)를 제공한다.

스트럿츠 설정 파일인 strut.xml에 다음과 같이 액션매핑을 설정한다.

<action name="fileUpload" class="tutorial.FileUpload">
         <interceptor-ref name="fileUpload"/>
         <interceptor-ref name="basicStack"/>
         <result>index.jsp</result>
</action>

그 다음에 실제 파일을 업로드할 FileUpload클래스를 작성해주면 된다.

private File[] upload;
private String[] uploadContentType;
private String[] uploadFileName;
// 각 필드 get/setter 필요    
public String execute() throws Exception
{
       // 톰캣의 임시 폴더에 올라가 있는 파일을 서버에 저장하기 위한 로직이 필요.
       return SUCCESS;
}


위와 같이 액션 클래스를 작성해주고 디버깅을 실행해 주면 upload 변수에 임시서버에 올라간 파일명을 확인 할 수 있다. 이를 가지고 실제 서버에 파일을 쓰는 로직을 작성해 주면된다.

위와 같이 작성해 주면 ajax를 통해 파일 업로드를 수행 할 수 있다.
이것을 사용한 이유는 액션을 수행하고 나서(파일 업로드 후 디비에 파일 관련 정보 저장)
새로고침을 했을 경우 액션 url이 남아 있어서 해당 액션이 다시 수행되는 경우가 발생하였기 때문에
브라우저 url을 변경하지 않고 액션을 수행하기 위하여 이를 작성하였다.

물론 내 레벨이 낮기 때문에
이 포스트 수준역시 낮은 편이며.
아마도 액션을 다시 수행하는것을 막는 방법이 struts2에 존재할 것 같다.

jquery를 사용하여 RIA 애플리케이션 개발자에게 도움이 되었으면 좋겠다.
ps. 파일 쓰는 로직 및 파일들을 다시 작성하여 war 형태로 첨부 할 예정.

사용할 자바 스크립트 파일.
ajax.js = 최범균님의 Ajax 프로그래밍에 있는 파일(ajax 연결 관련 및 이벤트 처리).
log.js = 로그 출력용 파일.

시나리오
text영역에 keyup 이벤트가 발생 했을 경우
text.value를 얻어 와서 ajax를 통해 실행 후 데이터베이스에 id 값이 있는지 없는지 비교.
window.onload = function() {
    var id = document.getElementById("id");
    // 특정 요소에 keyup 리스너를 등록
    ajax.Event.addListener(id, "keyup", idHandler, false);
}
// keyup이벤트가 발생하면 아래 메서드가 실행된다.
function idHandler(event) {
    //log(document.regist.id.value);
    var params = "id=" + document.regist.id.value;
    new ajax.xhr.Request("registIdCheck.action", params, viewInfo, "GET");
}
//idHandler에서 실행된 결과를 idCheck란 id를 가진 요소에 출력해준다.
function viewInfo(req) {
    if(req.readyState == 4) {
        if(req.status == 200) {
            var idCheck = document.getElementById("idCheck");
            idCheck.innerHTML = req.responseText;
        }
    }
}

스트럿츠2 + iBatis를 해서 개발중.

iBatis 소스
<select id="selectCheckUserId" parameterClass="string" resultClass="string">
        SELECT id FROM member WHERE id = #id#
</select>
이렇게 해서 sqlMap.queryForObjec("selectCheckUserId", param);
실행하게되면 반환 값으로 select 한 id 값을 얻어 올 수 있다.

struts2 소스
@Override
    public String execute() throws Exception {
        if (registerService.selectCheckUserId(id) == true)
            return SUCCESS;
        return INPUT;
    }

반환 값이 없으면 null 이 return 되기 때문에 이를 가지고 반환 값이 없으면 success를
반환값이 있으면 input을 리턴해서

<action name="registIdCheck" class="net.DBLab.www.register.action.RegistIdCheck">
            <result name="success">../html/register/idCheckOK.jsp</result>
            <result name="input">../html/register/idCheck.jsp</result>
</action>
각각 result에 매핑된 경로 대로 이동해서 값을 출력한다.
idCheckOK는 아이디를 사용 할 수 있습니다.
idCheck는 아이디를 사용할 수 없습니다를 각각 출력한다.

사용자 삽입 이미지

사용할 수 없을 경우

사용자 삽입 이미지

사용할 수 있을 경우