Basisklassen zum Modul jdk.httpserver
ulrich
2024-01-24 9d3717abd59e1672f5d8d7888ce613afdc7fb3c5
commit | author | age
786e8c 1 /*
U 2   http-base - Extensions to jdk.httpserver
3   Copyright (C) 2021  Ulrich Hilger
4
5   This program is free software: you can redistribute it and/or modify
6   it under the terms of the GNU Affero General Public License as
7   published by the Free Software Foundation, either version 3 of the
8   License, or (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU Affero General Public License for more details.
14
15   You should have received a copy of the GNU Affero General Public License
16   along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  */
18 package de.uhilger.httpserver.base.actor;
19
20 import com.sun.net.httpserver.Headers;
21 import com.sun.net.httpserver.HttpExchange;
22 import de.uhilger.httpserver.base.Range;
23 import de.uhilger.httpserver.base.RangeGroup;
24 import static de.uhilger.httpserver.base.handler.FileHandler.CONTENT_RANGE_HEADER;
25 import static de.uhilger.httpserver.base.handler.FileHandler.RANGE_HEADER;
26 import static de.uhilger.httpserver.base.handler.FileHandler.RANGE_PATTERN;
27 import static de.uhilger.httpserver.base.handler.FileHandler.SC_PARTIAL_CONTENT;
28 import static de.uhilger.httpserver.base.handler.FileHandler.STR_BLANK;
29 import static de.uhilger.httpserver.base.handler.FileHandler.STR_COMMA;
30 import static de.uhilger.httpserver.base.handler.FileHandler.STR_DASH;
31 import static de.uhilger.httpserver.base.handler.FileHandler.STR_SLASH;
32 import de.uhilger.httpserver.base.HttpResponder;
33 import java.io.File;
34 import java.io.FileInputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.OutputStream;
38 import java.util.Iterator;
39
40 /**
4e2a31 41  * Die Klasse FileActor fuehrt die zur Auslieferung von Teilen 
U 42  * einer Datei noetigen Handlungen aus. 
43  * 
786e8c 44  * @author Ulrich Hilger
U 45  * @version 1, 11.06.2021
46  */
47 public class FileActor {
48
49   /**
50    * Einen Teil des Inhalts einer Datei ausliefern
51    *
52    * Wenn eine Range angefragt wird, hat die Antwort einen Content-Range Header
53    * wie folgt:
54    *
55    * <code>
56    * Content-Range: bytes 0-1023/146515
57    * Content-Length: 1024
58    * </code>
59    *
60    * Wenn mehrere Ranges angefragt werden, hat die Antwort mehrere Content-Range
61    * Header als Multipart Response. Multipart Responses fehlen dieser
62    * Implementierung noch.
63    *
64    * (vgl. https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests)
65    *
66    * @param e das Objekt mit Methoden zur Untersuchung der Anfrage sowie zum
67    * Anfertigen und Senden der Antwort
68    * @param file die Datei, deren Inhalt teilweise ausgeliefert werden soll
69    * @throws IOException falls etwas schief geht entsteht dieser Fehler
70    */
71   /*
72    */
73   public void serveFileParts(HttpExchange e, File file) throws IOException {
74     if (file.exists()) {
75       HttpResponder fs = new HttpResponder();
76       fs.setHeaders(e, file);
77       long responseLength = 0;
78       long start = 0;
79       long end;
80       RangeGroup rangeGroup = parseRanges(e, file);
81       Iterator<Range> i = rangeGroup.getRanges();
82       Headers resHeaders = e.getResponseHeaders();
83       while (i.hasNext()) {
84         Range range = i.next();
85         start = range.getStart();
86         end = range.getEnd();
87         resHeaders.add(CONTENT_RANGE_HEADER, contentRangeHdr(range, file));
88         responseLength += (end - start);
89       }
90       e.sendResponseHeaders(SC_PARTIAL_CONTENT, responseLength);
91       if(HttpResponder.HTTP_GET.equalsIgnoreCase(e.getRequestMethod())) {
92         InputStream is = new FileInputStream(file);
93         OutputStream os = e.getResponseBody();
94         if (start > 0) {
95           is.skip(start);
96         }
97         long count = 0;
98         int byteRead = is.read();
99         while (byteRead > -1 && count < responseLength) {
100           ++count;
101           os.write(byteRead);
102           byteRead = is.read();
103         }
104         os.flush();
105         os.close();
106         is.close();
107       }
108     } else {
109       HttpResponder fs = new HttpResponder();
110       fs.sendNotFound(e, file.getName());
111     }
112   }
113
114   /**
115    * Die Byte-Ranges aus dem Range-Header ermitteln.
116    *
117    * Der Range-Header kann unterschiedliche Abschnitte bezeichnen, Beispiele:
118    * Range: bytes=200-1000, 2000-6576, 19000- Range: bytes=0-499, -500 (vgl.
119    * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range)
120    *
121    * @param e das Objekt mit Methoden zur Untersuchung der Anfrage sowie zum
122    * Anfertigen und Senden der Antwort
123    * @param file die Datei, deren Inhalt ausgeliefert werden soll
124    * @return die angefragten Byte-Ranges
125    */
126   protected RangeGroup parseRanges(HttpExchange e, File file) {
127     RangeGroup ranges = new RangeGroup();
128     String rangeHeader = e.getRequestHeaders().get(RANGE_HEADER).toString();
129
130     /*
131       Inhalt des Range-Headers von nicht benoetigten Angaben befreien
132     
133       Ein Range Header enthaelt neben den Start- und Endwerten der Ranges auch 
134       die Angabe "bytes:". Es ist aber keine andere Auspraegung als Bytes 
135       spezifiziert, daher muss die Angabe nicht ausgewertet werden und kann 
136       entfallen. Der Range-Header kann zudem noch eckige Klammern haben 
137       wie in [bytes=200-1000].
138     
139       Der regulaere Ausdruck "[^\\d-,]" bezeichnet alle Zeichen, die keine 
140       Ziffern 0-9, Bindestrich oder Komma sind.
141      */
142     rangeHeader = rangeHeader.replaceAll(RANGE_PATTERN, "");
143
144     /*
145       Die Ranges ermitteln. 
146     
147       Nach dem vorangegangenen Schritt besteht der Header-Ausdruck nur noch 
148       aus einer mit Kommas getrennten Liste aus Start- und Endwerten wie z.B. 
149       "-103,214-930,1647-"
150     
151       Ein Range-Ausdruck kann dann drei verschiedene Auspraegungen haben:
152       1. Startwert fehlt, z.B. -200
153       2. Start und Ende sind vorhanden, z.B. 101-200
154       3. Endwert fehlt, z.B. 201-
155       
156       Teilt man einen Range-String mit der Methode String.split("-") am 
157       Bindestrich ('-') in ein String-Array 'values' gilt:
158       values.length < 2: Fall 3 ist gegeben
159       values.length > 1 und values[0].length < 1: Fall 1 ist gegeben
160       ansonsten: Fall 2 ist gegeben
161      */
162     String[] rangeArray = rangeHeader.split(STR_COMMA);
163     for (String rangeStr : rangeArray) {
164       Range range = new Range();
165       String[] values = rangeStr.split(STR_DASH);
166       if (values.length < 2) {
167         // Fall 3
168         range.setStart(Long.parseLong(values[0]));
169         range.setEnd(file.length());
170       } else {
171         if (values[0].length() < 1) {
172           // Fall 1
173           range.setStart(0);
174           range.setEnd(Long.parseLong(values[1]));
175         } else {
176           // Fall 2
177           range.setStart(Long.parseLong(values[0]));
178           range.setEnd(Long.parseLong(values[1]));
179         }
180       }
181       ranges.addRange(range);
182     }
183     return ranges;
184   }
185
186   /**
187    * Einen Content-Range Header erzeugen
188    * 
189    * @param range die Range, aus deren Inhalt der Header erzeugt werden soll
190    * @param file  die Datei, die den Inhalt liefert, der vom Header 
191    * bezeichnet wird
192    * @return der Inhalt des Content-Range Headers
193    */
194   protected String contentRangeHdr(Range range, File file) {
195     StringBuilder sb = new StringBuilder();
196     sb.append(HttpResponder.STR_BYTES);
197     sb.append(STR_BLANK);
198     sb.append(range.getStart());
199     sb.append(STR_DASH);
200     sb.append(range.getEnd());
201     sb.append(STR_SLASH);
202     sb.append(file.length());
203     return sb.toString();
204   }
205
206
207
208   
209 }