Example usage for io.netty.channel ChannelFuture addListener

List of usage examples for io.netty.channel ChannelFuture addListener

Introduction

In this page you can find the example usage for io.netty.channel ChannelFuture addListener.

Prototype

@Override
    ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> listener);

Source Link

Usage

From source file:net.hasor.rsf.remoting.transport.customer.RsfRequestManager.java

License:Apache License

/**???*/
private void sendRequest(RsfFuture rsfFuture) {
    //???//from  w w  w .  j a va  2 s.  co m
    RsfSettings rsfSettings = this.getRsfContext().getSettings();
    if (this.requestCount.get() >= rsfSettings.getMaximumRequest()) {
        SendLimitPolicy sendPolicy = rsfSettings.getSendLimitPolicy();
        String errorMessage = "maximum number of requests, apply SendPolicy = " + sendPolicy.name();
        LoggerHelper.logWarn(errorMessage);
        if (sendPolicy == SendLimitPolicy.Reject) {
            throw new RsfException(ProtocolStatus.ClientError, errorMessage);
        } else {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
    }
    //RsfFilter
    final RsfRequestImpl request = (RsfRequestImpl) rsfFuture.getRequest();
    final RequestMsg rsfMessage = request.getMsg();
    //??
    final AbstractRsfClient rsfClient = this.getClientManager().getClient(request.getBindInfo());
    final long beginTime = System.currentTimeMillis();
    final long timeout = rsfMessage.getClientTimeout();
    //
    if (rsfClient == null) {
        rsfFuture.failed(new IllegalStateException("The lack of effective service provider."));
        return;
    }
    if (rsfClient.isActive() == false) {
        rsfFuture.failed(new IllegalStateException("client is closed."));
        return;
    }
    this.startRequest(
            rsfFuture);/* timeout ???request*/
    //
    ChannelFuture future = rsfClient.getChannel().writeAndFlush(rsfMessage);
    /*sendData???*/
    future.addListener(new ChannelFutureListener() {
        public void operationComplete(ChannelFuture future) throws Exception {
            if (future.isSuccess()) {
                return;
            }
            String errorMsg = null;
            //
            if (System.currentTimeMillis() - beginTime >= timeout) {
                errorMsg = "send request too long time(" + (System.currentTimeMillis() - beginTime)
                        + "),requestID:" + rsfMessage.getRequestID();
            }
            //?
            if (future.isCancelled()) {
                errorMsg = "send request to cancelled by user,requestID:" + rsfMessage.getRequestID();
            }
            //
            if (!future.isSuccess()) {
                if (rsfClient.isActive()) {
                    // maybe some exception, so close the channel
                    getClientManager().unRegistered(rsfClient.getHostAddress());
                }
                errorMsg = "send request error " + future.cause();
            }
            LoggerHelper.logSevere(RsfRequestManager.this + ":" + errorMsg);
            //Response
            putResponse(request.getRequestID(), new RsfException(ProtocolStatus.ClientError, errorMsg));
        }
    });
}

From source file:net.hasor.rsf.rpc.net.Connector.java

License:Apache License

/**  */
public void connectionTo(final InterAddress hostAddress, final BasicFuture<RsfChannel> result) {
    ////from  w  ww . j a v  a 2s . com
    //
    Bootstrap boot = new Bootstrap();
    boot.group(this.workLoopGroup);
    boot.channel(NioSocketChannel.class);
    boot.handler(new ChannelInitializer<SocketChannel>() {
        public void initChannel(SocketChannel ch) throws Exception {
            ChannelHandler[] handlerArrays = channelHandler();
            ArrayList<ChannelHandler> handlers = new ArrayList<ChannelHandler>();
            handlers.addAll(Arrays.asList(handlerArrays)); // ??
            handlers.add(Connector.this); // ?RequestInfo?ResponseInfoRSF
            //
            ch.pipeline().addLast(handlers.toArray(new ChannelHandler[handlers.size()]));
        }
    });
    ChannelFuture future = configBoot(boot).connect(hostAddress.toSocketAddress());
    //
    future.addListener(new ChannelFutureListener() {
        public void operationComplete(ChannelFuture future) throws Exception {
            if (!future.isSuccess()) {
                future.channel().close();
                logger.error("connect to {} error.", hostAddress, future.cause());
                result.failed(future.cause());
            } else {
                Channel channel = future.channel();
                logger.info("connect to {} Success.", hostAddress);
                result.completed(new RsfChannel(protocol, bindAddress, channel, LinkType.Out));
            }
        }
    });
}

From source file:net.hasor.rsf.rpc.net.Connector.java

License:Apache License

/**
 * ??/*from  ww w  . j  av a  2s .  c om*/
 * @param listenLoopGroup ?
 */
public void startListener(NioEventLoopGroup listenLoopGroup) {
    //
    ServerBootstrap boot = new ServerBootstrap();
    boot.group(listenLoopGroup, this.workLoopGroup);
    boot.channel(NioServerSocketChannel.class);
    boot.childHandler(new ChannelInitializer<SocketChannel>() {
        public void initChannel(SocketChannel ch) throws Exception {
            ChannelHandler[] handlerArrays = channelHandler();
            ArrayList<ChannelHandler> handlers = new ArrayList<ChannelHandler>();
            handlers.addAll(Arrays.asList(handlerArrays)); // ??
            handlers.add(Connector.this); // ?RequestInfo?ResponseInfoRSF
            //
            ch.pipeline().addLast(handlers.toArray(new ChannelHandler[handlers.size()]));
        }
    });
    boot.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
    boot.childOption(ChannelOption.SO_KEEPALIVE, true);
    ChannelFuture future = configBoot(boot).bind(this.bindAddress.toSocketAddress());
    //
    final BasicFuture<RsfChannel> result = new BasicFuture<RsfChannel>();
    future.addListener(new ChannelFutureListener() {
        public void operationComplete(ChannelFuture future) throws Exception {
            if (!future.isSuccess()) {
                future.channel().close();
                result.failed(future.cause());
            } else {
                Channel channel = future.channel();
                result.completed(new RsfChannel(protocol, bindAddress, channel, LinkType.Listener));
            }
        }
    });
    try {
        this.localListener = result.get();
        logger.info("rsf Server started at {}", this.bindAddress);
    } catch (Exception e) {
        logger.error("rsf start listener error: " + e.getMessage(), e);
        throw new RsfException(ProtocolStatus.NetworkError,
                this.bindAddress.toString() + " -> " + e.getMessage());
    }
    //
}

From source file:net.hasor.rsf.rpc.net.RsfChannel.java

License:Apache License

/**? Netty*/
private void sendData(final long requestID, Object sendData, final SendCallBack callBack) {
    if (!this.channel.isActive()) {
        RsfException e = new RsfException(ProtocolStatus.NetworkError,
                "send (" + requestID + ") an error, socket Channel is close.");
        if (callBack != null) {
            callBack.failed(requestID, e);
        }/* w w w. j a v  a  2s .  c o  m*/
        return;
    }
    /*???*/
    this.sendPackets++;
    ChannelFuture future = this.channel.writeAndFlush(sendData);
    /*sendData???*/
    future.addListener(new ChannelFutureListener() {
        public void operationComplete(ChannelFuture future) throws Exception {
            if (future.isSuccess()) {
                if (callBack != null) {
                    callBack.complete(requestID);
                }
                return;
            }
            lastSendTime = System.currentTimeMillis();
            RsfException e = null;
            if (future.isCancelled()) {
                //?
                String errorMsg = "send request(" + requestID + ") to cancelled by user.";
                e = new RsfException(ProtocolStatus.NetworkError, errorMsg);
                logger.error(e.getMessage(), e);
                //Response
                if (callBack != null) {
                    callBack.failed(requestID, e);
                }
            } else if (!future.isSuccess()) {
                //
                Throwable ex = future.cause();
                String errorMsg = "send request(" + requestID + ") an error ->" + ex.getMessage();
                e = new RsfException(ProtocolStatus.NetworkError, errorMsg, ex);
                logger.error(e.getMessage(), e);
                //Response
                if (callBack != null) {
                    callBack.failed(requestID, e);
                }
            }
        }
    });
}

From source file:net.holmes.core.service.http.HttpFileRequestHandler.java

License:Open Source License

/**
 * {@inheritDoc}//from ww  w.ja  v  a 2 s.  co  m
 */
@Override
protected void channelRead0(final ChannelHandlerContext context, final HttpFileRequest request)
        throws HttpFileRequestException, IOException {
    // Check file
    File file = request.getFile();
    if (!isValidFile(file)) {
        throw new HttpFileRequestException(file.getPath(), NOT_FOUND);
    }

    // Get file descriptor
    RandomAccessFile randomFile = new RandomAccessFile(file, "r");
    long fileLength = randomFile.length();

    // Get start offset
    long startOffset = getStartOffset(request.getHttpRequest());

    // Build response
    HttpResponse response = buildHttpResponse(startOffset, fileLength);

    // Add HTTP headers to response
    addContentHeaders(response, fileLength - startOffset, request.getMimeType());
    addDateHeader(response, file, request.isStaticFile());
    boolean keepAlive = addKeepAliveHeader(response, request.getHttpRequest());

    // Write the response
    context.write(response);

    // Write the content
    context.write(new ChunkedFile(randomFile, startOffset, fileLength - startOffset, CHUNK_SIZE));

    // Write the end marker
    ChannelFuture lastContentFuture = context.writeAndFlush(EMPTY_LAST_CONTENT);

    // Decide whether to close the connection or not when the whole content is written out.
    if (!keepAlive) {
        lastContentFuture.addListener(CLOSE);
    }

}

From source file:net.javaforge.netty.servlet.bridge.ServletBridgeHandler.java

License:Apache License

protected void handleHttpServletRequest(ChannelHandlerContext ctx, HttpRequest request, FilterChainImpl chain)
        throws Exception {

    interceptOnRequestReceived(ctx, request);

    DefaultFullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);

    HttpServletResponseImpl resp = buildHttpServletResponse(response);
    HttpServletRequestImpl req = buildHttpServletRequest(request, chain);

    chain.doFilter(req, resp);/*from   www  .j  a va 2  s  . co  m*/

    interceptOnRequestSuccessed(ctx, request, response);

    resp.getWriter().flush();

    boolean keepAlive = HttpHeaders.isKeepAlive(request);

    if (keepAlive) {

        // Add 'Content-Length' header only for a keep-alive connection.
        response.headers().set(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(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
    }

    // write response...
    ChannelFuture future = ctx.channel().writeAndFlush(response);

    if (!keepAlive) {
        future.addListener(ChannelFutureListener.CLOSE);
    }

}

From source file:net.javaforge.netty.servlet.bridge.ServletBridgeHandler.java

License:Apache License

protected void handleStaticResourceRequest(ChannelHandlerContext ctx, HttpRequest request) throws Exception {
    if (request.method() != GET) {
        sendError(ctx, METHOD_NOT_ALLOWED);
        return;/*from w  w w  .j  av  a  2 s .  c o  m*/
    }

    String uri = Utils.sanitizeUri(request.uri());
    final String path = (uri != null
            ? ServletBridgeWebapp.get().getStaticResourcesFolder().getAbsolutePath() + File.separator + uri
            : null);

    if (path == null) {
        sendError(ctx, FORBIDDEN);
        return;
    }

    File file = new File(path);
    if (file.isHidden() || !file.exists()) {
        sendError(ctx, NOT_FOUND);
        return;
    }
    if (!file.isFile()) {
        sendError(ctx, FORBIDDEN);
        return;
    }

    RandomAccessFile raf;
    try {
        raf = new RandomAccessFile(file, "r");
    } catch (FileNotFoundException fnfe) {
        sendError(ctx, NOT_FOUND);
        return;
    }

    long fileLength = raf.length();

    HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
    setContentLength(response, fileLength);

    Channel ch = ctx.channel();

    // Write the initial line and the header.
    ch.write(response);

    // Write the content.
    ChannelFuture writeFuture;
    if (isSslChannel(ch)) {
        // Cannot use zero-copy with HTTPS.
        writeFuture = ch.write(new ChunkedFile(raf, 0, fileLength, 8192));
    } else {
        // No encryption - use zero-copy.
        final FileRegion region = new DefaultFileRegion(raf.getChannel(), 0, fileLength);
        writeFuture = ch.write(region);
        writeFuture.addListener(new ChannelProgressiveFutureListener() {

            @Override
            public void operationProgressed(ChannelProgressiveFuture channelProgressiveFuture, long current,
                    long total) throws Exception {
                System.out.printf("%s: %d / %d (+%d)%n", path, current, total, total);
            }

            @Override
            public void operationComplete(ChannelProgressiveFuture channelProgressiveFuture) throws Exception {
                region.release();
            }
        });
    }

}

From source file:net.kuujo.copycat.io.transport.NettyServer.java

License:Apache License

/**
 * Starts listening for the given member.
 *///from   w w  w.j  av  a 2s .co  m
private void listen(Address address, Consumer<Connection> listener, Context context) {
    channelGroup = new DefaultChannelGroup("copycat-acceptor-channels", GlobalEventExecutor.INSTANCE);

    handler = new ServerHandler(connections, listener, context);

    final ServerBootstrap bootstrap = new ServerBootstrap();
    bootstrap.group(eventLoopGroup)
            .channel(eventLoopGroup instanceof EpollEventLoopGroup ? EpollServerSocketChannel.class
                    : NioServerSocketChannel.class)
            .handler(new LoggingHandler(LogLevel.DEBUG)).childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel channel) throws Exception {
                    ChannelPipeline pipeline = channel.pipeline();
                    pipeline.addLast(FIELD_PREPENDER);
                    pipeline.addLast(new LengthFieldBasedFrameDecoder(1024 * 32, 0, 2, 0, 2));
                    pipeline.addLast(handler);
                }
            }).option(ChannelOption.SO_BACKLOG, 128).option(ChannelOption.TCP_NODELAY, true)
            .option(ChannelOption.SO_REUSEADDR, true).childOption(ChannelOption.ALLOCATOR, ALLOCATOR)
            .childOption(ChannelOption.SO_KEEPALIVE, true);

    LOGGER.info("Binding to {}", address);

    ChannelFuture bindFuture = bootstrap.bind(address.socketAddress());
    bindFuture.addListener((ChannelFutureListener) channelFuture -> {
        if (channelFuture.isSuccess()) {
            listening = true;
            context.executor().execute(() -> {
                LOGGER.info("Listening at {}", bindFuture.channel().localAddress());
                listenFuture.complete(null);
            });
        } else {
            context.execute(() -> listenFuture.completeExceptionally(channelFuture.cause()));
        }
    });
    channelGroup.add(bindFuture.channel());
}

From source file:net.NettyEngine4.file.HttpStaticFileServerHandler.java

License:Apache License

/**
 * <strong>Please keep in mind that this method will be renamed to
 * {@code messageReceived(ChannelHandlerContext, I)} in 5.0.</strong>
 * <p/>/*from www. jav  a2  s  . c  om*/
 * Is called for each message of type {@link SimpleChannelInboundHandler#channelRead0(io.netty.channel.ChannelHandlerContext, Object)}.
 *
 * @param ctx     the {@link ChannelHandlerContext} which this {@link SimpleChannelInboundHandler}
 *                belongs to
 * @param request the message to handle
 * @throws Exception is thrown if an error occurred
 */
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {

    if (!request.getDecoderResult().isSuccess()) {
        sendError(ctx, BAD_REQUEST);
        return;
    }

    if (request.getMethod() != GET) {
        sendError(ctx, METHOD_NOT_ALLOWED);
        return;
    }

    final String uri = request.getUri();
    final String path = sanitizeUri(uri);
    if (path == null) {
        sendError(ctx, FORBIDDEN);
        return;
    }

    File file = new File(path);
    if (file.isHidden() || !file.exists()) {
        sendError(ctx, NOT_FOUND);
        return;
    }

    if (file.isDirectory()) {
        if (uri.endsWith("/")) {
            sendListing(ctx, file);
        } else {
            sendRedirect(ctx, uri + '/');
        }
        return;
    }

    if (!file.isFile()) {
        sendError(ctx, FORBIDDEN);
        return;
    }

    // Cache Validation
    String ifModifiedSince = request.headers().get(IF_MODIFIED_SINCE);
    if (ifModifiedSince != null && !ifModifiedSince.isEmpty()) {
        SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
        Date ifModifiedSinceDate = dateFormatter.parse(ifModifiedSince);

        // Only compare up to the second because the datetime format we send to the client
        // does not have milliseconds
        long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000;
        long fileLastModifiedSeconds = file.lastModified() / 1000;
        if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) {
            sendNotModified(ctx);
            return;
        }
    }

    RandomAccessFile raf;
    try {
        raf = new RandomAccessFile(file, "r");
    } catch (FileNotFoundException ignore) {
        sendError(ctx, NOT_FOUND);
        return;
    }
    long fileLength = raf.length();

    HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);

    HttpHeaders.setContentLength(response, fileLength);
    setContentTypeHeader(response, file);
    setDateAndCacheHeaders(response, file);
    if (HttpHeaders.isKeepAlive(request)) {
        response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
    }

    // Write the initial line and the header.
    ctx.write(response);

    // Write the content.
    ChannelFuture sendFileFuture;
    if (ctx.pipeline().get(SslHandler.class) == null) {
        sendFileFuture = ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength),
                ctx.newProgressivePromise());
    } else {
        sendFileFuture = ctx.write(new HttpChunkedInput(new ChunkedFile(raf, 0, fileLength, 8192)),
                ctx.newProgressivePromise());
    }

    /**
     *
     */
    sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
        @Override
        public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {
            if (total < 0) { // total unknown
                LOGGER.debug(future.channel() + " Transfer progress: " + progress);
            } else {
                LOGGER.debug(future.channel() + " Transfer progress: " + progress + " / " + total);
            }
        }

        @Override
        public void operationComplete(ChannelProgressiveFuture future) {
            LOGGER.debug(future.channel() + " Transfer complete.");
        }
    });

    // Write the end marker
    ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);

    // Decide whether to close the connection or not.
    if (!HttpHeaders.isKeepAlive(request)) {
        // Close the connection when the whole content is written out.
        lastContentFuture.addListener(ChannelFutureListener.CLOSE);
    }
}

From source file:net.pms.network.RequestV2.java

License:Open Source License

/**
 * Construct a proper HTTP response to a received request. After the response has been
 * created, it is sent and the resulting {@link ChannelFuture} object is returned.
 * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">RFC-2616</a>
 * for HTTP header field definitions.//from  w w w.  j av a2s  .  co  m
 *
 * @param ctx
 * @param output The {@link HttpResponse} object that will be used to construct the response.
 * @param e The {@link io.netty.handler.codec.http.FullHttpRequest} object used to communicate with the client that sent
 *          the request.
 * @param close Set to true to close the channel after sending the response. By default the
 *          channel is not closed after sending.
 * @param startStopListenerDelegate The {@link StartStopListenerDelegate} object that is used
 *          to notify plugins that the {@link DLNAResource} is about to start playing.
 * @return The {@link ChannelFuture} object via which the response was sent.
 * @throws IOException
 */
public ChannelFuture answer(final ChannelHandlerContext ctx, HttpResponse output, FullHttpRequest e,
        final boolean close, final StartStopListenerDelegate startStopListenerDelegate) throws IOException {
    ChannelFuture future = null;
    long CLoverride = -2; // 0 and above are valid Content-Length values, -1 means omit
    StringBuilder response = new StringBuilder();
    DLNAResource dlna = null;
    boolean xbox360 = mediaRenderer.isXbox360();

    // Samsung 2012 TVs have a problematic preceding slash that needs to be removed.
    if (argument.startsWith("/")) {
        LOGGER.trace("Stripping preceding slash from: " + argument);
        argument = argument.substring(1);
    }

    if ((method.equals("GET") || method.equals("HEAD")) && argument.startsWith("console/")) {
        // Request to output a page to the HTML console.
        output.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html");
        response.append(HTMLConsole.servePage(argument.substring(8)));
    } else if ((method.equals("GET") || method.equals("HEAD")) && argument.startsWith("get/")) {
        // Request to retrieve a file

        /**
         * Skip the leading "get/"
         * e.g. "get/0$1$5$3$4/Foo.mp4" -> "0$1$5$3$4/Foo.mp4"
         *
         * ExSport: I spotted on Android it is asking for "/get/0$2$4$2$1$3" which generates exception with response:
         * "Http: Response, HTTP/1.1, Status: Internal server error, URL: /get/0$2$4$2$1$3"
         * This should fix it
         */

        // Note: we intentionally include the trailing filename here because it may
        // be used to reconstruct lost Temp items.
        String id = argument.substring(argument.indexOf("get/") + 4);

        // Some clients escape the separators in their request: unescape them.
        id = id.replace("%24", "$");

        // Retrieve the DLNAresource itself.
        dlna = PMS.get().getRootFolder(mediaRenderer).getDLNAResource(id, mediaRenderer);
        String fileName = id.substring(id.indexOf('/') + 1);

        if (transferMode != null) {
            output.headers().set("TransferMode.DLNA.ORG", transferMode);
        }

        if (dlna != null && dlna.isFolder() && !fileName.startsWith("thumbnail0000")) {
            // if we found a folder we MUST be asked for thumbnails
            // otherwise this is not allowed
            dlna = null;
        }

        if (dlna != null) {
            // DLNAresource was found.

            if (fileName.startsWith("thumbnail0000")) {
                // This is a request for a thumbnail file.
                output.headers().set(HttpHeaders.Names.CONTENT_TYPE, dlna.getThumbnailContentType());
                output.headers().set(HttpHeaders.Names.ACCEPT_RANGES, "bytes");
                output.headers().set(HttpHeaders.Names.EXPIRES, getFUTUREDATE() + " GMT");
                output.headers().set(HttpHeaders.Names.CONNECTION, "keep-alive");

                if (!configuration.isShowCodeThumbs() && !dlna.isCodeValid(dlna)) {
                    inputStream = dlna.getGenericThumbnailInputStream(null);
                } else {
                    if (mediaRenderer.isMediaParserV2()) {
                        dlna.checkThumbnail();
                    }
                    inputStream = dlna.getThumbnailInputStream();
                }
                inputStream = UMSUtils.scaleThumb(inputStream, mediaRenderer);
            } else if (dlna.getMedia() != null && fileName.contains("subtitle0000") && dlna.isCodeValid(dlna)) {
                // This is a request for a subtitle file
                output.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/plain");
                output.headers().set(HttpHeaders.Names.EXPIRES, getFUTUREDATE() + " GMT");
                DLNAMediaSubtitle sub = dlna.getMediaSubtitle();
                if (sub != null) {
                    try {
                        // XXX external file is null if the first subtitle track is embedded:
                        // http://www.ps3mediaserver.org/forum/viewtopic.php?f=3&t=15805&p=75534#p75534
                        if (sub.isExternal()) {
                            inputStream = new FileInputStream(sub.getExternalFile());
                            LOGGER.trace("Loading external subtitles: " + sub);
                        } else {
                            LOGGER.trace(
                                    "Not loading external subtitles because they are not external: " + sub);
                        }
                    } catch (NullPointerException npe) {
                        LOGGER.trace("Not loading external subtitles because we could not find them at " + sub);
                    }
                } else {
                    LOGGER.trace("Not loading external subtitles because dlna.getMediaSubtitle returned null");
                }
            } else if (dlna.isCodeValid(dlna)) {
                // This is a request for a regular file.
                DLNAResource.Rendering origRendering = null;
                if (!mediaRenderer.equals(dlna.getDefaultRenderer())) {
                    // Adjust rendering details for this renderer
                    origRendering = dlna.updateRendering(mediaRenderer);
                }
                // If range has not been initialized yet and the DLNAResource has its
                // own start and end defined, initialize range with those values before
                // requesting the input stream.
                Range.Time splitRange = dlna.getSplitRange();

                if (range.getStart() == null && splitRange.getStart() != null) {
                    range.setStart(splitRange.getStart());
                }

                if (range.getEnd() == null && splitRange.getEnd() != null) {
                    range.setEnd(splitRange.getEnd());
                }

                long totalsize = dlna.length(mediaRenderer);
                boolean ignoreTranscodeByteRangeRequests = mediaRenderer.ignoreTranscodeByteRangeRequests();

                // Ignore ByteRangeRequests while media is transcoded
                if (!ignoreTranscodeByteRangeRequests || totalsize != DLNAMediaInfo.TRANS_SIZE
                        || (ignoreTranscodeByteRangeRequests && lowRange == 0
                                && totalsize == DLNAMediaInfo.TRANS_SIZE)) {
                    inputStream = dlna.getInputStream(
                            Range.create(lowRange, highRange, range.getStart(), range.getEnd()), mediaRenderer);
                    if (dlna.isResume()) {
                        // Update range to possibly adjusted resume time
                        range.setStart(dlna.getResume().getTimeOffset() / (double) 1000);
                    }
                }

                Format format = dlna.getFormat();
                if (format != null && format.isVideo()) {
                    if (dlna.getMedia() != null && !configuration.isDisableSubtitles()) {
                        // Some renderers (like Samsung devices) allow a custom header for a subtitle URL
                        String subtitleHttpHeader = mediaRenderer.getSubtitleHttpHeader();
                        if (subtitleHttpHeader != null && !"".equals(subtitleHttpHeader)) {
                            // Device allows a custom subtitle HTTP header; construct it
                            DLNAMediaSubtitle sub = dlna.getMediaSubtitle();
                            if (sub != null) {
                                String subtitleUrl;
                                String subExtension = sub.getType().getExtension();
                                if (isNotBlank(subExtension)) {
                                    subExtension = "." + subExtension;
                                }
                                subtitleUrl = "http://" + PMS.get().getServer().getHost() + ':'
                                        + PMS.get().getServer().getPort() + "/get/"
                                        + id.substring(0, id.indexOf('/')) + "/subtitle0000" + subExtension;

                                output.headers().set(subtitleHttpHeader, subtitleUrl);
                            } else {
                                LOGGER.trace(
                                        "Did not send subtitle headers because dlna.getMediaSubtitle returned null");
                            }
                        } else {
                            LOGGER.trace(
                                    "Did not send subtitle headers because mediaRenderer.getSubtitleHttpHeader returned either null or blank");
                        }
                    } else {
                        LOGGER.trace(
                                "Did not send subtitle headers because dlna.getMedia returned null or configuration.isDisableSubtitles was true");
                    }
                }

                String name = dlna.getDisplayName(mediaRenderer);
                if (dlna.isNoName()) {
                    name = dlna.getName() + " " + dlna.getDisplayName(mediaRenderer);
                }

                if (inputStream == null) {
                    if (!ignoreTranscodeByteRangeRequests) {
                        // No inputStream indicates that transcoding / remuxing probably crashed.
                        LOGGER.error("There is no inputstream to return for " + name);
                    }
                } else {
                    // Notify plugins that the DLNAresource is about to start playing
                    startStopListenerDelegate.start(dlna);

                    // Try to determine the content type of the file
                    String rendererMimeType = getRendererMimeType(dlna.mimeType(), mediaRenderer);

                    if (rendererMimeType != null && !"".equals(rendererMimeType)) {
                        output.headers().set(HttpHeaders.Names.CONTENT_TYPE, rendererMimeType);
                    }

                    // Response generation:
                    // We use -1 for arithmetic convenience but don't send it as a value.
                    // If Content-Length < 0 we omit it, for Content-Range we use '*' to signify unspecified.
                    boolean chunked = mediaRenderer.isChunkedTransfer();

                    // Determine the total size. Note: when transcoding the length is
                    // not known in advance, so DLNAMediaInfo.TRANS_SIZE will be returned instead.
                    if (chunked && totalsize == DLNAMediaInfo.TRANS_SIZE) {
                        // In chunked mode we try to avoid arbitrary values.
                        totalsize = -1;
                    }

                    long remaining = totalsize - lowRange;
                    long requested = highRange - lowRange;

                    if (requested != 0) {
                        // Determine the range (i.e. smaller of known or requested bytes)
                        long bytes = remaining > -1 ? remaining : inputStream.available();

                        if (requested > 0 && bytes > requested) {
                            bytes = requested + 1;
                        }

                        // Calculate the corresponding highRange (this is usually redundant).
                        highRange = lowRange + bytes - (bytes > 0 ? 1 : 0);

                        LOGGER.trace(
                                (chunked ? "Using chunked response. " : "") + "Sending " + bytes + " bytes.");

                        output.headers().set(HttpHeaders.Names.CONTENT_RANGE,
                                "bytes " + lowRange + "-" + (highRange > -1 ? highRange : "*") + "/"
                                        + (totalsize > -1 ? totalsize : "*"));

                        // Content-Length refers to the current chunk size here, though in chunked
                        // mode if the request is open-ended and totalsize is unknown we omit it.
                        if (chunked && requested < 0 && totalsize < 0) {
                            CLoverride = -1;
                        } else {
                            CLoverride = bytes;
                        }
                    } else {
                        // Content-Length refers to the total remaining size of the stream here.
                        CLoverride = remaining;
                    }

                    // Calculate the corresponding highRange (this is usually redundant).
                    highRange = lowRange + CLoverride - (CLoverride > 0 ? 1 : 0);

                    if (contentFeatures != null) {
                        output.headers().set("ContentFeatures.DLNA.ORG",
                                dlna.getDlnaContentFeatures(mediaRenderer));
                    }

                    output.headers().set(HttpHeaders.Names.ACCEPT_RANGES, "bytes");
                    output.headers().set(HttpHeaders.Names.CONNECTION, "keep-alive");
                }
                if (origRendering != null) {
                    // Restore original rendering details
                    dlna.updateRendering(origRendering);
                }
            }
        }
    } else if ((method.equals("GET") || method.equals("HEAD")) && (argument.toLowerCase().endsWith(".png")
            || argument.toLowerCase().endsWith(".jpg") || argument.toLowerCase().endsWith(".jpeg"))) {
        if (argument.toLowerCase().endsWith(".png")) {
            output.headers().set(HttpHeaders.Names.CONTENT_TYPE, "image/png");
        } else {
            output.headers().set(HttpHeaders.Names.CONTENT_TYPE, "image/jpeg");
        }

        output.headers().set(HttpHeaders.Names.ACCEPT_RANGES, "bytes");
        output.headers().set(HttpHeaders.Names.CONNECTION, "keep-alive");
        output.headers().set(HttpHeaders.Names.EXPIRES, getFUTUREDATE() + " GMT");
        inputStream = getResourceInputStream(argument);
    } else if ((method.equals("GET") || method.equals("HEAD"))
            && (argument.equals("description/fetch") || argument.endsWith("1.0.xml"))) {
        output.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/xml; charset=\"utf-8\"");
        output.headers().set(HttpHeaders.Names.CACHE_CONTROL, "no-cache");
        output.headers().set(HttpHeaders.Names.EXPIRES, "0");
        output.headers().set(HttpHeaders.Names.ACCEPT_RANGES, "bytes");
        output.headers().set(HttpHeaders.Names.CONNECTION, "keep-alive");
        inputStream = getResourceInputStream((argument.equals("description/fetch") ? "PMS.xml" : argument));

        if (argument.equals("description/fetch")) {
            byte b[] = new byte[inputStream.available()];
            inputStream.read(b);
            String s = new String(b);
            s = s.replace("[uuid]", PMS.get().usn()); //.substring(0, PMS.get().usn().length()-2));

            String profileName = "";
            if (configuration.isAppendProfileName()) {
                profileName = " [" + configuration.getProfileName() + "]";
            }

            String serverName = configuration.getServerName();

            if (PMS.get().getServer().getHost() != null) {
                s = s.replace("[host]", PMS.get().getServer().getHost());
                s = s.replace("[port]", "" + PMS.get().getServer().getPort());
            }

            if (xbox360) {
                LOGGER.debug("DLNA changes for Xbox 360");
                s = s.replace("Universal Media Server", serverName + profileName + " : Windows Media Connect");
                s = s.replace("<modelName>UMS</modelName>", "<modelName>Windows Media Connect</modelName>");
                s = s.replace("<serviceList>", "<serviceList>" + CRLF + "<service>" + CRLF
                        + "<serviceType>urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1</serviceType>"
                        + CRLF
                        + "<serviceId>urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar</serviceId>"
                        + CRLF + "<SCPDURL>/upnp/mrr/scpd</SCPDURL>" + CRLF
                        + "<controlURL>/upnp/mrr/control</controlURL>" + CRLF + "</service>" + CRLF);
            } else {
                s = s.replace("Universal Media Server", serverName + profileName);
            }

            response.append(s);
            inputStream = null;
        }
    } else if (method.equals("POST")
            && (argument.contains("MS_MediaReceiverRegistrar_control") || argument.contains("mrr/control"))) {
        output.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/xml; charset=\"utf-8\"");
        response.append(HTTPXMLHelper.XML_HEADER);
        response.append(CRLF);
        response.append(HTTPXMLHelper.SOAP_ENCODING_HEADER);
        response.append(CRLF);

        if (soapaction != null && soapaction.contains("IsAuthorized")) {
            response.append(HTTPXMLHelper.XBOX_360_2);
            response.append(CRLF);
        } else if (soapaction != null && soapaction.contains("IsValidated")) {
            response.append(HTTPXMLHelper.XBOX_360_1);
            response.append(CRLF);
        }

        response.append(HTTPXMLHelper.BROWSERESPONSE_FOOTER);
        response.append(CRLF);
        response.append(HTTPXMLHelper.SOAP_ENCODING_FOOTER);
        response.append(CRLF);
    } else if (method.equals("POST") && argument.endsWith("upnp/control/connection_manager")) {
        output.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/xml; charset=\"utf-8\"");

        if (soapaction != null && soapaction.contains("ConnectionManager:1#GetProtocolInfo")) {
            response.append(HTTPXMLHelper.XML_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.PROTOCOLINFO_RESPONSE);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_FOOTER);
            response.append(CRLF);
        }
    } else if (method.equals("POST") && argument.endsWith("upnp/control/content_directory")) {
        output.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/xml; charset=\"utf-8\"");

        if (soapaction != null && soapaction.contains("ContentDirectory:1#GetSystemUpdateID")) {
            response.append(HTTPXMLHelper.XML_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.GETSYSTEMUPDATEID_HEADER);
            response.append(CRLF);
            response.append("<Id>").append(DLNAResource.getSystemUpdateId()).append("</Id>");
            response.append(CRLF);
            response.append(HTTPXMLHelper.GETSYSTEMUPDATEID_FOOTER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_FOOTER);
            response.append(CRLF);
        } else if (soapaction != null && soapaction.contains("ContentDirectory:1#X_GetFeatureList")) { // Added for Samsung 2012 TVs
            response.append(HTTPXMLHelper.XML_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SAMSUNG_ERROR_RESPONSE);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_FOOTER);
            response.append(CRLF);
        } else if (soapaction != null && soapaction.contains("ContentDirectory:1#GetSortCapabilities")) {
            response.append(HTTPXMLHelper.XML_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SORTCAPS_RESPONSE);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_FOOTER);
            response.append(CRLF);
        } else if (soapaction != null && soapaction.contains("ContentDirectory:1#GetSearchCapabilities")) {
            response.append(HTTPXMLHelper.XML_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SEARCHCAPS_RESPONSE);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_FOOTER);
            response.append(CRLF);
        } else if (soapaction != null && (soapaction.contains("ContentDirectory:1#Browse")
                || soapaction.contains("ContentDirectory:1#Search"))) {
            //LOGGER.trace(content);
            objectID = getEnclosingValue(content, "<ObjectID", "</ObjectID>");
            String containerID = null;
            if ((objectID == null || objectID.length() == 0)) {
                containerID = getEnclosingValue(content, "<ContainerID", "</ContainerID>");
                if (containerID == null || (xbox360 && !containerID.contains("$"))) {
                    objectID = "0";
                } else {
                    objectID = containerID;
                    containerID = null;
                }
            }
            String sI = getEnclosingValue(content, "<StartingIndex", "</StartingIndex>");
            String rC = getEnclosingValue(content, "<RequestedCount", "</RequestedCount>");
            browseFlag = getEnclosingValue(content, "<BrowseFlag", "</BrowseFlag>");

            if (sI != null) {
                startingIndex = Integer.parseInt(sI);
            }

            if (rC != null) {
                requestCount = Integer.parseInt(rC);
            }

            response.append(HTTPXMLHelper.XML_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_HEADER);
            response.append(CRLF);

            if (soapaction != null && soapaction.contains("ContentDirectory:1#Search")) {
                response.append(HTTPXMLHelper.SEARCHRESPONSE_HEADER);
            } else {
                response.append(HTTPXMLHelper.BROWSERESPONSE_HEADER);
            }

            response.append(CRLF);
            response.append(HTTPXMLHelper.RESULT_HEADER);
            response.append(HTTPXMLHelper.DIDL_HEADER);

            boolean browseDirectChildren = browseFlag != null && browseFlag.equals("BrowseDirectChildren");

            if (soapaction != null && soapaction.contains("ContentDirectory:1#Search")) {
                browseDirectChildren = true;
            }

            // Xbox 360 virtual containers ... d'oh!
            String searchCriteria = null;
            if (xbox360 && configuration.getUseCache() && PMS.get().getLibrary() != null
                    && containerID != null) {
                if (containerID.equals("7") && PMS.get().getLibrary().getAlbumFolder() != null) {
                    objectID = PMS.get().getLibrary().getAlbumFolder().getResourceId();
                } else if (containerID.equals("6") && PMS.get().getLibrary().getArtistFolder() != null) {
                    objectID = PMS.get().getLibrary().getArtistFolder().getResourceId();
                } else if (containerID.equals("5") && PMS.get().getLibrary().getGenreFolder() != null) {
                    objectID = PMS.get().getLibrary().getGenreFolder().getResourceId();
                } else if (containerID.equals("F") && PMS.get().getLibrary().getPlaylistFolder() != null) {
                    objectID = PMS.get().getLibrary().getPlaylistFolder().getResourceId();
                } else if (containerID.equals("4") && PMS.get().getLibrary().getAllFolder() != null) {
                    objectID = PMS.get().getLibrary().getAllFolder().getResourceId();
                } else if (containerID.equals("1")) {
                    String artist = getEnclosingValue(content, "upnp:artist = &quot;", "&quot;)");
                    if (artist != null) {
                        objectID = PMS.get().getLibrary().getArtistFolder().getResourceId();
                        searchCriteria = artist;
                    }
                }
            } else if (soapaction.contains("ContentDirectory:1#Search")) {
                searchCriteria = getEnclosingValue(content, "<SearchCriteria", "</SearchCriteria>");
            }

            List<DLNAResource> files = PMS.get().getRootFolder(mediaRenderer).getDLNAResources(objectID,
                    browseDirectChildren, startingIndex, requestCount, mediaRenderer, searchCriteria);

            if (searchCriteria != null && files != null) {
                UMSUtils.postSearch(files, searchCriteria);
                if (xbox360) {
                    if (files.size() > 0) {
                        files = files.get(0).getChildren();
                    }
                }
            }

            int minus = 0;
            if (files != null) {
                for (DLNAResource uf : files) {
                    if (xbox360 && containerID != null) {
                        uf.setFakeParentId(containerID);
                    }

                    if (uf.isCompatible(mediaRenderer)
                            && (uf.getPlayer() == null || uf.getPlayer().isPlayerCompatible(mediaRenderer))) {
                        response.append(uf.getDidlString(mediaRenderer));
                    } else {
                        minus++;
                    }
                }
            }

            response.append(HTTPXMLHelper.DIDL_FOOTER);
            response.append(HTTPXMLHelper.RESULT_FOOTER);
            response.append(CRLF);

            int filessize = 0;
            if (files != null) {
                filessize = files.size();
            }

            response.append("<NumberReturned>").append(filessize - minus).append("</NumberReturned>");
            response.append(CRLF);
            DLNAResource parentFolder = null;

            if (files != null && filessize > 0) {
                parentFolder = files.get(0).getParent();
            }

            if (browseDirectChildren && mediaRenderer.isMediaParserV2() && mediaRenderer.isDLNATreeHack()) {
                // with the new parser, files are parsed and analyzed *before*
                // creating the DLNA tree, every 10 items (the ps3 asks 10 by 10),
                // so we do not know exactly the total number of items in the DLNA folder to send
                // (regular files, plus the #transcode folder, maybe the #imdb one, also files can be
                // invalidated and hidden if format is broken or encrypted, etc.).
                // let's send a fake total size to force the renderer to ask following items
                int totalCount = startingIndex + requestCount + 1; // returns 11 when 10 asked

                // If no more elements, send the startingIndex
                if (filessize - minus <= 0) {
                    totalCount = startingIndex;
                }

                response.append("<TotalMatches>").append(totalCount).append("</TotalMatches>");
            } else if (browseDirectChildren) {
                response.append("<TotalMatches>")
                        .append(((parentFolder != null) ? parentFolder.childrenNumber() : filessize) - minus)
                        .append("</TotalMatches>");
            } else {
                // From upnp spec: If BrowseMetadata is specified in the BrowseFlags then TotalMatches = 1
                response.append("<TotalMatches>1</TotalMatches>");
            }

            response.append(CRLF);
            response.append("<UpdateID>");

            if (parentFolder != null) {
                response.append(parentFolder.getUpdateId());
            } else {
                response.append("1");
            }

            response.append("</UpdateID>");
            response.append(CRLF);
            if (soapaction != null && soapaction.contains("ContentDirectory:1#Search")) {
                response.append(HTTPXMLHelper.SEARCHRESPONSE_FOOTER);
            } else {
                response.append(HTTPXMLHelper.BROWSERESPONSE_FOOTER);
            }
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_FOOTER);
            response.append(CRLF);
            //LOGGER.trace(response.toString());
        }
    } else if (method.equals("SUBSCRIBE")) {
        output.headers().set("SID", PMS.get().usn());
        output.headers().set("TIMEOUT", "Second-1800");

        if (soapaction != null) {
            String cb = soapaction.replace("<", "").replace(">", "");

            try {
                URL soapActionUrl = new URL(cb);
                String addr = soapActionUrl.getHost();
                int port = soapActionUrl.getPort();
                Socket sock = new Socket(addr, port);
                try (OutputStream out = sock.getOutputStream()) {
                    out.write(("NOTIFY /" + argument + " HTTP/1.1").getBytes());
                    out.write(CRLF.getBytes());
                    out.write(("SID: " + PMS.get().usn()).getBytes());
                    out.write(CRLF.getBytes());
                    out.write(("SEQ: " + 0).getBytes());
                    out.write(CRLF.getBytes());
                    out.write(("NT: upnp:event").getBytes());
                    out.write(CRLF.getBytes());
                    out.write(("NTS: upnp:propchange").getBytes());
                    out.write(CRLF.getBytes());
                    out.write(("HOST: " + addr + ":" + port).getBytes());
                    out.write(CRLF.getBytes());
                    out.flush();
                    sock.close();
                }
            } catch (MalformedURLException ex) {
                LOGGER.debug("Cannot parse address and port from soap action \"" + soapaction + "\"", ex);
            }
        } else {
            LOGGER.debug("Expected soap action in request");
        }

        if (argument.contains("connection_manager")) {
            response.append(HTTPXMLHelper.eventHeader("urn:schemas-upnp-org:service:ConnectionManager:1"));
            response.append(HTTPXMLHelper.eventProp("SinkProtocolInfo"));
            response.append(HTTPXMLHelper.eventProp("SourceProtocolInfo"));
            response.append(HTTPXMLHelper.eventProp("CurrentConnectionIDs"));
            response.append(HTTPXMLHelper.EVENT_FOOTER);
        } else if (argument.contains("content_directory")) {
            response.append(HTTPXMLHelper.eventHeader("urn:schemas-upnp-org:service:ContentDirectory:1"));
            response.append(HTTPXMLHelper.eventProp("TransferIDs"));
            response.append(HTTPXMLHelper.eventProp("ContainerUpdateIDs"));
            response.append(HTTPXMLHelper.eventProp("SystemUpdateID", "" + DLNAResource.getSystemUpdateId()));
            response.append(HTTPXMLHelper.EVENT_FOOTER);
        }
    } else if (method.equals("NOTIFY")) {
        output.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/xml");
        output.headers().set("NT", "upnp:event");
        output.headers().set("NTS", "upnp:propchange");
        output.headers().set("SID", PMS.get().usn());
        output.headers().set("SEQ", "0");
        response.append("<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">");
        response.append("<e:property>");
        response.append("<TransferIDs></TransferIDs>");
        response.append("</e:property>");
        response.append("<e:property>");
        response.append("<ContainerUpdateIDs></ContainerUpdateIDs>");
        response.append("</e:property>");
        response.append("<e:property>");
        response.append("<SystemUpdateID>").append(DLNAResource.getSystemUpdateId())
                .append("</SystemUpdateID>");
        response.append("</e:property>");
        response.append("</e:propertyset>");
    }

    output.headers().set("Server", PMS.get().getServerName());

    if (response.length() > 0) {
        // A response message was constructed; convert it to data ready to be sent.
        byte responseData[] = response.toString().getBytes("UTF-8");
        output.headers().set(HttpHeaders.Names.CONTENT_LENGTH, "" + responseData.length);

        // HEAD requests only require headers to be set, no need to set contents.
        if (!method.equals("HEAD")) {
            // Not a HEAD request, so set the contents of the response.
            ByteBuf buf = Unpooled.copiedBuffer(responseData);
            HttpResponse oldOutput = output;
            output = new DefaultFullHttpResponse(output.getProtocolVersion(), output.getStatus(), buf);
            output.headers().add(oldOutput.headers());
        }

        // Send the response to the client.
        future = ctx.writeAndFlush(output);

        if (close) {
            // Close the channel after the response is sent.
            future.addListener(ChannelFutureListener.CLOSE);
        }
    } else if (inputStream != null) {
        // There is an input stream to send as a response.

        if (CLoverride > -2) {
            // Content-Length override has been set, send or omit as appropriate
            if (CLoverride > -1 && CLoverride != DLNAMediaInfo.TRANS_SIZE) {
                // Since PS3 firmware 2.50, it is wiser not to send an arbitrary Content-Length,
                // as the PS3 will display a network error and request the last seconds of the
                // transcoded video. Better to send no Content-Length at all.
                output.headers().set(HttpHeaders.Names.CONTENT_LENGTH, "" + CLoverride);
            }
        } else {
            int cl = inputStream.available();
            LOGGER.trace("Available Content-Length: " + cl);
            output.headers().set(HttpHeaders.Names.CONTENT_LENGTH, "" + cl);
        }

        if (range.isStartOffsetAvailable() && dlna != null) {
            // Add timeseek information headers.
            String timeseekValue = StringUtil.convertTimeToString(range.getStartOrZero(),
                    StringUtil.DURATION_TIME_FORMAT);
            String timetotalValue = dlna.getMedia().getDurationString();
            String timeEndValue = range.isEndLimitAvailable()
                    ? StringUtil.convertTimeToString(range.getEnd(), StringUtil.DURATION_TIME_FORMAT)
                    : timetotalValue;
            output.headers().set("TimeSeekRange.dlna.org",
                    "npt=" + timeseekValue + "-" + timeEndValue + "/" + timetotalValue);
            output.headers().set("X-Seek-Range",
                    "npt=" + timeseekValue + "-" + timeEndValue + "/" + timetotalValue);
        }

        // Send the response headers to the client.
        future = ctx.write(output);

        if (lowRange != DLNAMediaInfo.ENDFILE_POS && !method.equals("HEAD")) {
            // Send the response body to the client in chunks.
            ChannelFuture chunkWriteFuture = ctx.writeAndFlush(new ChunkedStream(inputStream, BUFFER_SIZE));

            // Add a listener to clean up after sending the entire response body.
            chunkWriteFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) {
                    try {
                        PMS.get().getRegistry().reenableGoToSleep();
                        inputStream.close();
                    } catch (IOException e) {
                        LOGGER.debug("Caught exception", e);
                    }

                    // Always close the channel after the response is sent because of
                    // a freeze at the end of video when the channel is not closed.
                    future.channel().close();
                    startStopListenerDelegate.stop();
                }
            });
        } else {
            // HEAD method is being used, so simply clean up after the response was sent.
            ctx.flush();
            try {
                PMS.get().getRegistry().reenableGoToSleep();
                inputStream.close();
            } catch (IOException ioe) {
                LOGGER.debug("Caught exception", ioe);
            }

            if (close) {
                // Close the channel after the response is sent
                future.addListener(ChannelFutureListener.CLOSE);
            }

            startStopListenerDelegate.stop();
        }
    } else {
        // No response data and no input stream. Seems we are merely serving up headers.
        if (lowRange > 0 && highRange > 0) {
            // FIXME: There is no content, so why set a length?
            output.headers().set(HttpHeaders.Names.CONTENT_LENGTH, "" + (highRange - lowRange + 1));
        } else {
            output.headers().set(HttpHeaders.Names.CONTENT_LENGTH, "0");
        }

        // Send the response headers to the client.
        future = ctx.writeAndFlush(output);

        if (close) {
            // Close the channel after the response is sent.
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }

    // Log trace information
    Iterator<String> it = output.headers().names().iterator();

    while (it.hasNext()) {
        String headerName = it.next();
        LOGGER.trace("Sent to socket: " + headerName + ": " + output.headers().get(headerName));
    }

    return future;
}