1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.waarp.gateway.kernel.http;
21
22 import io.netty.buffer.ByteBuf;
23 import io.netty.buffer.ByteBufAllocator;
24 import io.netty.channel.ChannelFuture;
25 import io.netty.channel.ChannelFutureListener;
26 import io.netty.channel.ChannelHandlerContext;
27 import io.netty.handler.codec.http.DefaultFullHttpResponse;
28 import io.netty.handler.codec.http.DefaultHttpResponse;
29 import io.netty.handler.codec.http.HttpHeaderNames;
30 import io.netty.handler.codec.http.HttpHeaderValues;
31 import io.netty.handler.codec.http.HttpRequest;
32 import io.netty.handler.codec.http.HttpResponse;
33 import io.netty.handler.codec.http.HttpResponseStatus;
34 import io.netty.handler.codec.http.HttpUtil;
35 import io.netty.handler.codec.http.cookie.Cookie;
36 import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
37 import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
38 import org.waarp.common.logging.SysErrLogger;
39 import org.waarp.common.utility.WaarpNettyUtil;
40
41 import javax.activation.MimetypesFileTypeMap;
42 import java.io.File;
43 import java.io.FileInputStream;
44 import java.io.FileNotFoundException;
45 import java.io.IOException;
46 import java.text.DateFormat;
47 import java.text.ParseException;
48 import java.text.SimpleDateFormat;
49 import java.util.ArrayList;
50 import java.util.Calendar;
51 import java.util.Date;
52 import java.util.GregorianCalendar;
53 import java.util.Locale;
54 import java.util.Set;
55 import java.util.TimeZone;
56
57 import static io.netty.handler.codec.http.HttpVersion.*;
58
59
60
61
62 public final class HttpWriteCacheEnable {
63
64
65
66 public static final Locale LOCALE_US = Locale.US;
67
68
69
70
71 public static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT");
72
73
74
75
76 public static final String RFC1123_PATTERN = "EEE, dd MMM yyyyy HH:mm:ss z";
77
78 private static final ArrayList<String> cache_control;
79
80 private static final int MAX_AGE_SECOND = 604800;
81
82 static {
83 cache_control = new ArrayList<String>(2);
84 cache_control.add(HttpHeaderValues.PUBLIC.toString());
85 cache_control.add(HttpHeaderValues.MAX_AGE + "=" + MAX_AGE_SECOND);
86 cache_control.add(HttpHeaderValues.MUST_REVALIDATE.toString());
87 }
88
89
90
91
92 public static final MimetypesFileTypeMap mimetypesFileTypeMap =
93 new MimetypesFileTypeMap();
94
95 static {
96 mimetypesFileTypeMap.addMimeTypes("text/css css CSS");
97 mimetypesFileTypeMap.addMimeTypes("text/javascript js JS");
98
99 mimetypesFileTypeMap.addMimeTypes("application/json json JSON map MAP");
100 mimetypesFileTypeMap.addMimeTypes("text/plain txt text TXT");
101 mimetypesFileTypeMap.addMimeTypes("text/html htm html HTM HTML htmls htx");
102 mimetypesFileTypeMap.addMimeTypes("image/jpeg jpe jpeg jpg JPG");
103 mimetypesFileTypeMap.addMimeTypes("image/png png PNG");
104 mimetypesFileTypeMap.addMimeTypes("image/gif gif GIF");
105 mimetypesFileTypeMap.addMimeTypes("image/x-icon ico ICO");
106 }
107
108 private HttpWriteCacheEnable() {
109 }
110
111
112
113
114
115
116
117
118
119
120 public static void writeFile(final HttpRequest request,
121 final ChannelHandlerContext ctx,
122 final String filename,
123 final String cookieNameToRemove) {
124
125 HttpResponse response;
126 final boolean keepAlive = HttpUtil.isKeepAlive(request);
127 final File file = new File(filename);
128 if (!file.isFile() || !file.canRead()) {
129 SysErrLogger.FAKE_LOGGER.syserr("Cannot read " + file.getAbsolutePath());
130 sendError(request, ctx, cookieNameToRemove, keepAlive);
131 return;
132 }
133 final DateFormat rfc1123Format =
134 new SimpleDateFormat(RFC1123_PATTERN, LOCALE_US);
135 rfc1123Format.setTimeZone(GMT_ZONE);
136 final Date lastModifDate = new Date(file.lastModified());
137 if (request.headers().contains(HttpHeaderNames.IF_MODIFIED_SINCE)) {
138 final String sdate =
139 request.headers().get(HttpHeaderNames.IF_MODIFIED_SINCE);
140 try {
141 final Date ifmodif = rfc1123Format.parse(sdate);
142 if (ifmodif.after(lastModifDate)) {
143 response = new DefaultHttpResponse(HTTP_1_1,
144 HttpResponseStatus.NOT_MODIFIED);
145 handleCookies(request, response, cookieNameToRemove);
146 ctx.writeAndFlush(response);
147 return;
148 }
149 } catch (final ParseException ignored) {
150
151 }
152 }
153 final int fileLength = (int) file.length();
154 final ByteBuf byteBuf =
155 ByteBufAllocator.DEFAULT.buffer(fileLength, fileLength);
156 FileInputStream inputStream = null;
157 try {
158 inputStream = new FileInputStream(file);
159 byteBuf.writeBytes(inputStream, fileLength);
160 } catch (final FileNotFoundException e) {
161 WaarpNettyUtil.release(byteBuf);
162 sendError(request, ctx, cookieNameToRemove, keepAlive);
163 return;
164 } catch (final IOException e) {
165 WaarpNettyUtil.release(byteBuf);
166 sendError(request, ctx, cookieNameToRemove, keepAlive);
167 return;
168 }
169
170 response =
171 new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK, byteBuf);
172 response.headers().set(HttpHeaderNames.CONTENT_LENGTH, fileLength);
173 setContentTypeHeader(response, file);
174 setDateAndCacheHeaders(response, rfc1123Format, lastModifDate);
175
176 if (!keepAlive) {
177 response.headers()
178 .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
179 } else if (request.protocolVersion().equals(HTTP_1_0)) {
180 response.headers()
181 .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
182 }
183
184
185 final ChannelFuture sendFileFuture = ctx.writeAndFlush(response);
186
187
188 if (!keepAlive) {
189
190 sendFileFuture.addListener(ChannelFutureListener.CLOSE);
191 }
192 }
193
194
195
196
197
198
199
200 private static void setContentTypeHeader(final HttpResponse response,
201 final File file) {
202 response.headers().set(HttpHeaderNames.CONTENT_TYPE,
203 mimetypesFileTypeMap.getContentType(file.getPath()));
204 }
205
206
207
208
209
210
211
212
213 private static void setDateAndCacheHeaders(final HttpResponse response,
214 final DateFormat rfc1123Format,
215 final Date lastModifDate) {
216
217 final Calendar time = new GregorianCalendar();
218 response.headers()
219 .set(HttpHeaderNames.DATE, rfc1123Format.format(time.getTime()));
220
221
222 time.add(Calendar.SECOND, MAX_AGE_SECOND);
223 response.headers()
224 .set(HttpHeaderNames.EXPIRES, rfc1123Format.format(time.getTime()));
225 response.headers().set(HttpHeaderNames.CACHE_CONTROL, cache_control);
226 response.headers().set(HttpHeaderNames.LAST_MODIFIED,
227 rfc1123Format.format(lastModifDate));
228 }
229
230 private static void sendError(final HttpRequest request,
231 final ChannelHandlerContext ctx,
232 final String cookieNameToRemove,
233 final boolean keepAlive) {
234 final HttpResponse response;
235 response =
236 new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.NOT_FOUND);
237 response.headers().add(HttpHeaderNames.CONTENT_LENGTH, 0);
238 if (!keepAlive) {
239 response.headers()
240 .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
241 } else if (request.protocolVersion().equals(HTTP_1_0)) {
242 response.headers()
243 .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
244 }
245 handleCookies(request, response, cookieNameToRemove);
246 final ChannelFuture sendFileFuture = ctx.writeAndFlush(response);
247
248 if (!keepAlive) {
249
250 sendFileFuture.addListener(ChannelFutureListener.CLOSE);
251 }
252 }
253
254
255
256
257
258
259
260
261 public static void handleCookies(final HttpRequest request,
262 final HttpResponse response,
263 final String cookieNameToRemove) {
264 final String cookieString = request.headers().get(HttpHeaderNames.COOKIE);
265 if (cookieString != null) {
266 final Set<Cookie> cookies = ServerCookieDecoder.LAX.decode(cookieString);
267 if (!cookies.isEmpty()) {
268
269
270 for (final Cookie cookie : cookies) {
271 if (cookie.name().equalsIgnoreCase(cookieNameToRemove)) {
272
273 } else {
274 response.headers().add(HttpHeaderNames.SET_COOKIE,
275 ServerCookieEncoder.LAX.encode(cookie));
276 }
277 }
278 }
279 }
280 }
281
282 }