/* * Copyright (c) 2011-2013 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package io.vertx.core.http.impl; import io.netty.handler.codec.http.DefaultHttpHeaders; import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.HttpClientResponse; import io.vertx.core.http.HttpFrame; import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpVersion; import io.vertx.core.net.NetSocket; import java.util.ArrayList; import java.util.List; /** * This class is optimised for performance when used on the same event loop that is was passed to the handler with. * However it can be used safely from other threads. * * The internal state is protected using the synchronized keyword. If always used on the same event loop, then * we benefit from biased locking which makes the overhead of synchronized near zero. * * @author <a href="http://tfox.org">Tim Fox</a> */ public class HttpClientResponseImpl implements HttpClientResponse { private final HttpVersion version; private final int statusCode; private final String statusMessage; private final HttpClientRequestBase request; private final HttpClientConnection conn; private final HttpClientStream stream; private Handler<Buffer> dataHandler; private Handler<HttpFrame> customFrameHandler; private Handler<Void> endHandler; private Handler<Throwable> exceptionHandler; private boolean hasPausedEnd; private boolean paused; private Buffer pausedLastChunk; private MultiMap pausedTrailers; private NetSocket netSocket; // Track for metrics private long bytesRead; // Cache these for performance private MultiMap headers; private MultiMap trailers; private List<String> cookies; HttpClientResponseImpl(HttpClientRequestBase request, HttpVersion version, HttpClientStream stream, int statusCode, String statusMessage, MultiMap headers) { this.version = version; this.statusCode = statusCode; this.statusMessage = statusMessage; this.request = request; this.stream = stream; this.conn = stream.connection(); this.headers = headers; } @Override public HttpClientRequestBase request() { return request; } @Override public HttpVersion version() { return version; } @Override public int statusCode() { return statusCode; } @Override public String statusMessage() { return statusMessage; } @Override public MultiMap headers() { return headers; } @Override public String getHeader(String headerName) { return headers().get(headerName); } @Override public String getHeader(CharSequence headerName) { return headers().get(headerName); } @Override public MultiMap trailers() { synchronized (conn) { if (trailers == null) { trailers = new HeadersAdaptor(new DefaultHttpHeaders()); } return trailers; } } @Override public String getTrailer(String trailerName) { return trailers != null ? trailers.get(trailerName) : null; } @Override public List<String> cookies() { synchronized (conn) { if (cookies == null) { cookies = new ArrayList<>(); cookies.addAll(headers().getAll(HttpHeaders.SET_COOKIE)); if (trailers != null) { cookies.addAll(trailers.getAll(HttpHeaders.SET_COOKIE)); } } return cookies; } } @Override public HttpClientResponse handler(Handler<Buffer> dataHandler) { synchronized (conn) { this.dataHandler = dataHandler; return this; } } @Override public HttpClientResponse endHandler(Handler<Void> endHandler) { synchronized (conn) { this.endHandler = endHandler; return this; } } @Override public HttpClientResponse exceptionHandler(Handler<Throwable> exceptionHandler) { synchronized (conn) { this.exceptionHandler = exceptionHandler; return this; } } @Override public HttpClientResponse pause() { synchronized (conn) { if (!paused) { paused = true; stream.doPause(); } return this; } } @Override public HttpClientResponse resume() { synchronized (conn) { if (paused) { paused = false; doResume(); stream.doResume(); } return this; } } @Override public HttpClientResponse bodyHandler(final Handler<Buffer> bodyHandler) { BodyHandler handler = new BodyHandler(); handler(handler); endHandler(v -> handler.notifyHandler(bodyHandler)); return this; } @Override public HttpClientResponse customFrameHandler(Handler<HttpFrame> handler) { synchronized (conn) { customFrameHandler = handler; return this; } } private void doResume() { if (hasPausedEnd) { final Buffer theChunk = pausedLastChunk; final MultiMap theTrailer = pausedTrailers; stream.getContext().runOnContext(v -> handleEnd(theChunk, theTrailer)); hasPausedEnd = false; pausedTrailers = null; pausedLastChunk = null; } } void handleUnknowFrame(HttpFrame frame) { synchronized (conn) { if (customFrameHandler != null) { try { customFrameHandler.handle(frame); } catch (Throwable t) { handleException(t); } } } } void handleChunk(Buffer data) { synchronized (conn) { request.dataReceived(); bytesRead += data.length(); if (dataHandler != null) { try { dataHandler.handle(data); } catch (Throwable t) { handleException(t); } } } } void handleEnd(Buffer lastChunk, MultiMap trailers) { synchronized (conn) { conn.reportBytesRead(bytesRead); bytesRead = 0; if (paused) { pausedLastChunk = lastChunk; hasPausedEnd = true; pausedTrailers = trailers; } else { if (lastChunk != null) { handleChunk(lastChunk); } this.trailers = trailers; if (endHandler != null) { try { endHandler.handle(null); } catch (Throwable t) { handleException(t); } } request.handleResponseEnd(); } } } void handleException(Throwable e) { synchronized (conn) { if (exceptionHandler != null) { exceptionHandler.handle(e); } } } @Override public NetSocket netSocket() { synchronized (conn) { if (netSocket == null) { netSocket = stream.createNetSocket(); } return netSocket; } } private static final class BodyHandler implements Handler<Buffer> { private Buffer body; @Override public void handle(Buffer event) { body().appendBuffer(event); } private Buffer body() { if (body == null) { body = Buffer.buffer(); } return body; } void notifyHandler(Handler<Buffer> bodyHandler) { bodyHandler.handle(body()); // reset body so it can get GC'ed body = null; } } }