/* * Copyright 2009 Red Hat, Inc. * * Red Hat licenses this file to you 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 org.waarp.openr66.protocol.http.rest.client; import java.net.ConnectException; import java.nio.channels.ClosedChannelException; import java.nio.charset.UnsupportedCharsetException; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.LastHttpContent; import org.waarp.common.crypto.ssl.WaarpSslUtility; import org.waarp.common.json.JsonHandler; import org.waarp.common.logging.WaarpLogger; import org.waarp.common.logging.WaarpLoggerFactory; import org.waarp.common.utility.WaarpStringUtils; import org.waarp.gateway.kernel.exception.HttpIncorrectRequestException; import org.waarp.gateway.kernel.exception.HttpInvalidAuthenticationException; import org.waarp.gateway.kernel.rest.RestArgument; import org.waarp.gateway.kernel.rest.DataModelRestMethodHandler.COMMAND_TYPE; import org.waarp.gateway.kernel.rest.client.HttpRestClientSimpleResponseHandler; import org.waarp.gateway.kernel.rest.client.RestFuture; import org.waarp.openr66.protocol.http.rest.handler.HttpRestAbstractR66Handler.ACTIONS_TYPE; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; /** * Rest client response handler. * * Note: by default, no connection are closed except in case of error or if in HTTP 1.0 or explicitly to be closed. * * @author Frederic Bregier */ public abstract class HttpRestR66ClientResponseHandler extends SimpleChannelInboundHandler<HttpObject> { /** * Internal Logger */ private static final WaarpLogger logger = WaarpLoggerFactory.getLogger(HttpRestR66ClientResponseHandler.class); private ByteBuf cumulativeBody = null; protected JsonNode jsonObject = null; protected void addContent(FullHttpResponse response) throws HttpIncorrectRequestException { ByteBuf content = response.content(); if (content != null && content.isReadable()) { content.retain(); if (cumulativeBody != null) { cumulativeBody = Unpooled.wrappedBuffer(cumulativeBody, content); } else { cumulativeBody = content; } // get the Json equivalent of the Body try { String json = cumulativeBody.toString(WaarpStringUtils.UTF8); jsonObject = JsonHandler.getFromString(json); } catch (UnsupportedCharsetException e2) { logger.warn("Error", e2); throw new HttpIncorrectRequestException(e2); } cumulativeBody = null; } } /** * Setting the RestArgument to the RestFuture and validating RestFuture. * * @param channel * @throws HttpInvalidAuthenticationException */ protected void actionFromResponse(Channel channel) throws HttpInvalidAuthenticationException { boolean includeValidation = false; RestArgument ra = new RestArgument((ObjectNode) jsonObject); if (jsonObject == null) { logger.debug("Recv: EMPTY"); } RestFuture restFuture = channel.attr(HttpRestClientSimpleResponseHandler.RESTARGUMENT).get(); restFuture.setRestArgument(ra); switch (ra.getMethod()) { case CONNECT: break; case DELETE: includeValidation = delete(channel, ra); break; case GET: includeValidation = get(channel, ra); break; case HEAD: break; case OPTIONS: includeValidation = options(channel, ra); break; case PATCH: break; case POST: includeValidation = post(channel, ra); break; case PUT: includeValidation = put(channel, ra); break; case TRACE: break; default: break; } if (!includeValidation) { // finalize the future restFuture.setSuccess(); } } /** * Method calls when a action REST command is raised as answer * * @param channel * @param ra * @param act * @return if validation is done (or suppose to be) * @throws HttpInvalidAuthenticationException */ protected abstract boolean action(Channel channel, RestArgument ra, ACTIONS_TYPE act) throws HttpInvalidAuthenticationException; /** * Method calls when a REST Get command is raised as answer * * @param channel * @param ra * @return if validation is done (or suppose to be) * @throws HttpInvalidAuthenticationException */ protected abstract boolean afterDbGet(Channel channel, RestArgument ra) throws HttpInvalidAuthenticationException; /** * Method calls when a REST Post command is raised as answer * * @param channel * @param ra * @return if validation is done (or suppose to be) * @throws HttpInvalidAuthenticationException */ protected abstract boolean afterDbPost(Channel channel, RestArgument ra) throws HttpInvalidAuthenticationException; /** * Method calls when a REST Put command is raised as answer * * @param channel * @param ra * @return if validation is done (or suppose to be) * @throws HttpInvalidAuthenticationException */ protected abstract boolean afterDbPut(Channel channel, RestArgument ra) throws HttpInvalidAuthenticationException; /** * Method calls when a REST Delete command is raised as answer * * @param channel * @param ra * @return if validation is done (or suppose to be) * @throws HttpInvalidAuthenticationException */ protected abstract boolean afterDbDelete(Channel channel, RestArgument ra) throws HttpInvalidAuthenticationException; /** * Method calls when a REST GetMultiple command is raised as answer * * @param channel * @param ra * @return if validation is done (or suppose to be) * @throws HttpInvalidAuthenticationException */ protected abstract boolean afterDbGetMultiple(Channel channel, RestArgument ra) throws HttpInvalidAuthenticationException; /** * Method calls when a REST Options command is raised as answer * * @param channel * @param ra * @return if validation is done (or suppose to be) * @throws HttpInvalidAuthenticationException */ protected abstract boolean afterDbOptions(Channel channel, RestArgument ra) throws HttpInvalidAuthenticationException; /** * Method calls when a REST command is in error * * @param channel * @param ra * (might be null) * @return if validation is done (or suppose to be) * @throws HttpInvalidAuthenticationException */ protected abstract boolean afterError(Channel channel, RestArgument ra) throws HttpInvalidAuthenticationException; protected boolean get(Channel channel, RestArgument ra) throws HttpInvalidAuthenticationException { if (logger.isDebugEnabled()) { logger.debug(ra.prettyPrint()); } if (ra.getCommand() == COMMAND_TYPE.GET) { return afterDbGet(channel, ra); } else if (ra.getCommand() == COMMAND_TYPE.MULTIGET) { return afterDbGetMultiple(channel, ra); } else { String cmd = ra.getCommandField(); try { ACTIONS_TYPE act = ACTIONS_TYPE.valueOf(cmd); return action(channel, ra, act); } catch (Exception e) { return false; } } } protected boolean put(Channel channel, RestArgument ra) throws HttpInvalidAuthenticationException { if (logger.isDebugEnabled()) { logger.debug(ra.prettyPrint()); } if (ra.getCommand() == COMMAND_TYPE.UPDATE) { return afterDbPut(channel, ra); } else { String cmd = ra.getCommandField(); try { ACTIONS_TYPE act = ACTIONS_TYPE.valueOf(cmd); return action(channel, ra, act); } catch (Exception e) { return false; } } } protected boolean post(Channel channel, RestArgument ra) throws HttpInvalidAuthenticationException { if (logger.isDebugEnabled()) { logger.debug(ra.prettyPrint()); } if (ra.getCommand() == COMMAND_TYPE.CREATE) { return afterDbPost(channel, ra); } else { String cmd = ra.getCommandField(); try { ACTIONS_TYPE act = ACTIONS_TYPE.valueOf(cmd); return action(channel, ra, act); } catch (Exception e) { return false; } } } protected boolean delete(Channel channel, RestArgument ra) throws HttpInvalidAuthenticationException { if (logger.isDebugEnabled()) { logger.debug(ra.prettyPrint()); } if (ra.getCommand() == COMMAND_TYPE.DELETE) { return afterDbDelete(channel, ra); } else { String cmd = ra.getCommandField(); try { ACTIONS_TYPE act = ACTIONS_TYPE.valueOf(cmd); return action(channel, ra, act); } catch (Exception e) { return false; } } } protected boolean options(Channel channel, RestArgument ra) throws HttpInvalidAuthenticationException { if (logger.isDebugEnabled()) { logger.debug(ra.prettyPrint()); } if (ra.getCommand() == COMMAND_TYPE.OPTIONS) { return afterDbOptions(channel, ra); } else { String cmd = ra.getCommandField(); try { ACTIONS_TYPE act = ACTIONS_TYPE.valueOf(cmd); return action(channel, ra, act); } catch (Exception e) { return false; } } } @Override protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception { HttpObject obj = msg; if (obj instanceof HttpResponse) { HttpResponse response = (HttpResponse) msg; HttpResponseStatus status = response.status(); logger.debug(HttpHeaderNames.REFERER + ": " + response.headers().get(HttpHeaderNames.REFERER) + " STATUS: " + status); if (response.status().code() != 200) { if (response instanceof FullHttpResponse) { addContent((FullHttpResponse) response); } RestArgument ra = null; if (jsonObject != null) { ra = new RestArgument((ObjectNode) jsonObject); RestFuture restFuture = ctx.channel().attr(HttpRestClientSimpleResponseHandler.RESTARGUMENT).get(); restFuture.setRestArgument(ra); logger.error("Error: " + response.status().code() + " " + response.status().reasonPhrase() + "\n" + ra.prettyPrint()); } else { logger.error("Error: " + response.status().code() + " " + response.status().reasonPhrase()); } if (!afterError(ctx.channel(), ra)) { RestFuture restFuture = ctx.channel().attr(HttpRestClientSimpleResponseHandler.RESTARGUMENT).get(); restFuture.cancel(); } if (ctx.channel().isActive()) { logger.debug("Will close"); WaarpSslUtility.closingSslChannel(ctx.channel()); } } else { if (response instanceof FullHttpResponse) { addContent((FullHttpResponse) response); actionFromResponse(ctx.channel()); } } } else { HttpContent chunk = (HttpContent) msg; if (chunk instanceof LastHttpContent) { ByteBuf content = chunk.content(); if (content != null && content.isReadable()) { content.retain(); if (cumulativeBody != null) { cumulativeBody = Unpooled.wrappedBuffer(cumulativeBody, content); } else { cumulativeBody = content; } } // get the Json equivalent of the Body if (cumulativeBody == null) { jsonObject = JsonHandler.createObjectNode(); } else { try { String json = cumulativeBody.toString(WaarpStringUtils.UTF8); jsonObject = JsonHandler.getFromString(json); } catch (Throwable e2) { logger.warn("Error", e2); throw new HttpIncorrectRequestException(e2); } cumulativeBody = null; } actionFromResponse(ctx.channel()); } else { ByteBuf content = chunk.content(); if (content != null && content.isReadable()) { content.retain(); if (cumulativeBody != null) { cumulativeBody = Unpooled.wrappedBuffer(cumulativeBody, content); } else { cumulativeBody = content; } } } } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { RestFuture restFuture = ctx.channel().attr(HttpRestClientSimpleResponseHandler.RESTARGUMENT).get(); if (cause instanceof ClosedChannelException) { logger.debug("Close before ending"); restFuture.setFailure(cause); return; } else if (cause instanceof ConnectException) { if (ctx.channel().isActive()) { logger.debug("Will close"); restFuture.setFailure(cause); WaarpSslUtility.closingSslChannel(ctx.channel()); } return; } logger.warn("Error", cause); if (ctx.channel() != null && restFuture != null) { restFuture.setFailure(cause); } logger.debug("Will close"); WaarpSslUtility.closingSslChannel(ctx.channel()); } }