1 /******************************************************************************
2 
3     HTTP response message generator
4 
5     Before rendering an HTTP response message, the names of all header fields
6     the response may contain must be added, except the General-Header,
7     Response-Header and Entity-Header fields specified in RFC 2616 section 4.5,
8     6.2 and 7.1, respectively.
9     Before calling render(), the values of these message header fields of
10     interest can be assigned by the ParamSet (HttpResponse super class) methods.
11     Header fields with a null value (which is the value reset() assigns to all
12     fields) will be omitted when rendering the response message header.
13     Specification of General-Header fields:
14 
15         See_Also: http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.5
16 
17     Specification of Request-Header fields:
18 
19         See_Also: http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.2
20 
21     Specification of Entity-Header fields:
22 
23         See_Also: http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.1
24 
25     For the definition of the categories the standard request message header
26     fields are of
27 
28         See_Also: http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5
29 
30     Copyright:
31         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
32         All rights reserved.
33 
34     License:
35         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
36         Alternatively, this file may be distributed under the terms of the Tango
37         3-Clause BSD License (see LICENSE_BSD.txt for details).
38 
39  ******************************************************************************/
40 
41 module ocean.net.http.HttpResponse;
42 
43 
44 import ocean.meta.types.Qualifiers;
45 
46 import ocean.core.Verify;
47 
48 import ocean.net.http.HttpConst : HttpResponseCode;
49 
50 import ocean.net.http.message.HttpHeader;
51 
52 import ocean.net.http.consts.StatusCodes: StatusPhrases;
53 import ocean.net.http.consts.HttpVersion: HttpVersion, HttpVersionIds;
54 
55 import ocean.net.http.time.HttpTimeFormatter;
56 
57 import ocean.util.container.AppendBuffer;
58 
59 version (unittest)
60     import ocean.core.Test;
61 
62 /******************************************************************************/
63 
64 class HttpResponse : HttpHeader
65 {
66     /**************************************************************************
67 
68         Response HTTP version; defaults to HTTP/1.1
69 
70      **************************************************************************/
71 
72     private HttpVersion http_version_ = HttpVersion.v1_1;
73 
74     /**************************************************************************
75 
76         Content string buffer
77 
78      **************************************************************************/
79 
80     private AppendBuffer!(char) content;
81 
82     /**************************************************************************
83 
84         Header line appender
85 
86      **************************************************************************/
87 
88     private AppendHeaderLines append_header_lines;
89 
90     /**************************************************************************
91 
92         Time formatter
93 
94      **************************************************************************/
95 
96     private HttpTimeFormatter time;
97 
98     /**************************************************************************
99 
100         Decimal string buffer for Content-Length header value
101 
102      **************************************************************************/
103 
104     private char[ulong_dec_length] dec_content_length;
105 
106     /**************************************************************************
107 
108         Constructor
109 
110         Params:
111             initial_buffer_size = the initial amount of memory to allocate to
112                 hold the rendered response. Will be extended if necessary.
113 
114      **************************************************************************/
115 
116     public this ( size_t initial_buffer_size = 1024 )
117     {
118         super(HeaderFieldNames.Response.NameList,
119               HeaderFieldNames.Entity.NameList);
120 
121         this.append_header_lines = new AppendHeaderLines(
122                 this.content = new AppendBuffer!(char)(initial_buffer_size));
123     }
124 
125     /**************************************************************************
126 
127         Renders the response message, using the 200 "OK" status code.
128         If a message body is provided, the "Content-Length" header field will be
129         set and, if head is false, msg_body will be copied into an internal
130         buffer.
131 
132         Params:
133             msg_body = response message body
134             head     = set to true if msg_body should actually not be appended
135                        to the response message (HEAD response)
136 
137         Returns:
138 
139 
140      **************************************************************************/
141 
142     public cstring render ( cstring msg_body = null, bool head = false )
143     {
144         return this.render(HttpResponseCode.init, msg_body);
145     }
146 
147     /**************************************************************************
148 
149         Renders the response message.
150         If a message body is provided, it is appended to the response message
151         according to RFC 2616, section 4.3; that is,
152         - If status is either below 200 or 204 or 304, neither a message body
153           nor a "Content-Length" header field are appended.
154         - Otherwise, if head is true, a "Content-Length" header field reflecting
155           msg_body.length is appended but the message body itself is not.
156         - Otherwise, if head is false, both a "Content-Length" header field
157           reflecting msg_body.length and the message body itself are appended.
158 
159         If a message body is not provided, the same is done as for a message
160         body with a zero length.
161 
162         Params:
163             status   = status code; must be at least 100 and less than 1000
164             msg_body = response message body
165             head     = set to true if msg_body should actually not be appended
166                        to the response message (HEAD response)
167 
168         Returns:
169             response message (exposes an internal buffer)
170 
171      **************************************************************************/
172 
173     public cstring render ( HttpResponseCode status, cstring msg_body = null,
174         bool head = false )
175     {
176         verify(100 <= status, "invalid HTTP status code (below 100)");
177         verify(status < 1000, "invalid HTTP status code (1000 or above)");
178 
179         bool append_msg_body = this.setContentLength(status, msg_body);
180 
181         this.content.clear();
182 
183         this.setStatusLine(status);
184 
185         this.setDate();
186 
187         foreach (key, val; super) if (val)
188         {
189             this.append_header_lines(key, val);
190         }
191 
192         this.addHeaders(this.append_header_lines);
193 
194         this.content.append("\r\n"[],
195                             (append_msg_body && !head)? msg_body : null);
196 
197         return this.content[];
198     }
199 
200     /**************************************************************************
201 
202         Called by render() when a subclass may use append to add its response
203         header lines.
204 
205         Example:
206 
207         ---
208 
209         class MyHttpResponse : HttpResponse
210         {
211             protected override addHeaders ( AppendHeaderLines append )
212             {
213                 // append "Hello: World!\r\n"
214 
215                 append("Hello", "World!");
216             }
217         }
218 
219         ---
220 
221         If the header field value of a header line needs to be assembled
222         incrementally, the AppendHeaderLines.IncrementalValue may be used; see
223         the documentation of that class below for further information.
224 
225         Params:
226             append = header line appender
227 
228      **************************************************************************/
229 
230     protected void addHeaders ( AppendHeaderLines append ) { }
231 
232     /**************************************************************************
233 
234         Sets the content buffer length to the lowest currently possible value.
235 
236         Returns:
237             this instance
238 
239      **************************************************************************/
240 
241     public typeof (this) minimizeContentBuffer ( )
242     {
243         this.content.minimize();
244 
245         return this;
246     }
247 
248     /**************************************************************************
249 
250         Sets the Content-Length response message header.
251 
252         Params:
253             status   = HTTP status code
254             msg_body = HTTP response message body
255 
256         Returns:
257             true if msg_body should be appended to the HTTP response or false
258             if it should not be appended because a message body is not allowed
259             with the provided status code.
260 
261      **************************************************************************/
262 
263     private bool setContentLength ( HttpResponseCode code, cstring msg_body )
264     {
265         switch (code)
266         {
267             default:
268                 if (code >= 200)
269                 {
270                     bool b = super.set("Content-Length", msg_body.length, this.dec_content_length);
271                     verify(b);
272 
273                     return true;
274                 }
275                 return false;
276 
277             case HttpResponseCode.NoContent:
278                 bool b = super.set("Content-Length", "0");
279                 verify(b);
280 
281                 return false;
282 
283             case HttpResponseCode.NotModified:
284                 return false;
285         }
286     }
287 
288     /**************************************************************************
289 
290         Resets the content and renders the response status line.
291 
292         Params:
293             status   = status code
294 
295         Returns:
296             response status line
297 
298      **************************************************************************/
299 
300     private cstring setStatusLine ( HttpResponseCode status )
301     {
302         verify(this.http_version_ != 0, "HTTP version undefined");
303 
304         char[3] status_dec;
305 
306         return this.content.append(HttpVersionIds[this.http_version_],  " "[],
307                                    super.writeUnsigned(status_dec, status), " "[],
308                                    StatusPhrases[status],             "\r\n"[]);
309     }
310 
311     /**************************************************************************
312 
313         Sets the Date message header to the current wall clock time if it is not
314         already set.
315 
316      **************************************************************************/
317 
318     private void setDate ( )
319     {
320         super.access(HeaderFieldNames.General.Names.Date, (cstring, ref cstring val)
321         {
322             if (!val)
323             {
324                 val = this.time.format();
325             }
326         });
327     }
328 
329     /**************************************************************************
330 
331         Utility class; an instance is passed to addHeaders() to be used by a
332         subclass to append a header line to the response message.
333 
334      **************************************************************************/
335 
336     protected static class AppendHeaderLines
337     {
338         /**********************************************************************
339 
340             Response content
341 
342          **********************************************************************/
343 
344         private AppendBuffer!(char) content;
345 
346         /**********************************************************************
347 
348             Constructor
349 
350             Params:
351                 content = response content
352 
353          **********************************************************************/
354 
355         this ( AppendBuffer!(char) content )
356         {
357             this.content = content;
358         }
359 
360         /**********************************************************************
361 
362             Appends a response message header line; that is, appends
363             name ~ ": " ~ value ~ "\r\n" to the response message content.
364 
365             Params:
366                 name  = header field name
367                 value = header field value
368 
369          **********************************************************************/
370 
371         typeof (this) opCall ( cstring name, cstring value )
372         {
373             this.content.append(name, ": "[], value, "\r\n"[]);
374 
375             return this;
376         }
377 
378         /**********************************************************************
379 
380             true when an instance of AppendHeaderLine for this instance exists.
381 
382          **********************************************************************/
383 
384         private bool occupied = false;
385 
386         /**********************************************************************
387 
388             Utility class to append a response message header line where the
389             value is appended incrementally.
390 
391             Usage in a HttpResponse subclass:
392 
393             ---
394 
395             class MyHttpResponse : HttpResponse
396             {
397                 protected override addHeaders ( AppendHeaderLines append )
398                 {
399                     // append "Hello: World!\r\n"
400 
401                     {
402                         // constructor appends "Hello: "
403 
404                         scope inc_val = append.new IncrementalValue("Hello");
405 
406                          // append "Wor" ~ "ld!"
407 
408                         inc_val.appendToValue("Wor");
409                         inc_val.appendToValue("ld!");
410 
411                         // destructor appends "\r\n"
412                     }
413                 }
414             }
415 
416             ---
417 
418             Note: At most one instance may exist at a time per outer instance.
419 
420          **********************************************************************/
421 
422         class IncrementalValue
423         {
424             /******************************************************************
425 
426                 Constructor; opens a response message header line by appending
427                 name ~ ": " to the response message content.
428 
429                 Params:
430                     name = header field name
431 
432                 In:
433                     No other instance for the outer instance may currently
434                     exist.
435 
436              ******************************************************************/
437 
438             this ( cstring name )
439             {
440                 verify(!this.outer.occupied);
441                 this.outer.occupied = true;
442                 this.outer.content.append(name, ": "[]);
443             }
444 
445             /******************************************************************
446 
447                 Appends str to the header field value.
448 
449                 Params:
450                     chunk = header field value chunk
451 
452              ******************************************************************/
453 
454             void appendToValue ( cstring chunk )
455             {
456                 this.outer.content ~= chunk;
457             }
458 
459             /******************************************************************
460 
461                 Destructor; closes a response message header line by appending
462                 "\r\n" to the response message content.
463 
464              ******************************************************************/
465 
466             ~this ( )
467             {
468                 this.outer.content ~= "\r\n";
469                 this.outer.occupied = false;
470             }
471         }
472     }
473 }
474 
475 unittest
476 {
477     static class MyHttpResponse : HttpResponse
478     {
479         protected override void addHeaders ( AppendHeaderLines append )
480         {
481             {
482                 scope key1 = append..new IncrementalValue("Key1");
483             }
484 
485             {
486                 scope key2 = append..new IncrementalValue("Key2");
487                 key2.appendToValue("chunk1");
488                 key2.appendToValue("chunk2");
489             }
490         }
491     }
492 
493     auto response = new MyHttpResponse;
494     response["Date"] = "dummy";
495     auto rendered = response.render();
496 
497     test!("==")(
498         rendered,
499         "HTTP/1.1 200 OK\r\nDate: dummy\r\nContent-Length: 0\r\n"
500             ~ "Key1: \r\nKey2: chunk1chunk2\r\n\r\n"
501     );
502 }