/************************************************************************* * Copyright 2009-2012 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. * * This file may incorporate work covered under the following copyright * and permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, * with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE * THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL, * COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE, * AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, * SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, * WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION, * REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO * IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT * NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS. ************************************************************************/ package com.eucalyptus.ws.server; import java.util.Date; import java.util.Collections; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicReference; import javax.security.auth.login.LoginException; import org.apache.log4j.Logger; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelEvent; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.ChannelHandler; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; import org.jboss.netty.handler.codec.frame.TooLongFrameException; import org.jboss.netty.handler.codec.http.DefaultHttpResponse; import org.jboss.netty.handler.codec.http.HttpHeaders; import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpResponse; import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.jboss.netty.handler.codec.http.HttpVersion; import org.jboss.netty.handler.timeout.ReadTimeoutException; import org.jboss.netty.handler.timeout.WriteTimeoutException; import com.eucalyptus.binding.Binding; import com.eucalyptus.component.annotation.ComponentPart; import com.eucalyptus.context.Contexts; import com.eucalyptus.crypto.util.Timestamps; import com.eucalyptus.http.MappingHttpMessage; import com.eucalyptus.http.MappingHttpRequest; import com.eucalyptus.records.Logs; import com.eucalyptus.system.Ats; import com.eucalyptus.util.Exceptions; import com.eucalyptus.ws.Handlers; import com.eucalyptus.ws.StackConfiguration; import com.eucalyptus.ws.WebServicesException; import com.eucalyptus.ws.handlers.ExceptionMarshallerHandler; import com.eucalyptus.ws.server.FilteredPipeline.InternalPipeline; public class NioServerHandler extends SimpleChannelUpstreamHandler {//TODO:GRZE: this needs to move up dependency tree. private static Logger LOG = Logger.getLogger( NioServerHandler.class ); private AtomicReference<FilteredPipeline> pipeline = new AtomicReference<FilteredPipeline>( ); @Override public void messageReceived( final ChannelHandlerContext ctx, final MessageEvent e ) throws Exception { Callable<Long> stat = MessageStatistics.startUpstream(ctx.getChannel(), this); try { if ( this.pipeline.get( ) == null ) { lookupPipeline( ctx, e ); } else if ( e.getMessage( ) instanceof MappingHttpRequest ) { MappingHttpRequest httpRequest = ( MappingHttpRequest ) e.getMessage( ); if ( isPersistentConnection( httpRequest ) ) { this.pipeline.set( null ); ChannelHandler p; while ( ( p = ctx.getPipeline( ).getLast( ) ) != this ) { ctx.getPipeline( ).remove( p ); } lookupPipeline( ctx, e ); } else { LOG.warn( "Hard close the socket on an attempt to do a second request." ); ctx.getChannel( ).close( ); return; } } stat.call( ); ctx.sendUpstream( e ); } catch ( Exception ex ) { LOG.trace( ex ); Logs.extreme( ).error( ex, ex ); stat.call( ); this.sendError( ctx, e, HttpResponseStatus.NOT_FOUND, ex ); } } public static boolean isPersistentConnection( final MappingHttpRequest httpRequest ) { return ( httpRequest.getProtocolVersion( ).equals( HttpVersion.HTTP_1_1 ) && !HttpHeaders.Values.CLOSE.equalsIgnoreCase( httpRequest.getHeader( HttpHeaders.Names.CONNECTION ) ) ) || ( httpRequest.getProtocolVersion( ).equals( HttpVersion.HTTP_1_0 ) && HttpHeaders.Values.KEEP_ALIVE.equalsIgnoreCase( httpRequest.getHeader( HttpHeaders.Names.CONNECTION ) ) ); } private void lookupPipeline( final ChannelHandlerContext ctx, final MessageEvent e ) throws DuplicatePipelineException, NoAcceptingPipelineException { try { final HttpRequest request = ( HttpRequest ) e.getMessage( ); if ( Logs.isExtrrreeeme( ) && request instanceof MappingHttpMessage ) { Logs.extreme( ).trace( ( ( MappingHttpMessage ) request ).logMessage( ) ); } final FilteredPipeline filteredPipeline = Pipelines.find( request ); if ( this.pipeline.compareAndSet( null, filteredPipeline ) ) { this.pipeline.get( ).unroll( ctx.getPipeline( ) ); if ( filteredPipeline instanceof InternalPipeline ) { Handlers.addInternalSystemHandlers( ctx.getPipeline( ) ); } final Ats ats = Ats.inClassHierarchy( filteredPipeline ); Handlers.addComponentHandlers( ats.has(ComponentPart.class) ? ats.get(ComponentPart.class).value() : null, ctx.getPipeline() ); Handlers.addSystemHandlers( ctx.getPipeline( ) ); } } catch ( DuplicatePipelineException e1 ) { LOG.error( "This is a BUG: " + e1, e1 ); throw e1; } catch ( NoAcceptingPipelineException e2 ) { throw e2; } } @Override public void exceptionCaught( final ChannelHandlerContext ctx, final ExceptionEvent e ) throws Exception { final Channel ch = e.getChannel( ); final Throwable cause = e.getCause( ); Logs.extreme( ).error( cause, cause ); try { if ( cause instanceof ReadTimeoutException ) {//TODO:ASAP:GRZE: wth are all these exception types?!?! ONLY WebServicesException caught; else wrap. this.sendError( ctx, e, HttpResponseStatus.REQUEST_TIMEOUT, cause ); } else if ( cause instanceof WriteTimeoutException ) { ctx.sendUpstream( e ); ctx.getChannel().close(); } else if ( cause instanceof TooLongFrameException ) { this.sendError( ctx, e, HttpResponseStatus.BAD_REQUEST, cause ); } else if ( cause instanceof IllegalArgumentException ) { this.sendError( ctx, e, HttpResponseStatus.BAD_REQUEST, cause ); } else if ( cause instanceof LoginException ) { this.sendError( ctx, e, HttpResponseStatus.FORBIDDEN, cause ); } else if ( e.getCause() instanceof WebServicesException ) { final WebServicesException webEx = (WebServicesException) e.getCause(); if ( webEx.getStatus().getCode() != 403 ) LOG.error( "Internal Error: " + cause.getMessage() ); Logs.extreme().error( cause, cause ); this.sendError( ctx, e, webEx.getStatus(), cause ); } else { this.sendError( ctx, e, HttpResponseStatus.BAD_REQUEST, cause ); } } finally { try { if ( ch != null ) { Contexts.clear( Contexts.lookup( ch ) ); } } catch ( Exception ex ) { // LOG.error( ex, ex ); } } } private void sendError( final ChannelHandlerContext ctx, final ChannelEvent event, final HttpResponseStatus restStatus, Throwable t ) { Logs.exhaust( ).error( t, t ); HttpResponseStatus status = restStatus; ChannelBuffer buffer = null; Map<String,String> headers = Collections.emptyMap( ); final ExceptionMarshallerHandler exceptionMarshallerHandler = ctx.getPipeline().get( ExceptionMarshallerHandler.class ); if ( exceptionMarshallerHandler != null ) { try { final ExceptionMarshallerHandler.ExceptionResponse exceptionResponse = exceptionMarshallerHandler.marshallException( event, status, t ); status = exceptionResponse.getStatus(); buffer = exceptionResponse.getContent(); headers = exceptionResponse.getHeaders(); } catch ( Exception e ) { Logs.exhaust().error( e, e ); } } if ( buffer == null ) { buffer = ChannelBuffers.copiedBuffer( Binding.createRestFault( status.toString(), t.getMessage(), Logs.isExtrrreeeme() ? Exceptions.string( t ) : t.getMessage() ), "UTF-8" ); } final HttpResponse response = new DefaultHttpResponse( HttpVersion.HTTP_1_1, status ); response.setHeader( HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=UTF-8" ); response.setHeader( HttpHeaders.Names.CONTENT_LENGTH, String.valueOf( buffer.readableBytes() ) ); response.setHeader( HttpHeaders.Names.DATE, Timestamps.formatRfc822Timestamp( new Date())); final java.util.Optional<String> header = StackConfiguration.getServerHeader( ); if ( header.isPresent( ) ) { response.setHeader( HttpHeaders.Names.SERVER, header.get( ) ); } for ( final Map.Entry<String,String> headerEntry : headers.entrySet( ) ) { response.setHeader( headerEntry.getKey( ), headerEntry.getValue( ) ); } response.setContent( buffer ); ChannelFuture writeFuture = Channels.future( ctx.getChannel( ) ); writeFuture.addListener( ChannelFutureListener.CLOSE ); response.setHeader( HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE ); if ( ctx.getChannel( ).isConnected( ) ) { Channels.write( ctx, writeFuture, response ); } } }