diff --git a/airsonic-main/pom.xml b/airsonic-main/pom.xml
index ad6e8d02..f632c631 100755
--- a/airsonic-main/pom.xml
+++ b/airsonic-main/pom.xml
@@ -493,6 +493,13 @@
provided
+
+
+ org.eclipse.jetty
+ jetty-io
+ provided
+
+
com.fasterxml.jackson.core
jackson-core
diff --git a/airsonic-main/src/main/java/org/airsonic/player/controller/StreamController.java b/airsonic-main/src/main/java/org/airsonic/player/controller/StreamController.java
index 1a40a656..83f0e6c7 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/controller/StreamController.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/controller/StreamController.java
@@ -107,7 +107,7 @@ public class StreamController {
playQueue.addFiles(false, playlistService.getFilesInPlaylist(playlistId));
player.setPlayQueue(playQueue);
Util.setContentLength(response, playQueue.length());
- LOG.info("Incoming Podcast request for playlist " + playlistId);
+ LOG.info("{}: Incoming Podcast request for playlist {}", request.getRemoteAddr(), playlistId);
}
response.setHeader("Access-Control-Allow-Origin", "*");
@@ -164,7 +164,7 @@ public class StreamController {
range = getRange(request, file);
if (settingsService.isEnableSeek() && range != null && !file.isVideo()) {
- LOG.info("Got HTTP range: " + range);
+ LOG.info("{}: Got HTTP range: {}", request.getRemoteAddr(), range);
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
Util.setContentLength(response, range.isClosed() ? range.size() : fileLength - range.getFirstBytePos());
long lastBytePos = range.getLastBytePos() != null ? range.getLastBytePos() : fileLength - 1;
@@ -248,12 +248,22 @@ public class StreamController {
}
}
}
- } catch (Exception err) {
- if("org.apache.catalina.connector.ClientAbortException".equals(err.getClass().getName())) {
- LOG.info("org.apache.catalina.connector.ClientAbortException: Connection reset");
+ } catch (IOException e) {
+
+ // This happens often and outside of the control of the server, so
+ // we catch Tomcat/Jetty "connection aborted by client" exceptions
+ // and display a short error message.
+ boolean shouldCatch = false;
+ shouldCatch |= Util.isInstanceOfClassName(e, "org.apache.catalina.connector.ClientAbortException");
+ shouldCatch |= Util.isInstanceOfClassName(e, "org.eclipse.jetty.io.EofException");
+ if (shouldCatch) {
+ LOG.info("{}: Client unexpectedly closed connection while loading {} ({})", request.getRemoteAddr(), Util.getURLForRequest(request), e.getCause().toString());
return;
}
- LOG.error("Error occurred in handleRequest.", err);
+
+ // Rethrow the exception in all other cases
+ throw e;
+
} finally {
if (status != null) {
securityService.updateUserByteCounts(user, status.getBytesTransfered(), 0L, 0L);
@@ -428,5 +438,4 @@ public class StreamController {
out.write(buf);
out.flush();
}
-
}
diff --git a/airsonic-main/src/main/java/org/airsonic/player/io/PlayQueueInputStream.java b/airsonic-main/src/main/java/org/airsonic/player/io/PlayQueueInputStream.java
index b48f1e14..af799c62 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/io/PlayQueueInputStream.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/io/PlayQueueInputStream.java
@@ -117,7 +117,7 @@ public class PlayQueueInputStream extends InputStream {
close();
} else if (!file.equals(currentFile)) {
close();
- LOG.info(player.getUsername() + " listening to \"" + FileUtil.getShortPath(file.getFile()) + "\"");
+ LOG.info("{}: {} listening to {}", player.getIpAddress(), player.getUsername(), FileUtil.getShortPath(file.getFile()));
mediaFileService.incrementPlayCount(file);
// Don't scrobble REST players (except Sonos)
diff --git a/airsonic-main/src/main/java/org/airsonic/player/spring/LoggingExceptionResolver.java b/airsonic-main/src/main/java/org/airsonic/player/spring/LoggingExceptionResolver.java
index f374b74d..0dcb193d 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/spring/LoggingExceptionResolver.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/spring/LoggingExceptionResolver.java
@@ -1,5 +1,6 @@
package org.airsonic.player.spring;
+import org.airsonic.player.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
@@ -11,13 +12,25 @@ import javax.servlet.http.HttpServletResponse;
public class LoggingExceptionResolver implements HandlerExceptionResolver, Ordered {
- private static final Logger logger = LoggerFactory.getLogger(LoggingExceptionResolver.class);
+ private static final Logger LOG = LoggerFactory.getLogger(LoggingExceptionResolver.class);
@Override
public ModelAndView resolveException(
- HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e
+ HttpServletRequest request, HttpServletResponse response, Object o, Exception e
) {
- logger.error("Exception occurred", e);
+ // This happens often and outside of the control of the server, so
+ // we catch Tomcat/Jetty "connection aborted by client" exceptions
+ // and display a short error message.
+ boolean shouldCatch = false;
+ shouldCatch |= Util.isInstanceOfClassName(e, "org.apache.catalina.connector.ClientAbortException");
+ shouldCatch |= Util.isInstanceOfClassName(e, "org.eclipse.jetty.io.EofException");
+ if (shouldCatch) {
+ LOG.info("{}: Client unexpectedly closed connection while loading {} ({})", request.getRemoteAddr(), Util.getURLForRequest(request), e.getCause().toString());
+ return null;
+ }
+
+ // Display a full stack trace in all other cases
+ LOG.error("{}: An exception occurred while loading {}", request.getRemoteAddr(), Util.getURLForRequest(request), e);
return null;
}
diff --git a/airsonic-main/src/main/java/org/airsonic/player/util/Util.java b/airsonic-main/src/main/java/org/airsonic/player/util/Util.java
index 294d3fb4..0a758f03 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/util/Util.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/util/Util.java
@@ -25,6 +25,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
@@ -117,4 +118,30 @@ public final class Util {
return "";
}
}
+
+ /**
+ * Return a complete URL for the given HTTP request,
+ * including the query string.
+ *
+ * @param request An HTTP request instance
+ * @return The associated URL
+ */
+ public static String getURLForRequest(HttpServletRequest request) {
+ String url = request.getRequestURL().toString();
+ String queryString = request.getQueryString();
+ if (queryString != null && queryString.length() > 0) url += "?" + queryString;
+ return url;
+ }
+
+ /**
+ * Return true if the given object is an instance of the class name in argument.
+ * If the class doesn't exist, returns false.
+ */
+ public static boolean isInstanceOfClassName(Object o, String className) {
+ try {
+ return Class.forName(className).isInstance(o);
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
}