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())
}
}
감사합니다!!
multipart/form-data
헤더가 첫 번째로 와야하는 함정 카드가 있었군요 😢