From ff7e5b88e75bda0d99614bead4f3b559023ca50a Mon Sep 17 00:00:00 2001
From: ulrich
Date: Fri, 26 Mar 2021 16:11:42 +0000
Subject: [PATCH] FileHandler Behandlung gemeinsamer Response Headers

---
 src/de/uhilger/minsrv/handler/FileHandler.java |  295 +++++++++++++++++++++++++++++++++-------------------------
 1 files changed, 169 insertions(+), 126 deletions(-)

diff --git a/src/de/uhilger/minsrv/handler/FileHandler.java b/src/de/uhilger/minsrv/handler/FileHandler.java
index d9d12b4..129cd57 100644
--- a/src/de/uhilger/minsrv/handler/FileHandler.java
+++ b/src/de/uhilger/minsrv/handler/FileHandler.java
@@ -14,7 +14,7 @@
 
   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
-*/
+ */
 package de.uhilger.minsrv.handler;
 
 import com.sun.net.httpserver.Headers;
@@ -26,6 +26,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
@@ -34,82 +35,91 @@
 import java.util.logging.Logger;
 
 /**
- * Die Klasse FileHandler dient zur Auslieferung von Dateiinhalten 
- * &uuml;ber HTTP.
+ * Die Klasse FileHandler dient zur Auslieferung von Dateiinhalten &uuml;ber
+ * HTTP.
  * 
- * @author ulrich
+ * F&uuml;r das Streaming &uuml;ber HTTP wird die 
+ * Auslieferung von Teilinhalten mit dem Accept-Ranges-Header angeboten und 
+ * via Range-Header unterst&uuml;tzt.
+ * (vgl. https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests)
+ * 
+ * @author Ulrich Hilger
  * @version 0.1, 25. M&auml;rz 2021
  */
 public class FileHandler implements HttpHandler {
-  
+
+  /* Der Logger fuer diesen FileHandler */
   private static final Logger logger = Logger.getLogger(FileHandler.class.getName());
 
+  /* Header Namen */
   final static String RANGE_HEADER = "Range";
   final static String CONTENT_RANGE_HEADER = "Content-Range";
   final static String ACCEPT_RANGES_HEADER = "Accept-Ranges";
   final static String LAST_MODIFIED_DATE_HEADER = "Last-Modified";
   final static String CONTENT_TYPE = "Content-Type";
-  
+
+  /* Status Codes */
+  public static final int SC_OK = 200;
   public static final int SC_PARTIAL_CONTENT = 206;
-  
+  public static final int SC_NOT_FOUND = 404;
+
+  /* Ablageort fuer Webinhalte */
   private final String basePath;
 
   /**
    * Ein neues Objekt der Klasse FileHandler erzeugen
-   * 
-   * @param basePath der Pfad zu Inhalten, die von diesem Handler 
-   * ausgeliefert werden
+   *
+   * @param basePath der Pfad zu Inhalten, die von diesem Handler ausgeliefert
+   * werden
    */
   public FileHandler(String basePath) {
     this.basePath = basePath;
   }
 
   /**
-   * Die Datei ermitteln, die sich aus dem angefragten URL ergibt,
-   * pr&uuml;fen, ob die Datei existiert und den Inhalt der Datei 
-   * abh&auml;ngig davon, ob ein Range-Header vorhanden ist, 
-   * ganz oder teilweise ausliefern.
-   * 
-   * @param e  das Objekt mit Methoden zur Untersuchung 
-   * der Anfrage sowie zum Anfertigen und Senden der Antwort
-   * @throws IOException falls etwas schief geht entsteht dieser Fehler 
+   * Die Datei ermitteln, die sich aus dem angefragten URL ergibt, pr&uuml;fen,
+   * ob die Datei existiert und den Inhalt der Datei abh&auml;ngig davon, ob ein
+   * Range-Header vorhanden ist, ganz oder teilweise ausliefern.
+   *
+   * @param e das Objekt mit Methoden zur Untersuchung der Anfrage sowie zum
+   * Anfertigen und Senden der Antwort
+   * @throws IOException falls etwas schief geht entsteht dieser Fehler
    */
   @Override
   public void handle(HttpExchange e) throws IOException {
     String ctxPath = e.getHttpContext().getPath();
     String uriPath = e.getRequestURI().getPath();
+    logger.info(uriPath);
     String fName = uriPath.substring(ctxPath.length());
-    if(fName.startsWith(".")) {
-      throw new IOException("Mit einem Punkt beginnende Dateinamen sind ungueltig.");
-    }
-    Headers headers = e.getRequestHeaders();
-    if (headers.containsKey(RANGE_HEADER)) {
-      logger.info("has range header");
-      File file = new File(basePath, fName);
-      logger.info(file.getAbsolutePath());
-      serveFileParts(e, file);
+    if (fName.startsWith(".")) {
+      sendNotFound(e, fName);
     } else {
-      logger.info("no range header");
-      if (fName.endsWith("/")) {
-        fName += "index.html";
+      Headers headers = e.getRequestHeaders();
+      if (headers.containsKey(RANGE_HEADER)) {
+        serveFileParts(e, new File(basePath, fName));
+      } else {
+        if (fName.endsWith("/")) {
+          fName += "index.html";
+        }
+        serveFile(e, new File(basePath, fName));
       }
-      File file = new File(basePath, fName);
-      serveFile(e, file);
     }
   }
 
   /**
    * Den Inhalt einer Datei ausliefern
-   * 
-   * @param e  das Objekt mit Methoden zur Untersuchung 
-   * der Anfrage sowie zum Anfertigen und Senden der Antwort
+   *
+   * @param e das Objekt mit Methoden zur Untersuchung der Anfrage sowie zum
+   * Anfertigen und Senden der Antwort
    * @param file die Datei, deren Inhalt ausgeliefert werden soll
-   * @throws IOException falls etwas schief geht entsteht dieser Fehler 
+   * @throws IOException falls etwas schief geht entsteht dieser Fehler
    */
   private void serveFile(HttpExchange e, File file) throws IOException {
-    OutputStream os = e.getResponseBody();
     if (file.exists()) {
-      e.sendResponseHeaders(200, file.length());
+      OutputStream os = e.getResponseBody();
+      Headers headers = e.getResponseHeaders();
+      setCommonHeaders(headers, file);
+      e.sendResponseHeaders(SC_OK, file.length());
       InputStream in = new FileInputStream(file);
       int b = in.read();
       while (b > -1) {
@@ -117,99 +127,108 @@
         b = in.read();
       }
       in.close();
+      os.flush();
+      os.close();
     } else {
-      String response = file.getName() + " not found.";
-      byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
-      e.sendResponseHeaders(404, bytes.length);
-      os.write(bytes);
+      sendNotFound(e, file.getName());
     }
-    os.flush();
-    os.close();
   }
-  
+
   /**
    * Einen Teil des Inhalts einer Datei ausliefern
-   * 
-   * @param e  das Objekt mit Methoden zur Untersuchung 
-   * der Anfrage sowie zum Anfertigen und Senden der Antwort
+   *
+   * Wenn eine Range angefragt wird, hat die Antwort einen Content-Range Header
+   * wie folgt:
+   *
+   * <code>
+   * Content-Range: bytes 0-1023/146515
+   * Content-Length: 1024
+   * </code>
+   *
+   * Wenn mehrere Ranges angefragt werden, hat die Antwort mehrere Content-Range
+   * Header als Multipart Response. Multipart Responses fehlen dieser
+   * Implementierung noch.
+   *
+   * (vgl. https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests)
+   *
+   * @param e das Objekt mit Methoden zur Untersuchung der Anfrage sowie zum
+   * Anfertigen und Senden der Antwort
    * @param file die Datei, deren Inhalt teilweise ausgeliefert werden soll
-   * @throws IOException falls etwas schief geht entsteht dieser Fehler 
+   * @throws IOException falls etwas schief geht entsteht dieser Fehler
    */
   /*
-    Wenn eine Range angefragt wird, hat die Antwort einen 
-    Content-Range Header wie folgt:
-
-    Content-Range: bytes 0-1023/146515
-    Content-Length: 1024
-  
-    Wenn mehrere Ranges angefragt werden, hat die Antwort mehrere 
-    Content-Range Header als Multipart Response. Multipart Responses fehlen 
-    dieser Implementierung noch.
-  
-    (vgl. https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests)
-  */
+   */
   private void serveFileParts(HttpExchange e, File file) throws IOException {
-    InputStream is = new FileInputStream(file);
-    OutputStream os = e.getResponseBody();
-    Headers resHeaders = e.getResponseHeaders();
-    long responseLength = 0;
-    long start = 0;
-    long end;
-    RangeGroup rangeGroup = parseRanges(e, file);
-    Iterator<Range> i = rangeGroup.getRanges();
-    while(i.hasNext()) {
-      Range range = i.next();
-      start = range.getStart();
-      end = range.getEnd();
-      StringBuilder sb = new StringBuilder();
-      sb.append("bytes ");
-      sb.append(range.getStart());
-      sb.append("-");
-      sb.append(range.getEnd());
-      sb.append("/");
-      sb.append(file.length());
-      resHeaders.add(CONTENT_RANGE_HEADER, sb.toString());      
-      logger.info(sb.toString());
-      responseLength += (end - start);
-      logger.info("responseLength: " + responseLength);
+    if (file.exists()) {
+      InputStream is = new FileInputStream(file);
+      OutputStream os = e.getResponseBody();
+      Headers resHeaders = e.getResponseHeaders();
+      setCommonHeaders(resHeaders, file);
+      long responseLength = 0;
+      long start = 0;
+      long end;
+      RangeGroup rangeGroup = parseRanges(e, file);
+      Iterator<Range> i = rangeGroup.getRanges();
+      while (i.hasNext()) {
+        Range range = i.next();
+        start = range.getStart();
+        end = range.getEnd();
+        StringBuilder sb = new StringBuilder();
+        sb.append("bytes ");
+        sb.append(range.getStart());
+        sb.append("-");
+        sb.append(range.getEnd());
+        sb.append("/");
+        sb.append(file.length());
+        resHeaders.add(CONTENT_RANGE_HEADER, sb.toString());
+        responseLength += (end - start);
+      }
+      e.sendResponseHeaders(SC_PARTIAL_CONTENT, responseLength);
+      if (start > 0) {
+        is.skip(start);
+      }
+      long count = 0;
+      int byteRead = is.read();
+      while (byteRead > -1 && count < responseLength) {
+        ++count;
+        os.write(byteRead);
+        byteRead = is.read();
+      }
+      os.flush();
+      os.close();
+      is.close();
+    } else {
+      sendNotFound(e, file.getName());
     }
-    resHeaders.add(CONTENT_TYPE, "video/mp4");    
+  }
+  
+  private void setCommonHeaders(Headers resHeaders, File file) throws IOException {
+    resHeaders.add(ACCEPT_RANGES_HEADER, "bytes");
+    String mimeType = Files.probeContentType(file.toPath());
+    if(mimeType != null) {
+      resHeaders.add(CONTENT_TYPE, mimeType);
+    }
     SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
     Date date = new Date(file.lastModified());
     resHeaders.add(LAST_MODIFIED_DATE_HEADER, sdf.format(date));
-    e.sendResponseHeaders(SC_PARTIAL_CONTENT, responseLength);
-    if(start > 0) {
-      is.skip(start);
-    }
-    long count = 0;
-    int byteRead = is.read();
-    while(byteRead > -1 && count < responseLength) {
-      ++count;
-      os.write(byteRead);
-      byteRead = is.read();
-    }    
-    os.flush();
-    os.close();
-    is.close();
   }
-  
+
   /**
    * Die Byte-Ranges aus dem Range-Header ermitteln.
-   * 
+   *
    * Der Range-Header kann unterschiedliche Abschnitte bezeichnen, Beispiele:
-   * Range: bytes=200-1000, 2000-6576, 19000-
-   * Range: bytes=0-499, -500
-   * (vgl. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range)
-   * 
-   * @param e das Objekt mit Methoden zur Untersuchung 
-   * der Anfrage sowie zum Anfertigen und Senden der Antwort
+   * Range: bytes=200-1000, 2000-6576, 19000- Range: bytes=0-499, -500 (vgl.
+   * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range)
+   *
+   * @param e das Objekt mit Methoden zur Untersuchung der Anfrage sowie zum
+   * Anfertigen und Senden der Antwort
    * @param file die Datei, deren Inhalt ausgeliefert werden soll
    * @return die angefragten Byte-Ranges
    */
   private RangeGroup parseRanges(HttpExchange e, File file) {
     RangeGroup ranges = new RangeGroup();
     String rangeHeader = e.getRequestHeaders().get(RANGE_HEADER).toString();
-
+    
     /*
       Inhalt des Range-Headers von nicht benoetigten Angaben befreien
     
@@ -221,10 +240,9 @@
     
       Der regulaere Ausdruck "[^\\d-,]" bezeichnet alle Zeichen, die keine 
       Ziffern 0-9, Bindestrich oder Komma sind.
-    */    
+     */
     rangeHeader = rangeHeader.replaceAll("[^\\d-,]", "");
-    logger.info(rangeHeader);
-    
+
     /*
       Die Ranges ermitteln. 
     
@@ -242,17 +260,17 @@
       values.length < 2: Fall 3 ist gegeben
       values.length > 1 und values[0].length < 1: Fall 1 ist gegeben
       ansonsten: Fall 2 ist gegeben
-    */
+     */
     String[] rangeArray = rangeHeader.split(",");
-    for(String rangeStr : rangeArray) {
+    for (String rangeStr : rangeArray) {
       Range range = new Range();
       String[] values = rangeStr.split("-");
-      if(values.length < 2) {
+      if (values.length < 2) {
         // Fall 3
         range.setStart(Long.parseLong(values[0]));
         range.setEnd(file.length());
       } else {
-        if(values[0].length() < 1) {
+        if (values[0].length() < 1) {
           // Fall 1
           range.setStart(0);
           range.setEnd(Long.parseLong(values[1]));
@@ -263,19 +281,39 @@
         }
       }
       ranges.addRange(range);
-    }    
+    }
     return ranges;
   }
-  
+
+  /**
+   * Eine nicht gefunden Antwort senden
+   * 
+   * @param e das Objekt mit Methoden zur Untersuchung der Anfrage sowie zum
+   * Anfertigen und Senden der Antwort
+   * @param fname Name der Datei, die nicht gefunden wurde
+   * @throws IOException falls etwas schief geht entsteht dieser Fehler
+   */
+  public void sendNotFound(HttpExchange e, String fname) throws IOException {
+    OutputStream os = e.getResponseBody();
+    String response = fname + " not found.";
+    byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
+    e.sendResponseHeaders(SC_NOT_FOUND, bytes.length);
+    os.write(bytes);
+    os.flush();
+    os.close();
+  }
+
   /**
    * Eine Range
    */
   class Range {
+
     private long start;
     private long end;
 
     /**
      * Den Beginn dieser Range ermitteln
+     *
      * @return Beginn dieser Range
      */
     public long getStart() {
@@ -284,6 +322,7 @@
 
     /**
      * Den Beginn dieser Range angeben
+     *
      * @param start Beginn dieser Range
      */
     public void setStart(long start) {
@@ -292,6 +331,7 @@
 
     /**
      * Das Ende dieser Range ermitteln
+     *
      * @return Ende dieser Range
      */
     public long getEnd() {
@@ -300,49 +340,52 @@
 
     /**
      * Das Ende dieser Range angeben
+     *
      * @param end Ende dieser Range
      */
     public void setEnd(long end) {
       this.end = end;
     }
   }
-  
+
   /**
    * Eine Gruppe aus Ranges
    */
   class RangeGroup {
+
     private List<Range> ranges;
     private long totalSize;
-    
+
     /**
      * Ein neues Objekt der Klasse RangeGroup erzeugen
      */
     public RangeGroup() {
       ranges = new ArrayList();
     }
-    
+
     /**
      * Dieser RangeGroup eine Range hinzufuegen.
-     * 
+     *
      * @param range die Range, die dieser RangeGroup hinzugefuegt werden soll
      */
     public void addRange(Range range) {
       ranges.add(range);
       totalSize += range.getEnd() - range.getStart();
     }
-    
+
     /**
-     * Die Gesamtgr&ouml;&szlig;e dieser RangeGroup ermitteln, also die 
-     * Summe der Anzahl von Bytes aller ihrer Ranges.
-     * 
+     * Die Gesamtgr&ouml;&szlig;e dieser RangeGroup ermitteln, also die Summe
+     * der Anzahl von Bytes aller ihrer Ranges.
+     *
      * @return die Gr&ouml;&szlig;e dieser RangeGroup in Bytes
      */
     public long getSize() {
       return totalSize;
     }
-    
+
     /**
      * Einen Iterator &uuml;ber die Ranges dieser RangeGroup abrufen
+     *
      * @return Iterator &uuml;ber die Ranges dieser RangeGroup
      */
     public Iterator<Range> getRanges() {

--
Gitblit v1.9.3