Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ihoneymon/a83b22a42795b349c389a883a7bbf356 to your computer and use it in GitHub Desktop.
Save ihoneymon/a83b22a42795b349c389a883a7bbf356 to your computer and use it in GitHub Desktop.
nGrinder 파일 업로드 테스트 스크립트

20190322 nGrinder file upload(multipart/form-data) 기능 확인

nGrinder 를 이용한 성능테스트를 작성하고 있다. 이 과정에서 다른 시스템과 연계하는 과정에서 운영으로 넘어가기 위한 성능테스트를 준비하고 있는데, 파일업로드를 하는 과정이 필요했다.

이 구현코드는 여기저기 찾아보다가 grinder script gallery가 제일 깔끔하게 정리되어있다.

@RunWith(GrinderRunner)
class HyosungCmsMemberAgreementUploadTestBase extends TestBase {
    @Before
    void before() {
        def boundary = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"))
        headers = [
                new NVPair("Authorization", "Bearer api-key"),
                new NVPair("Content-Type", "multipart/form-data;")
        ]
        request.setHeaders(headers)
        cookies.each { CookieModule.addCookie(it, HTTPPluginControl.getThreadHTTPClientContext()) }

        grinder.logger.info("before thread. init headers and cookies")
    }


    @Test
    void test() {
        grinder.logger.debug("headers[0]: ${headers[0]}, headers[1]: ${headers[1]}")

        def num = getNumber()
        def obj = jsonSlurper.parseText new File("$num-member-register.json").text

        //given
        NVPair[] params = [new NVPair("run number", "$grinder.runNumber")]
        NVPair[] files = [new NVPair("agreement", "src/main/resources/agreement-dummy.pdf")]

        def data = Codecs.mpFormDataEncode(params, files, headers)

        //when
        HTTPResponse result = request.POST("$host/v1/$obj.data.id/agreement", data)

        //then
        grinder.logger.info("Result: {}", result)
        grinder.logger.info("Result text: {}", result.getText())
    }
}

위 코드를 실행하면 다음과 같은 오류메시지를 확인할 수 있다.

Failed to parse multipart servlet request; nested exception is java.io.IOException: org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found

멀티파트 요청에서 boundary 값을 찾지 못해서 반려(리젝, reject)한다고 한다. 그래서 다음과 같이 변경해보면

headers = [
        new NVPair("Authorization", "Bearer api-key"),
        new NVPair("Content-Type", "multipart/form-data; boundary=--------------------------1234")
]

Authorization 값을 찾지 못한다는 슬픈 이야기가 들린다. 왜그런지 머리를 감싸쥐고 외쳐봤지만 쉽게 찾을 수 없었다.

위 코드는 아무리 실행해도 Authorization를 읽어오는 과정에서 오류가 발생한다. 그 원인이 무엇인지 이해하는데 상당한 시간을 소요했다. 위 코드를 다음과 같이 수정하면 정상적으로 수행된다.

Codecs.mpFormDataEncode를 열어보면

public static final byte[] mpFormDataEncode(NVPair[] opts, NVPair[] files, NVPair[] ct_hdr) throws IOException {
    return mpFormDataEncode(opts, files, ct_hdr, (FilenameMangler)null);
}

public static final byte[] mpFormDataEncode(NVPair[] opts, NVPair[] files, NVPair[] ct_hdr, FilenameMangler mangler) throws IOException {
    //  생략
    if (pos != len) {
        throw new Error("Calculated " + len + " bytes but wrote " + pos + " bytes!");
    } else {
        ct_hdr[0] = new NVPair("Content-Type", "multipart/form-data; boundary=" + new String(boundary, 4, boundary.length - 4, "8859_1"));
        return res;
    }

}

요런 코드를 볼 수 있다. 크흐…​ 요걸 찾아내고 이해하는데 삽질을…​

문제가 되는 부분이 바로!! ct_hdr[0] 이다. 위에서 다음과 같이 선언한 headers

headers = [
    new NVPair("Authorization", "Bearer api-key"),
    new NVPair("Content-Type", "multipart/form-data; boundary=--------------------------$boundary")
]

부분이 Codecs.mpFormDataEncode를 거치면,

headers = [
    new NVPair("Content-Type", "multipart/form-data; boundary=1231238974598123656")),
    new NVPair("Content-Type", "multipart/form-data; boundary=--------------------------$boundary")
]

으로 변경되면서 Authorization 헤더가 날아가버린다. 크흐…​.

아래가 손질을 마친 완성본…​

@RunWith(GrinderRunner)
class HyosungCmsMemberAgreementUploadTestBase extends TestBase {
    @Before
    void before() {
        headers = [
                new NVPair("Content-Type", "multipart/form-data"),
                new NVPair("Authorization", "Bearer api-key")
        ]
        request.setHeaders(headers)
        cookies.each { CookieModule.addCookie(it, HTTPPluginControl.getThreadHTTPClientContext()) }

        grinder.logger.info("before thread. init headers and cookies")
    }


    /**
     * @link <ahref="http://ngrinder.642.n7.nabble.com/post-multipart-form-data-td2362.html"        >        Ngrinder Upload file</a>
     */
    @Test
    void test() {
      grinder.logger.debug("headers[0]: ${headers[0]}, headers[1]: ${headers[1]}")

     def num = getNumber()
     def obj = jsonSlurper.parseText new File("$num-member-register.json").text

     //given
     NVPair[] params = [new NVPair("run number", "$grinder.runNumber")]
     NVPair[] files = [new NVPair("agreement", "src/main/resources/agreement-dummy.pdf")]

     def data = Codecs.mpFormDataEncode(params, files, headers)

     //when
     HTTPResponse result = request.POST("$host/v1/$obj.data.id/agreement", data)

     //then
     grinder.logger.info("Result: {}", result)
     grinder.logger.info("Result text: {}", result.getText())
    }
}

참고

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment