All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.gatling.http.client.impl.HttpAppHandler Maven / Gradle / Ivy

There is a newer version: 3.13.5
Show newest version
/*
 * Copyright 2011-2018 GatlingCorp (https://gatling.io)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.gatling.http.client.impl;

import io.gatling.http.client.HttpListener;
import io.gatling.http.client.HttpClientConfig;
import io.gatling.http.client.ahc.util.HttpUtils;
import io.gatling.http.client.impl.request.WritableRequest;
import io.gatling.http.client.impl.request.WritableRequestBuilder;
import io.gatling.http.client.pool.ChannelPool;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.DecoderResultProvider;
import io.netty.handler.codec.http.*;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

class HttpAppHandler extends ChannelDuplexHandler {

  private static final Logger LOGGER = LoggerFactory.getLogger(HttpAppHandler.class);

  private static final IOException PREMATURE_CLOSE = new IOException("Premature close") {
    @Override
    public synchronized Throwable fillInStackTrace() {
      return this;
    }
  };

  private final DefaultHttpClient client;
  private final ChannelPool channelPool;
  private final HttpClientConfig config;
  private HttpTx tx;
  private boolean httpResponseReceived;

  HttpAppHandler(DefaultHttpClient client, ChannelPool channelPool, HttpClientConfig config) {
    this.client = client;
    this.channelPool = channelPool;
    this.config = config;
  }

  private void setActive(HttpTx tx) {
    this.tx = tx;
  }

  private void setInactive() {
    tx = null;
    httpResponseReceived = false;
  }

  private boolean isInactive() {
    return tx == null || tx.requestTimeout.isDone();
  }

  private void crash(ChannelHandlerContext ctx, Throwable cause, boolean close) {
    if (isInactive()) {
      return;
    }
    crash0(ctx, cause, close, tx);
  }

  private void crash0(ChannelHandlerContext ctx, Throwable cause, boolean close, HttpTx tx) {
    try {
      tx.requestTimeout.cancel();
      tx.listener.onThrowable(cause);
      setInactive();

    } catch (Exception e) {
      LOGGER.error("Exception while handling HTTP/1.1 crash, please report to Gatling maintainers", e);
    } finally {
      if (close) {
        ctx.close();
      }
    }
  }

  @Override
  public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {

    HttpTx tx = (HttpTx) msg;
    setActive(tx);

    if (tx.requestTimeout.isDone()) {
      setInactive();
      return;
    }

    try {
      WritableRequest request = WritableRequestBuilder.buildRequest(tx.request, ctx.alloc(), config, false);
      tx.closeConnection = HttpUtils.isConnectionClose(request.getRequest().headers());

      LOGGER.debug("Write request {}", request);

      request.write(ctx);
    } catch (Exception e) {
      crash(ctx, e, true);
    }
  }

  private boolean exitOnDecodingFailure(ChannelHandlerContext ctx, DecoderResultProvider message) {
    Throwable t = message.decoderResult().cause();
    if (t != null) {
      crash(ctx, t, true);
      return true;
    }
    return false;
  }

  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) {
    if (isInactive()) {
      return;
    }

    LOGGER.debug("Read msg='{}'", msg);

    try {
      if (msg instanceof HttpResponse) {
        httpResponseReceived = true;
        HttpResponse response = (HttpResponse) msg;
        if (exitOnDecodingFailure(ctx, response)) {
          return;
        }
        tx.listener.onHttpResponse(response.status(), response.headers());
        tx.closeConnection = tx.closeConnection && HttpUtils.isConnectionClose(response.headers());

      } else if (msg instanceof HttpContent) {
        HttpContent chunk = (HttpContent) msg;
        if (exitOnDecodingFailure(ctx, chunk)) {
          return;
        }
        boolean last = chunk instanceof LastHttpContent;

        // making a local copy because setInactive might be called (on last)
        HttpTx tx = this.tx;

        if (last) {
          tx.requestTimeout.cancel();
          setInactive();
          if (tx.closeConnection) {
            ctx.channel().close();
          } else {
            channelPool.offer(ctx.channel());
          }
        }

        try {
          tx.listener.onHttpResponseBodyChunk(chunk.content(), last);
        } catch (Exception e) {
          // can't let exceptionCaught handle this because setInactive might have been called (on last)
          crash0(ctx, e, true, tx);
          throw e;
        }
      }
    } finally {
      ReferenceCountUtil.release(msg);
    }
  }

  @Override
  public void channelInactive(ChannelHandlerContext ctx) {
    if (isInactive()) {
      return;
    }

    if (!httpResponseReceived && client.retry(tx, ctx.channel().eventLoop())) {
      // only retry when we haven't started receiving response
      setInactive();
    } else {
      crash(ctx, PREMATURE_CLOSE, false);
    }
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
    crash(ctx, cause, true);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy