/* * 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 com.github.rmannibucau.featuredmock.http; import com.github.rmannibucau.featuredmock.util.Extensions; import com.github.rmannibucau.featuredmock.util.IOs; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.util.CharsetUtil; import java.io.InputStream; @ChannelHandler.Sharable class FeaturedHandler extends SimpleChannelInboundHandler<FullHttpRequest> implements Extensions { private final ContentTypeMapper[] mappers; private final RequestObserver observer; public FeaturedHandler(final ContentTypeMapper[] mappers, final RequestObserver observer) { this.mappers = mappers; this.observer = observer; } @Override protected void channelRead0(final ChannelHandlerContext ctx, final FullHttpRequest request) throws Exception { if (!request.getDecoderResult().isSuccess()) { sendError(ctx, HttpResponseStatus.BAD_REQUEST); return; } InputStream stream = null; String type = null; final String accept = request.headers().get(HttpHeaders.Names.ACCEPT); if (accept != null) { for (final String a : accept.split(",")) { if (mappers != null) { for (final ContentTypeMapper mapper : mappers) { if (mapper.handle(a)) { final String extension = mapper.extension(a); stream = findResource(request, extension); if (stream == null && extension != null && !extension.startsWith(".")) { stream = findResource(request, "." + extension); } if (stream != null) { type = mapper.contentType(a); break; } } } } if (stream == null) { for (final String ext : EXTENSIONS) { if (a.contains(ext.substring(1))) { stream = findResource(request, ext); if (stream != null) { type = "application/" + ext.substring(1); // remove dot } break; } } } if (stream != null) { break; } } } if (stream == null) { // not found from content type so try all extensions for (final String ext : EXTENSIONS) { stream = findResource(request, ext); if (stream != null) { type = "application/" + ext.substring(1); // remove dot break; } } } if (stream == null) { // try without extension stream = findResource(request, ""); if (stream != null) { type = "text/plain"; } } if (stream == null) { sendError(ctx, HttpResponseStatus.NOT_FOUND); return; } if (observer != null) { observer.onRequest(request); } final byte[] bytes = IOs.slurp(stream); final HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(bytes)); HttpHeaders.setContentLength(response, bytes.length); response.headers().set(HttpHeaders.Names.CONTENT_TYPE, type); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } private static InputStream findResource(final FullHttpRequest request, final String ext) { String path = request.getUri(); if (path.startsWith("/")) { path = path.substring(1); } if (path.contains("?")) { path = path.substring(0, path.indexOf('?')); } if (path.contains("#")) { path = path.substring(0, path.indexOf('#')); } if (path.endsWith("/")) { path = path.substring(0, path.length() - 1) + ext; } else { path = path + ext; } final ClassLoader loader = Thread.currentThread().getContextClassLoader(); final InputStream is = loader.getResourceAsStream(request.getMethod().name() + "-" + path); if (is != null) { return is; } return loader.getResourceAsStream(path); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (ctx.channel().isActive()) { sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR); } } private static void sendError(final ChannelHandlerContext ctx, final HttpResponseStatus status) { final FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status.toString() + "\r\n", CharsetUtil.UTF_8)); response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=UTF-8"); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } }