com.vmware.dcp.common.http.netty.NettyHttpServerResponseHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.dcp.common.http.netty.NettyHttpServerResponseHandler.java

Source

/*
 * Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved.
 *
 * 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 com.vmware.dcp.common.http.netty;

import java.net.ProtocolException;
import java.util.EnumSet;
import java.util.Map.Entry;
import java.util.logging.Logger;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpResponse;

import com.vmware.dcp.common.Operation;
import com.vmware.dcp.common.ServiceErrorResponse;
import com.vmware.dcp.common.ServiceErrorResponse.ErrorDetail;
import com.vmware.dcp.common.Utils;

/**
 * Processes responses from a remote HTTP server and completes the request associated with the
 * channel
 */
public class NettyHttpServerResponseHandler extends SimpleChannelInboundHandler<HttpObject> {

    private NettyChannelPool pool;

    public NettyHttpServerResponseHandler(NettyChannelPool pool) {
        this.pool = pool;
    }

    @Override
    public void messageReceived(ChannelHandlerContext ctx, HttpObject msg) {
        Operation request = ctx.channel().attr(NettyChannelContext.OPERATION_KEY).get();
        if (msg instanceof FullHttpResponse) {
            FullHttpResponse response = (FullHttpResponse) msg;
            request.setStatusCode(response.status().code());
            parseResponseHeaders(request, response);
            completeRequest(ctx, request, response.content());
        }
    }

    private void parseResponseHeaders(Operation request, HttpResponse nettyResponse) {
        HttpHeaders headers = nettyResponse.headers();
        if (headers.isEmpty()) {
            return;
        }

        request.setKeepAlive(HttpHeaderUtil.isKeepAlive(nettyResponse));
        if (HttpHeaderUtil.isContentLengthSet(nettyResponse)) {
            request.setContentLength(HttpHeaderUtil.getContentLength(nettyResponse));
        }

        for (Entry<String, String> h : headers.entriesConverted()) {
            String key = h.getKey();
            String value = h.getValue();

            if (key.equalsIgnoreCase(HttpHeaderNames.CONTENT_TYPE.toString())) {
                request.setContentType(value);
                continue;
            }
            request.addResponseHeader(key, value);
        }
    }

    private void completeRequest(ChannelHandlerContext ctx, Operation request, ByteBuf content) {
        decodeResponseBody(request, content);
        this.pool.returnOrClose((NettyChannelContext) request.getSocketContext(), !request.isKeepAlive());
    }

    private void decodeResponseBody(Operation request, ByteBuf content) {
        if (!content.isReadable()) {
            if (checkResponseForError(request)) {
                return;
            }
            // skip body decode, request had no body
            request.setContentLength(0).setContentType(Operation.MEDIA_TYPE_APPLICATION_JSON);
            request.complete();
            return;
        }

        request.nestCompletion((o, e) -> {
            if (e != null) {
                request.fail(e);
                return;
            }
            completeRequest(request);
        });

        Utils.decodeBody(request, content.nioBuffer());
    }

    private void completeRequest(Operation request) {
        if (checkResponseForError(request)) {
            return;
        }
        request.complete();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        Logger.getAnonymousLogger().warning(Utils.toString(cause));
        Operation request = ctx.channel().attr(NettyChannelContext.OPERATION_KEY).get();

        if (request == null) {
            Logger.getAnonymousLogger().info("no request associated with channel");
            return;
        }

        // I/O exception this code recommends retry since it never made it to the remote end
        request.setStatusCode(Operation.STATUS_CODE_BAD_REQUEST);
        request.setBody(
                ServiceErrorResponse.create(cause, request.getStatusCode(), EnumSet.of(ErrorDetail.SHOULD_RETRY)));
        request.fail(cause);
    }

    private boolean checkResponseForError(Operation op) {
        if (op.getStatusCode() < Operation.STATUS_CODE_FAILURE_THRESHOLD) {
            return false;
        }
        String errorMsg = String.format("Service %s returned error %d for %s: %s", op.getUri(), op.getStatusCode(),
                op.getAction(), Utils.toJsonHtml(op.getBodyRaw()));

        if (!op.hasBody()) {
            op.setBodyNoCloning(Utils.toServiceErrorResponse(new ProtocolException(errorMsg)));
        }

        op.fail(new ProtocolException(errorMsg));
        return true;
    }
}