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; + } + } }