Basisklassen zum Modul jdk.httpserver
ulrich
2021-07-03 f5728b1d24410f076777c1a6cd409c5e65d72051
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 /**
41  *
42  * @author Ulrich Hilger
43  * @version 1, 11.06.2021
44  */
45 public class FileActor {
46
47   /**
48    * Einen Teil des Inhalts einer Datei ausliefern
49    *
50    * Wenn eine Range angefragt wird, hat die Antwort einen Content-Range Header
51    * wie folgt:
52    *
53    * <code>
54    * Content-Range: bytes 0-1023/146515
55    * Content-Length: 1024
56    * </code>
57    *
58    * Wenn mehrere Ranges angefragt werden, hat die Antwort mehrere Content-Range
59    * Header als Multipart Response. Multipart Responses fehlen dieser
60    * Implementierung noch.
61    *
62    * (vgl. https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests)
63    *
64    * @param e das Objekt mit Methoden zur Untersuchung der Anfrage sowie zum
65    * Anfertigen und Senden der Antwort
66    * @param file die Datei, deren Inhalt teilweise ausgeliefert werden soll
67    * @throws IOException falls etwas schief geht entsteht dieser Fehler
68    */
69   /*
70    */
71   public void serveFileParts(HttpExchange e, File file) throws IOException {
72     if (file.exists()) {
73       HttpResponder fs = new HttpResponder();
74       fs.setHeaders(e, file);
75       long responseLength = 0;
76       long start = 0;
77       long end;
78       RangeGroup rangeGroup = parseRanges(e, file);
79       Iterator<Range> i = rangeGroup.getRanges();
80       Headers resHeaders = e.getResponseHeaders();
81       while (i.hasNext()) {
82         Range range = i.next();
83         start = range.getStart();
84         end = range.getEnd();
85         resHeaders.add(CONTENT_RANGE_HEADER, contentRangeHdr(range, file));
86         responseLength += (end - start);
87       }
88       e.sendResponseHeaders(SC_PARTIAL_CONTENT, responseLength);
89       if(HttpResponder.HTTP_GET.equalsIgnoreCase(e.getRequestMethod())) {
90         InputStream is = new FileInputStream(file);
91         OutputStream os = e.getResponseBody();
92         if (start > 0) {
93           is.skip(start);
94         }
95         long count = 0;
96         int byteRead = is.read();
97         while (byteRead > -1 && count < responseLength) {
98           ++count;
99           os.write(byteRead);
100           byteRead = is.read();
101         }
102         os.flush();
103         os.close();
104         is.close();
105       }
106     } else {
107       HttpResponder fs = new HttpResponder();
108       fs.sendNotFound(e, file.getName());
109     }
110   }
111
112   /**
113    * Die Byte-Ranges aus dem Range-Header ermitteln.
114    *
115    * Der Range-Header kann unterschiedliche Abschnitte bezeichnen, Beispiele:
116    * Range: bytes=200-1000, 2000-6576, 19000- Range: bytes=0-499, -500 (vgl.
117    * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range)
118    *
119    * @param e das Objekt mit Methoden zur Untersuchung der Anfrage sowie zum
120    * Anfertigen und Senden der Antwort
121    * @param file die Datei, deren Inhalt ausgeliefert werden soll
122    * @return die angefragten Byte-Ranges
123    */
124   protected RangeGroup parseRanges(HttpExchange e, File file) {
125     RangeGroup ranges = new RangeGroup();
126     String rangeHeader = e.getRequestHeaders().get(RANGE_HEADER).toString();
127
128     /*
129       Inhalt des Range-Headers von nicht benoetigten Angaben befreien
130     
131       Ein Range Header enthaelt neben den Start- und Endwerten der Ranges auch 
132       die Angabe "bytes:". Es ist aber keine andere Auspraegung als Bytes 
133       spezifiziert, daher muss die Angabe nicht ausgewertet werden und kann 
134       entfallen. Der Range-Header kann zudem noch eckige Klammern haben 
135       wie in [bytes=200-1000].
136     
137       Der regulaere Ausdruck "[^\\d-,]" bezeichnet alle Zeichen, die keine 
138       Ziffern 0-9, Bindestrich oder Komma sind.
139      */
140     rangeHeader = rangeHeader.replaceAll(RANGE_PATTERN, "");
141
142     /*
143       Die Ranges ermitteln. 
144     
145       Nach dem vorangegangenen Schritt besteht der Header-Ausdruck nur noch 
146       aus einer mit Kommas getrennten Liste aus Start- und Endwerten wie z.B. 
147       "-103,214-930,1647-"
148     
149       Ein Range-Ausdruck kann dann drei verschiedene Auspraegungen haben:
150       1. Startwert fehlt, z.B. -200
151       2. Start und Ende sind vorhanden, z.B. 101-200
152       3. Endwert fehlt, z.B. 201-
153       
154       Teilt man einen Range-String mit der Methode String.split("-") am 
155       Bindestrich ('-') in ein String-Array 'values' gilt:
156       values.length < 2: Fall 3 ist gegeben
157       values.length > 1 und values[0].length < 1: Fall 1 ist gegeben
158       ansonsten: Fall 2 ist gegeben
159      */
160     String[] rangeArray = rangeHeader.split(STR_COMMA);
161     for (String rangeStr : rangeArray) {
162       Range range = new Range();
163       String[] values = rangeStr.split(STR_DASH);
164       if (values.length < 2) {
165         // Fall 3
166         range.setStart(Long.parseLong(values[0]));
167         range.setEnd(file.length());
168       } else {
169         if (values[0].length() < 1) {
170           // Fall 1
171           range.setStart(0);
172           range.setEnd(Long.parseLong(values[1]));
173         } else {
174           // Fall 2
175           range.setStart(Long.parseLong(values[0]));
176           range.setEnd(Long.parseLong(values[1]));
177         }
178       }
179       ranges.addRange(range);
180     }
181     return ranges;
182   }
183
184   /**
185    * Einen Content-Range Header erzeugen
186    * 
187    * @param range die Range, aus deren Inhalt der Header erzeugt werden soll
188    * @param file  die Datei, die den Inhalt liefert, der vom Header 
189    * bezeichnet wird
190    * @return der Inhalt des Content-Range Headers
191    */
192   protected String contentRangeHdr(Range range, File file) {
193     StringBuilder sb = new StringBuilder();
194     sb.append(HttpResponder.STR_BYTES);
195     sb.append(STR_BLANK);
196     sb.append(range.getStart());
197     sb.append(STR_DASH);
198     sb.append(range.getEnd());
199     sb.append(STR_SLASH);
200     sb.append(file.length());
201     return sb.toString();
202   }
203
204
205
206   
207 }