1 package com.bradmcevoy.http.http11;
2
3 import com.bradmcevoy.http.*;
4 import com.bradmcevoy.http.Response.Status;
5 import com.bradmcevoy.http.exceptions.BadRequestException;
6 import java.io.IOException;
7 import java.io.OutputStream;
8 import java.io.PrintWriter;
9 import java.util.Date;
10 import java.util.List;
11 import java.util.Map;
12
13 import org.slf4j.Logger;
14 import org.slf4j.LoggerFactory;
15
16 import com.bradmcevoy.http.exceptions.NotAuthorizedException;
17 import com.bradmcevoy.io.BufferingOutputStream;
18 import com.bradmcevoy.io.ReadingException;
19 import com.bradmcevoy.io.StreamUtils;
20 import com.bradmcevoy.io.WritingException;
21 import java.io.InputStream;
22 import org.apache.commons.io.IOUtils;
23
24
25
26
27 public class DefaultHttp11ResponseHandler implements Http11ResponseHandler {
28
29 public enum BUFFERING {
30 always,
31 never,
32 whenNeeded
33 }
34
35 private static final Logger log = LoggerFactory.getLogger( DefaultHttp11ResponseHandler.class );
36 public static final String METHOD_NOT_ALLOWED_HTML = "<html><body><h1>Method Not Allowed</h1></body></html>";
37 public static final String NOT_FOUND_HTML = "<html><body><h1>${url} Not Found (404)</h1></body></html>";
38 public static final String METHOD_NOT_IMPLEMENTED_HTML = "<html><body><h1>Method Not Implemented</h1></body></html>";
39 public static final String CONFLICT_HTML = "<html><body><h1>Conflict</h1></body></html>";
40 public static final String SERVER_ERROR_HTML = "<html><body><h1>Server Error</h1></body></html>";
41 public static final String NOT_AUTHORISED_HTML = "<html><body><h1>Not authorised</h1></body></html>";
42 private final AuthenticationService authenticationService;
43 private final ETagGenerator eTagGenerator;
44 private CacheControlHelper cacheControlHelper = new DefaultCacheControlHelper();
45 private int maxMemorySize = 100000;
46 private BUFFERING buffering;
47
48 public DefaultHttp11ResponseHandler(AuthenticationService authenticationService) {
49 this.authenticationService = authenticationService;
50 this.eTagGenerator = new DefaultETagGenerator();
51 }
52
53 public DefaultHttp11ResponseHandler(AuthenticationService authenticationService, ETagGenerator eTagGenerator) {
54 this.authenticationService = authenticationService;
55 this.eTagGenerator = eTagGenerator;
56 }
57
58
59
60
61
62 public CacheControlHelper getCacheControlHelper() {
63 return cacheControlHelper;
64 }
65
66 public void setCacheControlHelper(CacheControlHelper cacheControlHelper) {
67 this.cacheControlHelper = cacheControlHelper;
68 }
69
70 public String generateEtag(Resource r) {
71 return eTagGenerator.generateEtag(r);
72 }
73
74 public void respondWithOptions(Resource resource, Response response, Request request, List<String> methodsAllowed) {
75 response.setStatus(Response.Status.SC_OK);
76 response.setAllowHeader(methodsAllowed);
77 response.setContentLengthHeader((long) 0);
78 }
79
80 public void respondNotFound(Response response, Request request) {
81 response.setStatus(Response.Status.SC_NOT_FOUND);
82 response.setContentTypeHeader("text/html");
83 PrintWriter pw = new PrintWriter(response.getOutputStream(), true);
84
85 String s = NOT_FOUND_HTML.replace("${url}", request.getAbsolutePath());
86 pw.print(s);
87 pw.flush();
88
89 }
90
91 public void respondUnauthorised(Resource resource, Response response, Request request) {
92 log.trace("respondUnauthorised");
93 response.setStatus(Response.Status.SC_UNAUTHORIZED);
94 List<String> challenges = authenticationService.getChallenges(resource, request);
95 response.setAuthenticateHeader(challenges);
96
97
98
99
100
101
102
103
104
105 }
106
107 public void respondMethodNotImplemented(Resource resource, Response response, Request request) {
108
109 try {
110 response.setStatus(Response.Status.SC_NOT_IMPLEMENTED);
111 OutputStream out = response.getOutputStream();
112 out.write(METHOD_NOT_IMPLEMENTED_HTML.getBytes());
113 } catch (IOException ex) {
114 log.warn("exception writing content");
115 }
116 }
117
118 public void respondMethodNotAllowed(Resource res, Response response, Request request) {
119 log.debug("method not allowed. handler: " + this.getClass().getName() + " resource: " + res.getClass().getName());
120 try {
121 response.setStatus(Response.Status.SC_METHOD_NOT_ALLOWED);
122 OutputStream out = response.getOutputStream();
123 out.write(METHOD_NOT_ALLOWED_HTML.getBytes());
124 } catch (IOException ex) {
125 log.warn("exception writing content");
126 }
127 }
128
129
130
131
132
133
134
135 public void respondConflict(Resource resource, Response response, Request request, String message) {
136 log.debug("respondConflict");
137 try {
138 response.setStatus(Response.Status.SC_CONFLICT);
139 OutputStream out = response.getOutputStream();
140 out.write(CONFLICT_HTML.getBytes());
141 } catch (IOException ex) {
142 log.warn("exception writing content");
143 }
144 }
145
146 public void respondRedirect(Response response, Request request, String redirectUrl) {
147 if (redirectUrl == null) {
148 throw new NullPointerException("redirectUrl cannot be null");
149 }
150 log.trace("respondRedirect");
151
152 response.sendRedirect(redirectUrl);
153
154
155 }
156
157 public void respondExpectationFailed(Response response, Request request) {
158 response.setStatus(Response.Status.SC_EXPECTATION_FAILED);
159 }
160
161 public void respondCreated(Resource resource, Response response, Request request) {
162
163 response.setStatus(Response.Status.SC_CREATED);
164 }
165
166 public void respondNoContent(Resource resource, Response response, Request request) {
167
168
169
170 response.setStatus(Response.Status.SC_NO_CONTENT);
171 }
172
173 public void respondPartialContent(GetableResource resource, Response response, Request request, Map<String, String> params, Range range) throws NotAuthorizedException, BadRequestException {
174 log.debug("respondPartialContent: " + range.getStart() + " - " + range.getFinish());
175 response.setStatus(Response.Status.SC_PARTIAL_CONTENT);
176 response.setContentRangeHeader(range.getStart(), range.getFinish(), resource.getContentLength());
177 response.setDateHeader(new Date());
178 String etag = eTagGenerator.generateEtag(resource);
179 if (etag != null) {
180 response.setEtag(etag);
181 }
182 String acc = request.getAcceptHeader();
183 String ct = resource.getContentType(acc);
184 if (ct != null) {
185 response.setContentTypeHeader(ct);
186 }
187 try {
188 resource.sendContent(response.getOutputStream(), range, params, ct);
189 } catch (IOException ex) {
190 log.warn("IOException writing to output, probably client terminated connection", ex);
191 }
192 }
193
194 public void respondHead(Resource resource, Response response, Request request) {
195 setRespondContentCommonHeaders(response, resource, Response.Status.SC_NO_CONTENT, request.getAuthorization());
196 }
197
198 public void respondContent(Resource resource, Response response, Request request, Map<String, String> params) throws NotAuthorizedException, BadRequestException {
199 log.debug("respondContent: " + resource.getClass());
200 Auth auth = request.getAuthorization();
201 setRespondContentCommonHeaders(response, resource, auth);
202 if (resource instanceof GetableResource) {
203 GetableResource gr = (GetableResource) resource;
204 String acc = request.getAcceptHeader();
205 String ct = gr.getContentType(acc);
206 if (ct != null) {
207 ct = pickBestContentType(ct);
208 response.setContentTypeHeader(ct);
209 }
210 cacheControlHelper.setCacheControl(gr, response, request.getAuthorization());
211
212 Long contentLength = gr.getContentLength();
213 if (buffering == BUFFERING.always || (contentLength != null && buffering == BUFFERING.whenNeeded)) {
214 log.trace("sending content with known content length: " + contentLength);
215 response.setContentLengthHeader(contentLength);
216 sendContent(request, response, (GetableResource) resource, params, null, ct);
217 } else {
218 log.trace("buffering content...");
219 BufferingOutputStream tempOut = new BufferingOutputStream(maxMemorySize);
220 try {
221 ((GetableResource) resource).sendContent(tempOut, null, params, ct);
222 tempOut.close();
223 } catch (IOException ex) {
224 tempOut.deleteTempFileIfExists();
225 throw new RuntimeException("Exception generating buffered content", ex);
226 }
227 Long bufContentLength = tempOut.getSize();
228 if (contentLength != null) {
229 if (!contentLength.equals(bufContentLength)) {
230 throw new RuntimeException("Lengthd dont match: " + contentLength + " != " + bufContentLength);
231 }
232 }
233 log.trace("sending buffered content...");
234 response.setContentLengthHeader(bufContentLength);
235 InputStream in = tempOut.getInputStream();
236 try {
237 StreamUtils.readTo(in, response.getOutputStream());
238 } catch (ReadingException ex) {
239 throw new RuntimeException(ex);
240 } catch (WritingException ex) {
241 log.warn("exception writing, client probably closed connection", ex);
242 } finally {
243 IOUtils.closeQuietly(in);
244 }
245 return;
246
247
248 }
249
250 }
251 }
252
253 public void respondNotModified(GetableResource resource, Response response, Request request) {
254 log.trace("respondNotModified");
255 response.setStatus(Response.Status.SC_NOT_MODIFIED);
256 response.setDateHeader(new Date());
257 String etag = eTagGenerator.generateEtag(resource);
258 if (etag != null) {
259 response.setEtag(etag);
260 }
261
262
263
264
265 Date modDate = resource.getModifiedDate();
266 response.setLastModifiedHeader(modDate);
267
268 cacheControlHelper.setCacheControl(resource, response, request.getAuthorization());
269 }
270
271 protected void sendContent(Request request, Response response, GetableResource resource, Map<String, String> params, Range range, String contentType) throws NotAuthorizedException, BadRequestException {
272 long l = System.currentTimeMillis();
273 log.trace("sendContent");
274 OutputStream out = outputStreamForResponse(request, response, resource);
275 try {
276 resource.sendContent(out, null, params, contentType);
277 out.flush();
278 if (log.isTraceEnabled()) {
279 l = System.currentTimeMillis() - l;
280 log.trace("sendContent finished in " + l + "ms");
281 }
282 } catch (IOException ex) {
283 log.warn("IOException sending content", ex);
284 }
285 }
286
287 protected OutputStream outputStreamForResponse(Request request, Response response, GetableResource resource) {
288 OutputStream outToUse = response.getOutputStream();
289 return outToUse;
290 }
291
292 protected void output(final Response response, final String s) {
293 PrintWriter pw = new PrintWriter(response.getOutputStream(), true);
294 pw.print(s);
295 pw.flush();
296 }
297
298 protected void setRespondContentCommonHeaders(Response response, Resource resource, Auth auth) {
299 setRespondContentCommonHeaders(response, resource, Response.Status.SC_OK, auth);
300 }
301
302 protected void setRespondContentCommonHeaders(Response response, Resource resource, Response.Status status, Auth auth) {
303 response.setStatus(status);
304 response.setDateHeader(new Date());
305 String etag = eTagGenerator.generateEtag(resource);
306 if (etag != null) {
307 response.setEtag(etag);
308 }
309 setModifiedDate(response, resource, auth);
310 }
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329 public static void setModifiedDate(Response response, Resource resource, Auth auth) {
330 Date modDate = resource.getModifiedDate();
331 if (modDate != null) {
332
333 response.setLastModifiedHeader(modDate);
334
335
336
337
338
339
340
341
342
343
344 }
345 }
346
347 public void respondBadRequest(Resource resource, Response response, Request request) {
348 response.setStatus(Response.Status.SC_BAD_REQUEST);
349 }
350
351 public void respondForbidden(Resource resource, Response response, Request request) {
352 response.setStatus(Response.Status.SC_FORBIDDEN);
353 }
354
355 public void respondDeleteFailed(Request request, Response response, Resource resource, Status status) {
356 response.setStatus(status);
357 }
358
359 public AuthenticationService getAuthenticationService() {
360 return authenticationService;
361 }
362
363 public void respondServerError(Request request, Response response, String reason) {
364 try {
365 response.setStatus(Status.SC_INTERNAL_SERVER_ERROR);
366 OutputStream out = response.getOutputStream();
367 out.write(SERVER_ERROR_HTML.getBytes());
368 } catch (IOException ex) {
369 throw new RuntimeException(ex);
370 }
371 }
372
373
374
375
376
377
378
379 public int getMaxMemorySize() {
380 return maxMemorySize;
381 }
382
383 public void setMaxMemorySize(int maxMemorySize) {
384 this.maxMemorySize = maxMemorySize;
385 }
386
387 public BUFFERING getBuffering() {
388 return buffering;
389 }
390
391 public void setBuffering(BUFFERING buffering) {
392 this.buffering = buffering;
393 }
394
395
396
397
398
399
400
401
402
403 private String pickBestContentType(String ct) {
404 if( ct == null ) {
405 return null;
406 } else if( ct.contains(",")) {
407 return ct.split(",")[0];
408 } else {
409 return ct;
410 }
411 }
412
413 }