일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- docker
- Spring Batch
- hadoop
- 인텔리J
- spark
- elastic search
- intellij
- DDD
- 제주
- hdfs
- Storm
- Java
- 도메인주도설계
- Spring
- design pattern
- scala
- Linux
- Clean Code
- nginx
- Spring Boot
- 엘라스틱서치
- SBT
- elasticsearch
- Angular2
- 스프링 배치
- Hbase
- Gradle
- Spring XD
- apache storm
- hibernate
- Today
- Total
욱'S 노트
Tomcat Servlet 3.1 예제 및 동작구조 본문
개요
어제는 일단 인텔리J기반에서 서블릿을 실행해보았다. 애초에 목적이 Servlet 3.1이 어떻게 동작하는지 알아보기 위함이므로 아주 간단한 Servlet 3.1 예제를 작성해보자.
https://opennote46.tistory.com/257
Servlet 3.1 예제 작성
생각보다 Servlet 3.1 예제가 많지 않다. 오라클 공식 사이트에 있는 예제를 기반으로 작성해보았다.
먼저 데이터를 읽을때 사용하는 ReadListener를 구현한다. 각 메소드의 호출 시점은 javadoc에 자세히 설명되어 있지만 간단하게 주석을 달아보았다.
class ReadListenerImpl(val input: ServletInputStream, val res: HttpServletResponse?, val ac: AsyncContext) : ReadListener {
val queue = LinkedBlockingQueue<String>()
// 데이터를 읽을 수 있을 때 호출
override fun onDataAvailable() {
println("Data is available")
val sb = StringBuilder()
var len = -1
val b = ByteArray(1024)
while (input.isReady && (input.read(b).also { len = it }) != -1) {
val data = String(b, 0, len)
sb.append(data)
}
queue.add(sb.toString())
}
// 모든 데이터를 읽었을 때 호출
override fun onAllDataRead() {
println("Data is all read")
// now all data are read, set up a WriteListener to write
val output = res?.outputStream!!
val writeListener: WriteListener = WriteListenerImpl(output, queue, ac)
output.setWriteListener(writeListener)
}
// 에러가 발생했을 때 호출
override fun onError(t: Throwable?) {
ac.complete()
t?.printStackTrace()!!
}
}
다음은 WriteListener을 작성한다.
class WriteListenerImpl(val output: ServletOutputStream, val queue: LinkedBlockingQueue<String>, val ac: AsyncContext) : WriteListener {
// 데이터를 쓸 수 있을 때 호출
override fun onWritePossible() {
println("Write is possible")
while (output.isReady && queue.peek() != null) {
val data = queue.poll()
output.print(data)
}
if (queue.peek() == null) {
ac.complete()
}
}
// 에러가 발생했을 때 호출
override fun onError(t: Throwable?) {
ac.complete();
t?.printStackTrace()!!
}
}
그리고 Servlet을 하나 구현해보자. 기능은 아주 단순하게 POST 방식으로 전달받은 RequestBody를 그대로 출력하는 것이다.
@WebServlet("/upload", asyncSupported = true)
class UploadServlet : HttpServlet() {
override fun doPost(req: HttpServletRequest?, resp: HttpServletResponse?) {
val context: AsyncContext = req?.startAsync()!!
context.addListener(object : AsyncListener {
override fun onComplete(event: AsyncEvent?) {
println("onComplete")
event?.suppliedRequest
}
override fun onTimeout(event: AsyncEvent?) {
println("onTimeout")
}
override fun onError(event: AsyncEvent?) {
println("onError")
event?.throwable?.printStackTrace()!!
}
override fun onStartAsync(event: AsyncEvent?) {
println("onStartAsync")
}
})
val input = req.inputStream
val readListener: ReadListener = ReadListenerImpl(input, resp, context)
input.setReadListener(readListener)
}
}
테스트
CURL이나 PostMan과 같은 툴을 이용하면 응답이 제대로 돌아오는 것을 알 수 있다. 요청 주소는 http://localhost:8080/{contextPath}/upload 형식이다.
curl --location 'http://localhost:8080/Gradle___com_kakao___sample_1_0_SNAPSHOT_war__exploded_/upload' \
--header 'Content-Type: text/plain' \
--data 'Hello World'
동작 구조
Poller가 이벤트를 전달하고 소켓에서 OPEN_READ 이벤트가 발생하면 Request의 ReadListener의 DataAvailable을 수행한다. 만약 읽기가 완료되면 OnDataAllRead() 호출한다. 이후 OPEN_WRITE 이벤트가 발생하면 Response의 WriterListener의 onWritePossible 연산이 수행되며, response에 쓰기 연산을 수행한다. 기존 서블릿은 읽기가 완료될 때 까지 블럭킹이 되었다면 서블릿 3.1에서는 논블럭킹으로 읽기 쓰기 연산이 수행된다.
'Programming > Tomcat' 카테고리의 다른 글
내장 톰캣(Embedded Tomcat) 설정 및 실행 하기 (0) | 2025.01.02 |
---|---|
IntelliJ Tomcat 기반 서블릿 테스트 (0) | 2024.12.30 |
Tomcat - 시작 속도 빠르게 하기 (0) | 2015.08.06 |