List of usage examples for io.netty.channel ChannelFuture addListener
@Override ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> listener);
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 = "", "")"); 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; }