/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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.apache.cxf.transport.http.netty.server; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.cxf.common.i18n.Message; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.interceptor.Fault; import org.apache.cxf.transport.http.netty.server.interceptor.NettyInterceptor; import org.apache.cxf.transport.http.netty.server.servlet.NettyHttpServletRequest; import org.apache.cxf.transport.http.netty.server.servlet.NettyServletResponse; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.group.ChannelGroup; import io.netty.handler.codec.TooLongFrameException; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.DefaultHttpResponse; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpHeaders.Names; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; import io.netty.util.CharsetUtil; public class NettyHttpServletHandler extends ChannelInboundHandlerAdapter { private static final Logger LOG = LogUtils.getL7dLogger(NettyHttpServletHandler.class); private final ChannelGroup allChannels; private final NettyHttpServletPipelineFactory pipelineFactory; private List<NettyInterceptor> interceptors; public NettyHttpServletHandler(NettyHttpServletPipelineFactory pipelineFactory) { this.allChannels = pipelineFactory.getAllChannels(); this.pipelineFactory = pipelineFactory; } public NettyHttpServletHandler addInterceptor( NettyInterceptor interceptor) { if (this.interceptors == null) { this.interceptors = new ArrayList<>(); } this.interceptors.add(interceptor); return this; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { LOG.log(Level.FINE, "Opening new channel: {}", ctx.channel()); // Agent map allChannels.add(ctx.channel()); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof IdleStateEvent) { IdleStateEvent e = (IdleStateEvent) evt; if (e.state() == IdleState.READER_IDLE || e.state() == IdleState.WRITER_IDLE) { LOG.log(Level.FINE, "Closing idle channel: {}", e.state()); ctx.close(); } } } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { HttpRequest request = (HttpRequest) msg; if (HttpHeaders.is100ContinueExpected(request)) { ctx.write(new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE)); } // find the nettyHttpContextHandler by lookup the request url NettyHttpContextHandler nettyHttpContextHandler = pipelineFactory.getNettyHttpHandler(request.getUri()); if (nettyHttpContextHandler != null) { handleHttpServletRequest(ctx, request, nettyHttpContextHandler); } else { throw new RuntimeException( new Fault(new Message("NO_NETTY_SERVLET_HANDLER_FOUND", LOG, request.getUri()))); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } protected void handleHttpServletRequest(ChannelHandlerContext ctx, HttpRequest request, NettyHttpContextHandler nettyHttpContextHandler) throws Exception { interceptOnRequestReceived(ctx, request); FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); NettyServletResponse nettyServletResponse = buildHttpServletResponse(response); NettyHttpServletRequest nettyServletRequest = buildHttpServletRequest(request, nettyHttpContextHandler.getContextPath(), ctx); nettyHttpContextHandler.handle(nettyServletRequest.getRequestURI(), nettyServletRequest, nettyServletResponse); interceptOnRequestSuccessed(ctx, response); nettyServletResponse.getWriter().flush(); boolean keepAlive = HttpHeaders.isKeepAlive(request); if (keepAlive) { // Add 'Content-Length' header only for a keep-alive connection. response.headers().set(Names.CONTENT_LENGTH, response.content().readableBytes()); // Add keep alive header as per: // - // http://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01.html#Connection response.headers().set(Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE); } // write response... ChannelFuture future = ctx.write(response); if (!keepAlive) { future.addListener(ChannelFutureListener.CLOSE); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { LOG.log(Level.SEVERE, "UNEXPECTED_EXCEPCTION_IN_NETTY_SERVLET_HANDLER", cause); interceptOnRequestFailed(ctx, cause); Channel ch = ctx.channel(); if (cause instanceof IllegalArgumentException) { ch.close(); } else { if (cause instanceof TooLongFrameException) { sendError(ctx, HttpResponseStatus.BAD_REQUEST); return; } if (ch.isActive()) { sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR); } } ctx.close(); } private void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) { ByteBuf content = Unpooled.copiedBuffer("Failure: " + status.toString() + "\r\n", CharsetUtil.UTF_8); FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, content); response.headers().set(Names.CONTENT_TYPE, "text/plain; charset=UTF-8"); ctx.write(response).addListener(ChannelFutureListener.CLOSE); } private void interceptOnRequestReceived(ChannelHandlerContext ctx, HttpRequest request) { if (this.interceptors != null) { for (NettyInterceptor interceptor : this.interceptors) { interceptor.onRequestReceived(ctx, request); } } } private void interceptOnRequestSuccessed(ChannelHandlerContext ctx, HttpResponse response) { if (this.interceptors != null) { for (NettyInterceptor interceptor : this.interceptors) { interceptor.onRequestSuccessed(ctx, response); } } } private void interceptOnRequestFailed(ChannelHandlerContext ctx, Throwable e) { if (this.interceptors != null) { for (NettyInterceptor interceptor : this.interceptors) { interceptor.onRequestFailed(ctx, e); } } } protected NettyServletResponse buildHttpServletResponse( HttpResponse response) { // need to access the return new NettyServletResponse(response); } protected NettyHttpServletRequest buildHttpServletRequest( HttpRequest request, String contextPath, ChannelHandlerContext ctx) { return new NettyHttpServletRequest(request, contextPath, ctx); } }