/* * Copyright (c) 2013-2015 the original author or authors * * 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.werval.server.netty; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import io.werval.api.exceptions.WervalException; import io.werval.api.http.FormUploads.Upload; import io.werval.api.http.Headers; import io.werval.api.http.ProtocolVersion; import io.werval.api.http.Request; import io.werval.spi.http.HttpBuildersSPI; import io.werval.spi.http.HttpBuildersSPI.RequestBuilder; import io.werval.runtime.http.FormUploadsInstance.UploadInstance; import io.netty.channel.Channel; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.multipart.Attribute; import io.netty.handler.codec.http.multipart.FileUpload; import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException; import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.IncompatibleDataDecoderException; import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException; import io.netty.handler.codec.http.multipart.InterfaceHttpData; import static io.werval.api.http.Headers.Names.CONTENT_TYPE; import static io.werval.util.IllegalArguments.ensureNotEmpty; import static io.werval.util.IllegalArguments.ensureNotNull; import static io.netty.handler.codec.http.HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED; import static io.netty.handler.codec.http.HttpHeaders.Values.MULTIPART_FORM_DATA; import static io.netty.handler.codec.http.HttpMethod.PATCH; import static io.netty.handler.codec.http.HttpMethod.POST; import static io.netty.handler.codec.http.HttpMethod.PUT; /** * Factory methods used by the server. */ /* package */ final class NettyHttpFactories { /* package */ static String remoteAddressOf( Channel channel ) { SocketAddress remoteAddress = channel.remoteAddress(); if( remoteAddress == null || !( remoteAddress instanceof InetSocketAddress ) ) { return null; } InetAddress inetRemoteAddress = ( (InetSocketAddress) remoteAddress ).getAddress(); if( inetRemoteAddress == null ) { return null; } return inetRemoteAddress.getHostAddress(); } /* package */ static Request requestOf( Charset defaultCharset, HttpBuildersSPI builders, String remoteSocketAddress, String identity, FullHttpRequest request ) throws HttpRequestParsingException { ensureNotEmpty( "Request Identity", identity ); ensureNotNull( "Netty FullHttpRequest", request ); try { RequestBuilder builder = builders.newRequestBuilder() .identifiedBy( identity ) .remoteSocketAddress( remoteSocketAddress ) .version( ProtocolVersion.valueOf( request.getProtocolVersion().text() ) ) .method( request.getMethod().name() ) .uri( request.getUri() ) .headers( headersToMap( request.headers() ) ); if( request.content().readableBytes() > 0 && ( POST.equals( request.getMethod() ) || PUT.equals( request.getMethod() ) || PATCH.equals( request.getMethod() ) ) ) { Optional<String> contentType = Headers.extractContentMimeType( request.headers().get( CONTENT_TYPE ) ); if( contentType.isPresent() ) { switch( contentType.get() ) { case APPLICATION_X_WWW_FORM_URLENCODED: case MULTIPART_FORM_DATA: try { Map<String, List<String>> attributes = new LinkedHashMap<>(); Map<String, List<Upload>> uploads = new LinkedHashMap<>(); HttpPostRequestDecoder postDecoder = new HttpPostRequestDecoder( request ); for( InterfaceHttpData data : postDecoder.getBodyHttpDatas() ) { switch( data.getHttpDataType() ) { case Attribute: Attribute attribute = (Attribute) data; if( !attributes.containsKey( attribute.getName() ) ) { attributes.put( attribute.getName(), new ArrayList<>() ); } attributes.get( attribute.getName() ).add( attribute.getValue() ); break; case FileUpload: FileUpload fileUpload = (FileUpload) data; if( !uploads.containsKey( fileUpload.getName() ) ) { uploads.put( fileUpload.getName(), new ArrayList<>() ); } Upload upload; if( fileUpload.isInMemory() ) { upload = new UploadInstance( fileUpload.getContentType(), fileUpload.getCharset(), fileUpload.getFilename(), new ByteBufByteSource( fileUpload.getByteBuf() ).asBytes(), defaultCharset ); } else { upload = new UploadInstance( fileUpload.getContentType(), fileUpload.getCharset(), fileUpload.getFilename(), fileUpload.getFile(), defaultCharset ); } uploads.get( fileUpload.getName() ).add( upload ); break; default: break; } } builder = builder.bodyForm( attributes, uploads ); break; } catch( ErrorDataDecoderException | IncompatibleDataDecoderException | NotEnoughDataDecoderException | IOException ex ) { throw new WervalException( "Form or multipart parsing error", ex ); } default: builder = builder.bodyBytes( new ByteBufByteSource( request.content() ) ); break; } } else { builder = builder.bodyBytes( new ByteBufByteSource( request.content() ) ); } } return builder.build(); } catch( Exception ex ) { throw new HttpRequestParsingException( ex ); } } private static Map<String, List<String>> headersToMap( HttpHeaders nettyHeaders ) { Map<String, List<String>> headers = new HashMap<>(); for( String name : nettyHeaders.names() ) { if( !headers.containsKey( name ) ) { headers.put( name, new ArrayList<>() ); } for( String value : nettyHeaders.getAll( name ) ) { headers.get( name ).add( value ); } } return headers; } private NettyHttpFactories() { } }