You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ace.apache.org by br...@apache.org on 2013/09/19 11:01:28 UTC

svn commit: r1524660 - in /ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet: AgentDeploymentServlet.java ContentRangeResponseWrapper.java DeploymentServlet.java

Author: bramk
Date: Thu Sep 19 09:01:27 2013
New Revision: 1524660

URL: http://svn.apache.org/r1524660
Log:
ACE-386 - Added range request support to (agent)deployment servlets

Added:
    ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/ContentRangeResponseWrapper.java
Modified:
    ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/AgentDeploymentServlet.java
    ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/DeploymentServlet.java

Modified: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/AgentDeploymentServlet.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/AgentDeploymentServlet.java?rev=1524660&r1=1524659&r2=1524660&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/AgentDeploymentServlet.java (original)
+++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/AgentDeploymentServlet.java Thu Sep 19 09:01:27 2013
@@ -251,10 +251,13 @@ public class AgentDeploymentServlet exte
         }
     }
 
-    private void handlePackageDelivery(final String agentID, final Version version, final HttpServletRequest request, final HttpServletResponse response) throws AceRestException {
+    private void handlePackageDelivery(String agentID, Version version, HttpServletRequest request, HttpServletResponse response) throws AceRestException {
         ServletOutputStream output = null;
 
         try {
+            // Wrap response to add support for range requests
+            response = new ContentRangeResponseWrapper(request, response);
+
             InputStream inputStream = null;
             try {
                 inputStream = getAgentFromOBR(m_obrURL, agentID, version);

Added: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/ContentRangeResponseWrapper.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/ContentRangeResponseWrapper.java?rev=1524660&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/ContentRangeResponseWrapper.java (added)
+++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/ContentRangeResponseWrapper.java Thu Sep 19 09:01:27 2013
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ace.deployment.servlet;
+
+import java.io.BufferedInputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+/**
+ * Wraps a HttpServletResponse to add byte range support allowing client to request partial content.
+ * 
+ * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
+ */
+public class ContentRangeResponseWrapper extends HttpServletResponseWrapper {
+
+    /**
+     * Internal helper that Wraps a ServletOutputStream to add byte range support.
+     */
+    private static class ContentRangeOutputStreamWrapper extends ServletOutputStream {
+
+        private final HttpServletResponse m_response;
+        private final long m_requestFirstBytePos;
+        private final long m_requestLastBytePos;
+
+        private final FileOutputStream m_os;
+        private final File m_file;
+
+        private long m_instanceLen = 0l;
+
+        public ContentRangeOutputStreamWrapper(HttpServletResponse response, long firstBytePos, long lastBytePos) throws IOException {
+
+            assert response != null;
+            assert firstBytePos >= 0;
+            assert lastBytePos > firstBytePos;
+
+            m_response = response;
+            m_requestFirstBytePos = firstBytePos;
+            m_requestLastBytePos = lastBytePos;
+
+            // We use a file to buffer because Deployment Packages can be big and the current common ACE Agent case it a
+            // range request for some start position up-to EOF.
+            m_file = File.createTempFile("deploymentpackage", ".jar");
+            m_os = new FileOutputStream(m_file);
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+
+            // We only need to buffer the relevant bytes since we keep track of the instance length in the counter.
+            if (m_instanceLen >= m_requestFirstBytePos
+                && m_instanceLen <= m_requestLastBytePos) {
+                m_os.write(b);
+            }
+            m_instanceLen++;
+        }
+
+        @Override
+        public void close() throws IOException {
+            closeQuietly(m_os);
+
+            long instanceLastBytePos = m_instanceLen - 1;
+            InputStream is = null;
+            ServletOutputStream os = null;
+
+            try {
+                if (instanceLastBytePos < m_requestFirstBytePos) {
+
+                    m_response.setStatus(SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+                    m_response.setHeader("Content-Range", String.format("bytes */%d", m_instanceLen));
+                }
+                else {
+
+                    long firstBytePos = m_requestFirstBytePos;
+                    long lastBytePos = instanceLastBytePos < m_requestLastBytePos ? instanceLastBytePos : m_requestLastBytePos;
+                    long contentLength = lastBytePos - firstBytePos + 1;
+
+                    m_response.setStatus(SC_PARTIAL_CONTENT);
+                    m_response.setHeader("Content-Length", String.valueOf(contentLength));
+                    m_response.setHeader("Content-Range", String.format("bytes %d-%d/%d", firstBytePos, lastBytePos, m_instanceLen));
+
+                    is = new BufferedInputStream(new FileInputStream(m_file));
+                    os = m_response.getOutputStream();
+
+                    int b;
+                    while ((b = is.read()) != -1) {
+                        os.write(b);
+                    }
+                }
+            }
+            finally {
+                closeQuietly(is);
+                closeQuietly(os);
+                m_file.delete();
+            }
+        }
+
+        private static void closeQuietly(Closeable resource) throws IOException {
+            if (resource != null) {
+                try {
+                    resource.close();
+                }
+                catch (Exception e) {
+                    // ignore
+                }
+            }
+        }
+    }
+
+    private final HttpServletResponse m_response;
+    private ServletOutputStream m_outputStream;
+
+    public ContentRangeResponseWrapper(HttpServletRequest request, HttpServletResponse response) throws IOException {
+        super(response);
+
+        assert request != null;
+        assert response != null;
+
+        m_response = response;
+        m_response.setHeader("Accept-Ranges", "bytes");
+
+        // If a valid Range request is present we install the ContentRangeOutputStreamWrapper. Otherwise we do not touch
+        // the response ServletOutputStream until we have to in #getOutputStream().
+        long[] requestRange = getRequestRange(request);
+        if (requestRange != null) {
+            m_outputStream = new ContentRangeOutputStreamWrapper(response, requestRange[0], requestRange[1]);
+        }
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() throws IOException {
+        // If a ContentRangeOutputStreamWrapper is installed we return it. Otherwise we simply delegate to the original
+        // response directly.
+        if (m_outputStream == null) {
+            return m_response.getOutputStream();
+        }
+        return m_outputStream;
+    }
+
+    /**
+     * Pattern that matches valid Range request headers. Note that the lastBytePos group is optional. If it is empty
+     * this indicates all remaining bytes from startBytePos is requested.
+     */
+    private static final Pattern RANGE_REQUEST_PATTERN = Pattern.compile("^bytes=(\\d+)-(\\d+)?$");
+
+    /**
+     * Extracts and validates the Range header from a request. If the header is found but is syntactically invalid it
+     * will be ignored as required by specification.
+     * 
+     * @param request the request to use
+     * @return a long array with two elements (firstBytePos and lastBytePos), or <code>null</code> if no valid Range
+     *         header was found.
+     */
+    private static long[] getRequestRange(HttpServletRequest request) {
+        String rangeHeader = request.getHeader("Range");
+        if (rangeHeader != null) {
+            Matcher rangeMatcher = RANGE_REQUEST_PATTERN.matcher(rangeHeader);
+            if (rangeMatcher.find()) {
+                long firstBytePos = Long.parseLong(rangeMatcher.group(1));
+                long lastBytePos = (rangeMatcher.group(2) != null) ? Long.parseLong(rangeMatcher.group(2)) : Long.MAX_VALUE;
+                if (lastBytePos >= firstBytePos) {
+                    return new long[] { firstBytePos, lastBytePos };
+                }
+            }
+        }
+        return null;
+    }
+}

Modified: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/DeploymentServlet.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/DeploymentServlet.java?rev=1524660&r1=1524659&r2=1524660&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/DeploymentServlet.java (original)
+++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/DeploymentServlet.java Thu Sep 19 09:01:27 2013
@@ -119,9 +119,6 @@ public class DeploymentServlet extends H
             );
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
     protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         if (!authenticate(req)) {
@@ -176,10 +173,14 @@ public class DeploymentServlet extends H
         }
     }
 
-    private void handlePackageDelivery(final String targetID, final String version, final List<String> versions, final HttpServletRequest request, final HttpServletResponse response) throws AceRestException {
+    private void handlePackageDelivery(String targetID, String version, List<String> versions, HttpServletRequest request, HttpServletResponse response) throws AceRestException {
+
         ServletOutputStream output = null;
 
         try {
+            // Wrap response to add support for range requests
+            response = new ContentRangeResponseWrapper(request, response);
+
             if (!versions.contains(version)) {
                 throw new AceRestException(HttpServletResponse.SC_NOT_FOUND, "Unknown version (" + version + ")");
             }
@@ -275,6 +276,7 @@ public class DeploymentServlet extends H
         return "Ace Deployment Servlet Endpoint";
     }
 
+    @Override
     public void updated(Dictionary settings) throws ConfigurationException {
         if (settings != null) {
             String useAuthString = (String) settings.get(KEY_USE_AUTHENTICATION);