본문 바로가기

Java/Vert.x

버텍스 코어: HTTP 서버와 HTTP 요청 처리

더보기

버텍스는 논-블로킹(Non blocking)으로 동작하는 HTTP 서버와 클라이언트를 제공합니다.

버텍스에서 지원하는 HTTP 프로토콜은 HTTP/1.0HTTP/1.1 그리고 HTTP/2.0입니다.

HTTP 서버를 생성하려면 Vertx 객체의 createHTTPServer()를 호출합니다.

HttpServer server = vertx.createHttpServer();

 

HTTP 서버 구성

HTTP 서버의 환경 설정을 변경하려면 HttpServerOptions를 사용하여 HTTP 서버를 생성합니다.

HttpServerOptions options = new HttpServerOptions().setMaxWebSocketFrameSize(1000000);
HttpServer server = vertx.createHttpServer(options);

예를 들어, 디버깅을 위해 네트워크 로깅을 활성화 하려면 다음과 같이 작성합니다.

HttpServerOptions options = new HttpServerOptions().setLogActivity(true);
HttpServer server = vertx.createHttpServer(options);

네트워크 로깅과 관련해서는 logging network activity 문서를 참고합니다.

HTTP/2.0  서버 구성

버텍스는 TLS h2TCP h2c를 통한 HTTP/2.0 프로토콜을 지원합니다.

TLS h2 TCP h2c
h2ALPN(Application-Layer Protocol Negotiation)에 의해서 TLS 통신합니다. h2c는 TLS를 사용하지 않고 TCP 일반 평문으로 통신합니다.
HTTP/1.1에 의해 직접 요청되거나 구성됩니다.

h2 요청을 처리하려면 TLS가 활성화 되어야 합니다. TLS를 켜려면 setUseAlpn을 사용합니다.

HttpServerOptions options = new HttpServerOptions()
    .setUseAlpn(true)
    .setSsl(true)
    .setKeyStoreOptions(new JksOptions().setPath("/path/to/my/keystore"));

HttpServer server = vertx.createHttpServer(options);

ALPN은 프로토콜을 협상하는 TLS의 확장입니다. ALPN을 사용하면 서버와 클라이언트가 데이터를 교환하기 전에 프로토콜을 결정합니다.

일반적으로 h2를 사용하도록 되어있으며, 서버와 클라이언트가 협의되어 있다면 HTTP/1.1을 사용하기도 합니다.

더보기

ALPN을 지원하지 않는 클라이언트는 전통적인 방법의 SSL 핸드셰이크를 사용합니다.

h2c 요청을 TLS는 비활성화되어야 합니다.

서버는 클라이언트의 HTTP/2.0 업그레이드 요청이 오면 HTTP/2.0으로 업그레이드합니다. 이 요청은 HTTP/1.1 통신으로 진행됩니다.

h2c 연결은 PRI * HTTP/2.0\r\nSM\r\n 평문을 통해서도 직접 요청 될 수 있습니다.

더보기

대부분의 웹 브라우저는 h2c를 지원하지 않습니다.

개발하는 애플리케이션이 웹 서비스를 제공한다면 h2c 대신 h2를 사용하는 것이 좋습니다.

서버가 HTTP/2.0 연결을 수락하면 클라이언트에게 초기 설정을 전달합니다.

전달되는 설정은 클라이언트가 연결을 사용하는 방법을 정의하며, 서버의 디폴트 설정 값은 다음과 같습니다.

더보기

워커 버티클(Worker verticle)HTTP/2와 호환되지 않습니다.

버티클 배포 옵션(DeploymentOptions)에서 setWorker()가 사용되지 않도록 주의합니다.

Listen으로 수신 대기

HTTP 서버는 요청을 수신하도록 listen 상태로 대기합니다. 디폴트 호스트는 0.0.0.0이며 포트는 80을 사용합니다.

0.0.0.0호스트에서 사용 가능한 모든 IP로의 요청을 수신합니다.

HttpServer server = vertx.createHttpServer();
server.listen();

또는 다음과 같이 인자로 호스트와 포트 정보를 입력합니다.

이 방법은 HttpServerOptions를 사용하여 지정된 호스트와 포트를 무시합니다.

HttpServer server = vertx.createHttpServer();
server.listen(8080, "myhost.com");

서버의 수신 대기는 비동기로 처리됩니다. 비동기 처리의 결과를 콜백 받으려면 핸들러를 등록합니다.

HttpServer server = vertx.createHttpServer();
server.listen(8080, "myhost.com", res -> {
	if (res.succeeded()) {
		System.out.println("Server is now listening!");
	} else {
		System.out.println("Failed to bind!");
	}
});

HTTP 요청 수신과 응답

HTTP 서버로의 요청을 수신하려면 requestHandler로 핸들러 등록이 필요합니다.

HttpServer server = vertx.createHttpServer();
server.requestHandler(request -> {
	// Handle the request in here
});

핸들러는 요청의 헤더(Header)를 완전히 읽었을 때 호출되며, 요청에 본문(Body)이 포함되어 있다면 핸들러가 호출 된 이후 얼마간의 시간 간격을 두고 본문이 도착합니다.

핸들러에는 uri path params 그리고 headers 등 HTTP 요청 정보를 읽을 수 있는 HttpServerRequest 객체가 전달됩니다.

각각의 HTTP 요청은 HTTP 응답과 연결됩니다.

HttpServerRequest 객체의 response()를 사용하면 HTTPServerResponse 객체를 얻을 수 있습니다.

vertx.createHttpServer().requestHandler(request -> {
	request.response().end("Hello world");
}).listen(8080);

HTTP 요청의 본문 수신

HTTP 요청에는 본문(Body)가 포함 될 수 있습니다.

requestHandler는 본문 여부와 관계 없이 헤더(Header)가 도착하는 즉시 콜백되므로 이 핸들러에서는 본문 정보를 읽을 수 없습니다.

더보기

본문에 앞서 헤더가 먼저 도착하는 이유는 본문이 대용량 데이터(e.g. 파일 전송)를 포함하는 경우 전체 본문을 메모리에 버퍼링 하고 싶지 않을 수 있기 때문입니다.

본문을 받으려면 요청에 대한 handler를 등록합니다. 이 핸들러는 요청에 대한 본문의 청크(Chunk)가 도착 할 때마다 호출됩니다.

request.handler(buffer -> {
	System.out.println("I have received a chunk of the body of length " + buffer.length());
});

본문 핸들러에는 Buffer가 전달됩니다. 전달된 버퍼는 본문의 청크이며, 본문의 크기에 따라서 여러 번 나누어 전달 될 수 있습니다.

Buffer totalBuffer = Buffer.buffer();

request.handler(buffer -> {
	System.out.println("I have received a chunk of the body of length " + buffer.length());
	totalBuffer.appendBuffer(buffer);
});

request.endHandler(v -> {
	System.out.println("Full body received, length = " + totalBuffer.length());
});

이처럼 본문이 청크로 전달 되는 것은 일반적입니다(예시의 endHandler는 본문을 포함한 모든 요청이 완료되었을 때 호출됩니다).

버텍스에서는 bodyHandler를 등록하여 모든 청크가 도착했을 때 단 한 번만 콜백되는 핸들러를 제공합니다.

request.bodyHandler(totalBuffer -> {
	System.out.println("Full body received, length = " + totalBuffer.length());
});

version()

version()는 HTTP 버전 HTTP/1.0 HTTP/1.1 HTTP/2.0을 리턴합니다. 

HTTP 버전은 HttpVersion에서 정의합니다.

HttpVersion version = ((HttpServerRequest) request).version();

method()

method()는 HTTP 메소드 GET POST PUT DELETE HEAD OPTIONS 등을 리턴합니다.

HTTP 메소드는 HttpMethod에서 정의합니다.

HttpMethod method = ((HttpServerRequest) request).method();

uri(), absoluteURI()

uri()는 HTTP 요청에 대한 URI를 리턴합니다(대부분의 경우 상대적인 URI를 리턴합니다).

실제 URI를 얻으려면 absoluteURI()를 사용합니다.

String relativeURI = ((HttpServerRequest) request).uri();
String absoluteURI = ((HttpServerRequest) request).absoluteURI();

URI와 관련해서는 RFC 2616에서의 정의를 참고합니다.

path()

path()는 요청 URI에서 리소스 경로에 해당하는 문자열을 리턴합니다.

예를 들어 URI a/b/c/page.html?param1=abc&param2=xyz에서 리소스 경로는 a/b/c/page.html입니다.

String path = ((HttpServerRequest) request).path();

query()

query()는 요청 URI에서 쿼리 파라미터(Query parameter)에 해당하는 문자열을 리턴합니다.

예를 들어 URI a/b/c/page.html?param1=abc&param2=xyz에서 쿼리 파라미터는 param1=abc&param2=xyz입니다.

String query = ((HttpServerRequest) request).query();

headers()

headers()는 HTTP 요청에서의 헤더 정보를 MultiMap으로 리턴합니다.

MultiMap은 HashMap과 비슷하지만 하나의 키에 대해서 여러 개의 값을 입력 할 수 있습니다.

더보기

HTTP 헤더는 하나의 키에 여러 개의 값을 입력 할 수 있습니다.

String user_agent = ((MultiMap)((HttpServerRequest) request).headers()).get("user-agent");

host()

host()는 HTTP 요청에 대한 호스트 정보를 리턴합니다.

HTTP/1.x에서는 호스트 헤더를 리턴합니다. HTTP/1.0에서는 authority 유사 헤더를 리턴합니다.

String host = ((HttpServerRequest) request).host();

params()

params()는 HTTP 요청의 매개 변수(Parameters)를 리턴합니다. header()와 마찬가지로 MultiMap을 사용합니다.

String param1 = ((MultiMap)((HttpServerRequest) request).params()).get("param1");
더보기

HTTP 요청의 매개 변수는 URI에서도 찾을 수 있습니다. 

예를 들어 /page.html?param1=abc&param2=xyz와 같은 URI에서는 MutiMap 인스턴스가 param1 abc param2 xyz 키/값에 대한 쌍을 포함합니다.

remoteAddress()

remoteAddress()는 HTTP 요청에 대한 클라이언트의 주소 SockerAddress를 리턴합니다.

SocketAddress host = ((HttpServerRequest) request).remoteAddress();

정리 및 복습

  • 버텍스의 HTTP 서버는 HTTP/1.0 HTTP/1.1 HTTP/2.0 프로토콜을 지원합니다.
  • h2 ALPN(Application-Layer Protocol Negotiation)에 의해서 TLS 통신합니다. h2를 사용하려면 TLS를 활성화 해야 합니다.
  • h2c는 TLS를 사용하지 않고 TCP 일반 평문으로 통신합니다. h2c를 사용하려면 TLS를 비활성화 해야 합니다.
  • HTTP 서버를 createHTTPServer()로 생성하고 listen 상태로 대기합니다.
  • 이때 디폴트 호스트 0.0.0.0 호스트에서 사용 가능한 모든 IP로의 요청을 수신하며, 디폴트 포트는 80입니다.
  • HTTP 요청이 수신되면 requestHandler에 등록된 핸들러가 먼저 콜백됩니다.
  • 이 때 요청의 헤더가 포함되며 본문을 읽으려면 bodyHandler에 핸들러를 등록합니다. 이 핸들러는 모든 본문이 도착했을 때 단 한 번만 호출됩니다.
  • 각 본문의 청크(Chunk)를 수신하려면 bodyHandler 대신 handler를 사용합니다. 이 핸들러는 각 청크가 도착할 때마다 호출됩니다.