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 }