use of net.pms.external.StartStopListenerDelegate in project UniversalMediaServer by UniversalMediaServer.
the class RequestHandler method run.
@Override
public void run() {
Request request = null;
StartStopListenerDelegate startStopListenerDelegate = new StartStopListenerDelegate(socket.getInetAddress().getHostAddress());
try {
int receivedContentLength = -1;
String userAgentString = null;
ArrayList<String> identifiers = new ArrayList<>();
RendererConfiguration renderer = null;
InetSocketAddress remoteAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
InetAddress ia = remoteAddress.getAddress();
boolean isSelf = ia.getHostAddress().equals(PMS.get().getServer().getHost());
// Apply the IP filter
if (filterIp(ia)) {
throw new IOException("Access denied for address " + ia + " based on IP filter");
}
// The handler makes a couple of attempts to recognize a renderer from its requests.
// IP address matches from previous requests are preferred, when that fails request
// header matches are attempted and if those fail as well we're stuck with the
// default renderer.
// Attempt 1: try to recognize the renderer by its socket address from previous requests
renderer = RendererConfiguration.getRendererConfigurationBySocketAddress(ia);
// If the renderer exists but isn't marked as loaded it means it's unrecognized
// by upnp and we still need to attempt http recognition here.
boolean unrecognized = renderer == null || !renderer.loaded;
RendererConfiguration.SortedHeaderMap sortedHeaders = unrecognized ? new RendererConfiguration.SortedHeaderMap() : null;
// Gather all the headers
ArrayList<String> headerLines = new ArrayList<>();
String line = br.readLine();
while (line != null && line.length() > 0) {
headerLines.add(line);
if (sortedHeaders != null) {
sortedHeaders.put(line);
}
line = br.readLine();
}
if (unrecognized) {
// Attempt 2: try to recognize the renderer by matching headers
renderer = RendererConfiguration.getRendererConfigurationByHeaders(sortedHeaders, ia);
}
for (String headerLine : headerLines) {
// The request object is created inside the while loop.
if (request != null && request.getMediaRenderer() == null && renderer != null) {
request.setMediaRenderer(renderer);
}
if (headerLine.toUpperCase().startsWith("USER-AGENT")) {
// Is the request from our own Cling service, i.e. self-originating?
if (isSelf && headerLine.contains("UMS/")) {
// LOGGER.trace("Ignoring self-originating request from {}:{}", ia, remoteAddress.getPort());
return;
}
userAgentString = headerLine.substring(headerLine.indexOf(':') + 1).trim();
} else if (renderer != null && headerLine.startsWith("X-PANASONIC-DMP-Profile:")) {
PanasonicDmpProfiles.parsePanasonicDmpProfiles(headerLine, renderer);
}
try {
StringTokenizer s = new StringTokenizer(headerLine);
String temp = s.nextToken();
if (temp.equals("SUBSCRIBE") || temp.equals("GET") || temp.equals("POST") || temp.equals("HEAD")) {
request = new Request(temp, s.nextToken().substring(1));
if (s.hasMoreTokens() && s.nextToken().equals("HTTP/1.0")) {
request.setHttp10(true);
}
} else if (request != null && temp.toUpperCase().equals("CALLBACK:")) {
request.setSoapaction(s.nextToken());
} else if (request != null && temp.toUpperCase().equals("SOAPACTION:")) {
request.setSoapaction(s.nextToken());
} else if (headerLine.toUpperCase().contains("CONTENT-LENGTH:")) {
receivedContentLength = Integer.parseInt(headerLine.substring(headerLine.toUpperCase().indexOf("CONTENT-LENGTH: ") + 16));
} else if (headerLine.toUpperCase().contains("RANGE: BYTES=")) {
String nums = headerLine.substring(headerLine.toUpperCase().indexOf("RANGE: BYTES=") + 13).trim();
StringTokenizer st = new StringTokenizer(nums, "-");
if (!nums.startsWith("-")) {
request.setLowRange(Long.parseLong(st.nextToken()));
}
if (!nums.startsWith("-") && !nums.endsWith("-")) {
request.setHighRange(Long.parseLong(st.nextToken()));
} else {
request.setHighRange(-1);
}
} else if (headerLine.toLowerCase().contains("transfermode.dlna.org:")) {
request.setTransferMode(headerLine.substring(headerLine.toLowerCase().indexOf("transfermode.dlna.org:") + 22).trim());
} else if (headerLine.toLowerCase().contains("getcontentfeatures.dlna.org:")) {
request.setContentFeatures(headerLine.substring(headerLine.toLowerCase().indexOf("getcontentfeatures.dlna.org:") + 28).trim());
} else if (headerLine.toUpperCase().contains("TIMESEEKRANGE.DLNA.ORG: NPT=")) {
// firmware 2.50+
String timeseek = headerLine.substring(headerLine.toUpperCase().indexOf("TIMESEEKRANGE.DLNA.ORG: NPT=") + 28);
if (timeseek.endsWith("-")) {
timeseek = timeseek.substring(0, timeseek.length() - 1);
} else if (timeseek.indexOf('-') > -1) {
timeseek = timeseek.substring(0, timeseek.indexOf('-'));
}
request.setTimeseek(convertStringToTime(timeseek));
} else if (headerLine.toUpperCase().contains("TIMESEEKRANGE.DLNA.ORG : NPT=")) {
// firmware 2.40
String timeseek = headerLine.substring(headerLine.toUpperCase().indexOf("TIMESEEKRANGE.DLNA.ORG : NPT=") + 29);
if (timeseek.endsWith("-")) {
timeseek = timeseek.substring(0, timeseek.length() - 1);
} else if (timeseek.indexOf('-') > -1) {
timeseek = timeseek.substring(0, timeseek.indexOf('-'));
}
request.setTimeseek(convertStringToTime(timeseek));
} else {
/*
* If we made it to here, none of the previous header checks matched.
* Unknown headers make interesting logging info when we cannot recognize
* the media renderer, so keep track of the truly unknown ones.
*/
boolean isKnown = false;
// Try to match possible known headers.
String lowerCaseHeaderLine = headerLine.toLowerCase();
for (String knownHeaderString : KNOWN_HEADERS) {
if (lowerCaseHeaderLine.startsWith(knownHeaderString.toLowerCase())) {
isKnown = true;
break;
}
}
// It may be unusual but already known
if (renderer != null) {
String additionalHeader = renderer.getUserAgentAdditionalHttpHeader();
if (StringUtils.isNotBlank(additionalHeader) && lowerCaseHeaderLine.startsWith(additionalHeader)) {
isKnown = true;
}
}
if (!isKnown) {
// Truly unknown header, therefore interesting. Save for later use.
identifiers.add(headerLine);
}
}
} catch (IllegalArgumentException e) {
LOGGER.error("Error parsing HTTP headers: {}", e.getMessage());
LOGGER.trace("", e);
}
}
if (request != null) {
// Still no media renderer recognized?
if (renderer == null) {
// Attempt 3: Not really an attempt; all other attempts to recognize
// the renderer have failed. The only option left is to assume the
// default renderer.
renderer = RendererConfiguration.resolve(ia, null);
request.setMediaRenderer(renderer);
if (renderer != null) {
LOGGER.debug("Using default media renderer \"{}\"", renderer.getConfName());
if (userAgentString != null && !userAgentString.equals("FDSSDP")) {
// We have found an unknown renderer
identifiers.add(0, "User-Agent: " + userAgentString);
renderer.setIdentifiers(identifiers);
LOGGER.info("Media renderer was not recognized. Possible identifying HTTP headers:\n{}", StringUtils.join(identifiers, "\n"));
}
} else {
// it means we know via upnp that it's not really a renderer.
return;
}
} else if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Recognized media renderer \"{}\"", renderer.getRendererName());
}
}
if (receivedContentLength > 0) {
char[] buf = new char[receivedContentLength];
br.read(buf);
if (request != null) {
String textContent = new String(buf);
request.setTextContent(textContent);
if (LOGGER.isTraceEnabled()) {
logMessageReceived(headerLines, textContent, socket.getRemoteSocketAddress(), renderer);
}
} else if (LOGGER.isTraceEnabled()) {
logMessageReceived(headerLines, null, socket.getRemoteSocketAddress(), renderer);
}
}
if (request != null) {
request.answer(output, startStopListenerDelegate);
}
if (request != null && request.getInputStream() != null) {
request.getInputStream().close();
}
} catch (IOException e) {
LOGGER.error("Unexpected IO error in {}: {}", getClass().getName(), e.getMessage());
// LOGGER.trace("", e);
if (request != null && request.getInputStream() != null) {
try {
LOGGER.trace("Closing input stream: {}", request.getInputStream());
request.getInputStream().close();
} catch (IOException e1) {
LOGGER.error("Error closing input stream: {}", e1);
LOGGER.trace("", e1);
}
}
} finally {
try {
output.close();
br.close();
socket.close();
} catch (IOException e) {
LOGGER.error("Error closing connection: {}", e.getMessage());
LOGGER.trace("", e);
}
startStopListenerDelegate.stop();
}
}
use of net.pms.external.StartStopListenerDelegate in project UniversalMediaServer by UniversalMediaServer.
the class RequestHandlerV2 method writeResponse.
private void writeResponse(ChannelHandlerContext ctx, MessageEvent e, RequestV2 request, InetAddress ia) {
// Decide whether to close the connection or not.
boolean close = HttpHeaders.Values.CLOSE.equalsIgnoreCase(nettyRequest.headers().get(HttpHeaders.Names.CONNECTION)) || nettyRequest.getProtocolVersion().equals(HttpVersion.HTTP_1_0) && !HttpHeaders.Values.KEEP_ALIVE.equalsIgnoreCase(nettyRequest.headers().get(HttpHeaders.Names.CONNECTION));
// Build the response object.
HttpResponse response;
if (request.getLowRange() != 0 || request.getHighRange() != 0) {
response = new DefaultHttpResponse(request.isHttp10() ? HttpVersion.HTTP_1_0 : HttpVersion.HTTP_1_1, HttpResponseStatus.PARTIAL_CONTENT);
} else {
String soapAction = nettyRequest.headers().get("SOAPACTION");
if (soapAction != null && soapAction.contains("X_GetFeatureList")) {
LOGGER.debug("Invalid action in SOAPACTION: " + soapAction);
response = new DefaultHttpResponse(request.isHttp10() ? HttpVersion.HTTP_1_0 : HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR);
} else {
response = new DefaultHttpResponse(request.isHttp10() ? HttpVersion.HTTP_1_0 : HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
}
}
StartStopListenerDelegate startStopListenerDelegate = new StartStopListenerDelegate(ia.getHostAddress());
// Attach it to the context so it can be invoked if connection is reset unexpectedly
ctx.setAttachment(startStopListenerDelegate);
try {
request.answer(response, e, close, startStopListenerDelegate);
} catch (IOException e1) {
LOGGER.debug("HTTP request V2 IO error: " + e1.getMessage());
LOGGER.trace("", e1);
// note: we don't call stop() here in a finally block as
// answer() is non-blocking. we only (may) need to call it
// here in the case of an exception. it's a no-op if it's
// already been called
startStopListenerDelegate.stop();
}
}
use of net.pms.external.StartStopListenerDelegate in project UniversalMediaServer by UniversalMediaServer.
the class WebRender method start.
public void start(DLNAResource dlna) {
if (getPlayingRes() != dlna) {
stop();
}
setPlayingRes(dlna);
if (startStop == null) {
startStop = new StartStopListenerDelegate(ip);
}
startStop.setRenderer(this);
startStop.start(getPlayingRes());
}
use of net.pms.external.StartStopListenerDelegate in project UniversalMediaServer by UniversalMediaServer.
the class RequestHandlerV2 method exceptionCaught.
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
Channel ch = e.getChannel();
Throwable cause = e.getCause();
if (cause instanceof TooLongFrameException) {
sendError(ctx, HttpResponseStatus.BAD_REQUEST);
return;
}
if (cause != null) {
if (cause.getClass().equals(IOException.class)) {
LOGGER.debug("Connection error: " + cause);
StartStopListenerDelegate startStopListenerDelegate = (StartStopListenerDelegate) ctx.getAttachment();
if (startStopListenerDelegate != null) {
LOGGER.debug("Premature end, stopping...");
startStopListenerDelegate.stop();
}
} else if (!cause.getClass().equals(ClosedChannelException.class)) {
LOGGER.debug("Caught exception: {}", cause.getMessage());
LOGGER.trace("", cause);
}
}
if (ch.isConnected()) {
sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
}
ch.close();
}
Aggregations