@Override
  public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
    RequestV2 request = null;
    RendererConfiguration renderer = null;
    String userAgentString = null;
    StringBuilder unknownHeaders = new StringBuilder();
    String separator = "";

    HttpRequest nettyRequest = this.nettyRequest = (HttpRequest) e.getMessage();

    InetSocketAddress remoteAddress = (InetSocketAddress) e.getChannel().getRemoteAddress();
    InetAddress ia = remoteAddress.getAddress();

    // Is the request from our own Cling service, i.e. self-originating?
    boolean isSelf =
        ia.getHostAddress().equals(PMS.get().getServer().getHost())
            && nettyRequest.headers().get(HttpHeaders.Names.USER_AGENT) != null
            && nettyRequest.headers().get(HttpHeaders.Names.USER_AGENT).contains("UMS/");

    // Filter if required
    if (isSelf || filterIp(ia)) {
      e.getChannel().close();
      LOGGER.trace(
          isSelf
              ? ("Ignoring self-originating request from " + ia + ":" + remoteAddress.getPort())
              : ("Access denied for address " + ia + " based on IP filter"));
      return;
    }

    LOGGER.trace("Opened request handler on socket " + remoteAddress);
    PMS.get().getRegistry().disableGoToSleep();
    request = new RequestV2(nettyRequest.getMethod().getName(), nettyRequest.getUri().substring(1));
    LOGGER.trace(
        "Request: "
            + nettyRequest.getProtocolVersion().getText()
            + " : "
            + request.getMethod()
            + " : "
            + request.getArgument());

    if (nettyRequest.getProtocolVersion().getMinorVersion() == 0) {
      request.setHttp10(true);
    }

    HttpHeaders headers = nettyRequest.headers();

    // 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.
    if (renderer == null || !renderer.loaded) {
      // Attempt 2: try to recognize the renderer by matching headers
      renderer = RendererConfiguration.getRendererConfigurationByHeaders(headers.entries(), ia);
    }

    if (renderer != null) {
      request.setMediaRenderer(renderer);
    }

    Set<String> headerNames = headers.names();
    Iterator<String> iterator = headerNames.iterator();
    while (iterator.hasNext()) {
      String name = iterator.next();
      String headerLine = name + ": " + headers.get(name);
      LOGGER.trace("Received on socket: " + headerLine);

      if (headerLine.toUpperCase().startsWith("USER-AGENT")) {
        userAgentString = headerLine.substring(headerLine.indexOf(':') + 1).trim();
      }

      try {
        StringTokenizer s = new StringTokenizer(headerLine);
        String temp = s.nextToken();
        if (temp.toUpperCase().equals("SOAPACTION:")) {
          request.setSoapaction(s.nextToken());
        } else if (temp.toUpperCase().equals("CALLBACK:")) {
          request.setSoapaction(s.nextToken());
        } 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 {
          Matcher matcher = TIMERANGE_PATTERN.matcher(headerLine);
          if (matcher.find()) {
            String first = matcher.group(1);
            if (first != null) {
              request.setTimeRangeStartString(first);
            }
            String end = matcher.group(2);
            if (end != null) {
              request.setTimeRangeEndString(end);
            }
          } 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 known headers.
            String lowerCaseHeaderLine = headerLine.toLowerCase();
            for (String knownHeaderString : KNOWN_HEADERS) {
              if (lowerCaseHeaderLine.startsWith(knownHeaderString)) {
                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.
              unknownHeaders.append(separator).append(headerLine);
              separator = ", ";
            }
          }
        }
      } catch (Exception ee) {
        LOGGER.error("Error parsing HTTP headers", ee);
      }
    }

    // Still no media renderer recognized?
    if (request.getMediaRenderer() == 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.
      request.setMediaRenderer(RendererConfiguration.resolve(ia, null));
      if (request.getMediaRenderer() != null) {
        LOGGER.trace(
            "Using default media renderer: " + request.getMediaRenderer().getRendererName());

        if (userAgentString != null && !userAgentString.equals("FDSSDP")) {
          // We have found an unknown renderer
          LOGGER.info(
              "Media renderer was not recognized. Possible identifying HTTP headers: User-Agent: "
                  + userAgentString
                  + ("".equals(unknownHeaders.toString()) ? "" : ", " + unknownHeaders.toString()));
          PMS.get().setRendererFound(request.getMediaRenderer());
        }
      } else {
        // If RendererConfiguration.resolve() didn't return the default renderer
        // it means we know via upnp that it's not really a renderer.
        return;
      }
    } else {
      if (userAgentString != null) {
        LOGGER.debug("HTTP User-Agent: " + userAgentString);
      }

      LOGGER.trace("Recognized media renderer: " + request.getMediaRenderer().getRendererName());
    }

    if (nettyRequest.headers().contains(HttpHeaders.Names.CONTENT_LENGTH)) {
      byte data[] = new byte[(int) HttpHeaders.getContentLength(nettyRequest)];
      ChannelBuffer content = nettyRequest.getContent();
      content.readBytes(data);
      request.setTextContent(new String(data, "UTF-8"));
    }

    LOGGER.trace(
        "HTTP: "
            + request.getArgument()
            + " / "
            + request.getLowRange()
            + "-"
            + request.getHighRange());

    writeResponse(ctx, e, request, ia);
  }