1 module hunt.http.codec.http.encode.HttpGenerator; 2 3 import hunt.http.codec.http.model; 4 import hunt.http.codec.http.hpack.HpackEncoder; 5 6 import hunt.http.HttpField; 7 import hunt.http.HttpFields; 8 import hunt.http.HttpHeader; 9 import hunt.http.HttpMetaData; 10 import hunt.http.HttpMethod; 11 import hunt.http.HttpRequest; 12 import hunt.http.HttpResponse; 13 import hunt.http.HttpStatus; 14 import hunt.http.HttpVersion; 15 16 import hunt.http.Version; 17 import hunt.collection; 18 import hunt.util.DateTime; 19 import hunt.Exceptions; 20 import hunt.Functions; 21 import hunt.logging; 22 import hunt.text.Common; 23 import hunt.util.StringBuilder; 24 import hunt.text.StringUtils; 25 26 import core.time; 27 import std.array; 28 import std.conv; 29 import std.format; 30 import std.string; 31 32 33 /** 34 * HttpGenerator. Builds HTTP Messages. 35 * <p> 36 * If the system property "http.HttpGenerator.STRICT" is set 37 * to true, then the generator will strictly pass on the exact strings received 38 * from methods and header fields. Otherwise a fast case insensitive string 39 * lookup is used that may alter the case and white space of some 40 * methods/headers 41 */ 42 class HttpGenerator { 43 // static bool __STRICT = bool.getBoolean("hunt.http.codec.http.encode.HttpGenerator.STRICT"); 44 45 private enum byte[] __colon_space = [':', ' ']; 46 __gshared HttpResponse CONTINUE_100_INFO; 47 __gshared HttpResponse PROGRESS_102_INFO; 48 __gshared HttpResponse RESPONSE_500_INFO; 49 50 51 // states 52 enum State { 53 START, COMMITTED, COMPLETING, COMPLETING_1XX, END 54 } 55 56 enum Result { 57 NEED_CHUNK, // Need a small chunk buffer of CHUNK_SIZE 58 NEED_INFO, // Need the request/response metadata info 59 NEED_HEADER, // Need a buffer to build HTTP headers into 60 NEED_CHUNK_TRAILER, // Need a large chunk buffer for last chunk and trailers 61 FLUSH, // The buffers previously generated should be flushed 62 CONTINUE, // Continue generating the message 63 SHUTDOWN_OUT, // Need EOF to be signaled 64 DONE // Message generation complete 65 } 66 67 // other statics 68 static int CHUNK_SIZE = 12; 69 70 private State _state = State.START; 71 private EndOfContent _endOfContent = EndOfContent.UNKNOWN_CONTENT; 72 73 private long _contentPrepared = 0; 74 private bool _noContentResponse = false; 75 private bool _persistent = false; 76 private Supplier!HttpFields _trailers = null; 77 78 private int _send; 79 private __gshared int SEND_SERVER = 0x01; 80 private __gshared int SEND_XPOWEREDBY = 0x02; 81 private __gshared bool[string] __assumedContentMethods; // = new ArrayTrie<>(8); 82 83 shared static this() { 84 __assumedContentMethods[HttpMethod.POST.asString()] = true; 85 __assumedContentMethods[HttpMethod.PUT.asString()] = true; 86 CONTINUE_100_INFO = new HttpResponse(HttpVersion.HTTP_1_1, 100, null, null, -1); 87 PROGRESS_102_INFO = new HttpResponse(HttpVersion.HTTP_1_1, 102, null, null, -1); 88 HttpFields hf = new HttpFields(); 89 hf.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE); 90 91 RESPONSE_500_INFO = new HttpResponse(HttpVersion.HTTP_1_1, HttpStatus.INTERNAL_SERVER_ERROR_500, null, hf, 0); 92 } 93 94 /* ------------------------------------------------------------------------------- */ 95 static void setVersion(string serverVersion) { 96 SEND[SEND_SERVER] = StringUtils.getBytes("Server: " ~ serverVersion ~ "\015\012"); 97 SEND[SEND_XPOWEREDBY] = StringUtils.getBytes("X-Powered-By: " ~ serverVersion ~ "\015\012"); 98 SEND[SEND_SERVER | SEND_XPOWEREDBY] = StringUtils.getBytes("Server: " ~ serverVersion ~ 99 "\015\012X-Powered-By: " ~ serverVersion ~ "\015\012"); 100 } 101 102 /* ------------------------------------------------------------------------------- */ 103 // data 104 private bool _needCRLF = false; 105 private MonoTime startTime; 106 107 /* ------------------------------------------------------------------------------- */ 108 this() { 109 this(false, false); 110 } 111 112 /* ------------------------------------------------------------------------------- */ 113 this(bool sendServerVersion, bool sendXPoweredBy) { 114 _send = (sendServerVersion ? SEND_SERVER : 0) | (sendXPoweredBy ? SEND_XPOWEREDBY : 0); 115 } 116 117 /* ------------------------------------------------------------------------------- */ 118 void reset() { 119 _state = State.START; 120 _endOfContent = EndOfContent.UNKNOWN_CONTENT; 121 _noContentResponse = false; 122 _persistent = false; 123 _contentPrepared = 0; 124 _needCRLF = false; 125 _trailers = null; 126 } 127 128 /* ------------------------------------------------------------ */ 129 State getState() { 130 return _state; 131 } 132 133 /* ------------------------------------------------------------ */ 134 bool isState(State state) { 135 return _state == state; 136 } 137 138 /* ------------------------------------------------------------ */ 139 bool isIdle() { 140 return _state == State.START; 141 } 142 143 /* ------------------------------------------------------------ */ 144 bool isEnd() { 145 return _state == State.END; 146 } 147 148 /* ------------------------------------------------------------ */ 149 bool isCommitted() { 150 return _state >= State.COMMITTED; 151 } 152 153 /* ------------------------------------------------------------ */ 154 bool isChunking() { 155 return _endOfContent == EndOfContent.CHUNKED_CONTENT; 156 } 157 158 /* ------------------------------------------------------------ */ 159 bool isNoContent() { 160 return _noContentResponse; 161 } 162 163 /* ------------------------------------------------------------ */ 164 void setPersistent(bool persistent) { 165 _persistent = persistent; 166 } 167 168 /* ------------------------------------------------------------ */ 169 170 /** 171 * @return true if known to be persistent 172 */ 173 bool isPersistent() { 174 return _persistent; 175 } 176 177 /* ------------------------------------------------------------ */ 178 bool isWritten() { 179 return _contentPrepared > 0; 180 } 181 182 /* ------------------------------------------------------------ */ 183 long getContentPrepared() { 184 return _contentPrepared; 185 } 186 187 /* ------------------------------------------------------------ */ 188 void abort() { 189 _persistent = false; 190 _state = State.END; 191 _endOfContent = EndOfContent.UNKNOWN_CONTENT; 192 } 193 194 /* ------------------------------------------------------------ */ 195 Result generateRequest(HttpRequest metaData, ByteBuffer header, 196 ByteBuffer chunk, ByteBuffer content, bool last) { 197 198 switch (_state) { 199 case State.START: { 200 if (metaData is null) 201 return Result.NEED_INFO; 202 203 if (header is null) 204 return Result.NEED_HEADER; 205 206 // If we have not been told our persistence, set the default 207 // if (_persistent == null) 208 { 209 // HttpVersion v = HttpVersion.HTTP_1_0; 210 _persistent = metaData.getHttpVersion() > HttpVersion.HTTP_1_0; 211 if (!_persistent && HttpMethod.CONNECT.isSame(metaData.getMethod())) 212 _persistent = true; 213 } 214 215 // prepare the header 216 int pos = BufferUtils.flipToFill(header); 217 try { 218 // generate ResponseLine 219 generateRequestLine(metaData, header); 220 221 if (metaData.getHttpVersion() == HttpVersion.HTTP_0_9) 222 throw new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, "HTTP/0.9 not supported"); 223 224 generateHeaders(metaData, header, content, last); 225 enum string continueString = HttpHeaderValue.CONTINUE.asString(); 226 bool expect100 = metaData.getFields().contains(HttpHeader.EXPECT, continueString); 227 228 if (expect100) { 229 _state = State.COMMITTED; 230 } else { 231 // handle the content. 232 int len = BufferUtils.length(content); 233 if (len > 0) { 234 _contentPrepared += len; 235 if (isChunking()) 236 prepareChunk(header, len); 237 } 238 _state = last ? State.COMPLETING : State.COMMITTED; 239 } 240 241 return Result.FLUSH; 242 } catch (BadMessageException e) { 243 throw e; 244 } catch (BufferOverflowException e) { 245 throw new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, "Request header too large", e); 246 } catch (Exception e) { 247 throw new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, cast(string)e.message(), e); 248 } finally { 249 BufferUtils.flipToFlush(header, pos); 250 } 251 } 252 253 case State.COMMITTED: { 254 return committed(chunk, content, last); 255 } 256 257 case State.COMPLETING: { 258 return completing(chunk, content); 259 } 260 261 case State.END: 262 if (BufferUtils.hasContent(content)) { 263 version(HUNT_DEBUG) { 264 tracef("discarding content in COMPLETING"); 265 } 266 BufferUtils.clear(content); 267 } 268 return Result.DONE; 269 270 default: 271 throw new IllegalStateException(""); 272 } 273 } 274 275 private Result committed(ByteBuffer chunk, ByteBuffer content, bool last) { 276 int len = BufferUtils.length(content); 277 278 // handle the content. 279 if (len > 0) { 280 if (isChunking()) { 281 if (chunk is null) 282 return Result.NEED_CHUNK; 283 BufferUtils.clearToFill(chunk); 284 prepareChunk(chunk, len); 285 BufferUtils.flipToFlush(chunk, 0); 286 } 287 _contentPrepared += len; 288 } 289 290 if (last) { 291 _state = State.COMPLETING; 292 return len > 0 ? Result.FLUSH : Result.CONTINUE; 293 } 294 return len > 0 ? Result.FLUSH : Result.DONE; 295 } 296 297 private Result completing(ByteBuffer chunk, ByteBuffer content) { 298 version(HUNT_METRIC) { 299 scope(exit) { 300 Duration timeElapsed = MonoTime.currTime - startTime; 301 warningf("generating completed in: %d microseconds", 302 timeElapsed.total!(TimeUnit.Microsecond)()); 303 } 304 } 305 306 if (BufferUtils.hasContent(content)) { 307 version(HUNT_DEBUG) 308 tracef("discarding content in COMPLETING"); 309 BufferUtils.clear(content); 310 } 311 312 if (isChunking()) { 313 if (_trailers != null) { 314 // Do we need a chunk buffer? 315 if (chunk is null || chunk.capacity() <= CHUNK_SIZE) 316 return Result.NEED_CHUNK_TRAILER; 317 318 HttpFields trailers = _trailers(); 319 320 if (trailers !is null) { 321 // Write the last chunk 322 BufferUtils.clearToFill(chunk); 323 generateTrailers(chunk, trailers); 324 BufferUtils.flipToFlush(chunk, 0); 325 _endOfContent = EndOfContent.UNKNOWN_CONTENT; 326 return Result.FLUSH; 327 } 328 } 329 330 // Do we need a chunk buffer? 331 if (chunk is null) 332 return Result.NEED_CHUNK; 333 334 // Write the last chunk 335 BufferUtils.clearToFill(chunk); 336 prepareChunk(chunk, 0); 337 BufferUtils.flipToFlush(chunk, 0); 338 _endOfContent = EndOfContent.UNKNOWN_CONTENT; 339 return Result.FLUSH; 340 } 341 342 _state = State.END; 343 return _persistent ? Result.DONE : Result.SHUTDOWN_OUT; 344 345 } 346 347 /* ------------------------------------------------------------ */ 348 Result generateResponse(HttpResponse metaData, bool head, ByteBuffer header, 349 ByteBuffer chunk, ByteBuffer content, bool last){ 350 switch (_state) { 351 case State.START: { 352 if (metaData is null) 353 return Result.NEED_INFO; 354 HttpVersion ver = metaData.getHttpVersion(); 355 if (ver == HttpVersion.Null) { 356 throw new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, "No version"); 357 } 358 359 version(HUNT_METRIC) { 360 startTime = MonoTime.currTime; 361 debug info("generating response..."); 362 } 363 switch (ver.getVersion()) { 364 case HttpVersion.HTTP_1_0.getVersion(): 365 _persistent = false; 366 break; 367 368 case HttpVersion.HTTP_1_1.getVersion(): 369 _persistent = true; 370 break; 371 372 default: 373 _persistent = false; 374 _endOfContent = EndOfContent.EOF_CONTENT; 375 if (BufferUtils.hasContent(content)) 376 _contentPrepared += content.remaining(); 377 _state = last ? State.COMPLETING : State.COMMITTED; 378 return Result.FLUSH; 379 } 380 381 // Do we need a response header 382 if (header is null) 383 return Result.NEED_HEADER; 384 385 // prepare the header 386 int pos = BufferUtils.flipToFill(header); 387 try { 388 // generate ResponseLine 389 generateResponseLine(metaData, header); 390 391 // Handle 1xx and no content responses 392 int status = metaData.getStatus(); 393 if (status >= 100 && status < 200) { 394 _noContentResponse = true; 395 396 if (status != HttpStatus.SWITCHING_PROTOCOLS_101) { 397 header.put(HttpTokens.CRLF); 398 _state = State.COMPLETING_1XX; 399 return Result.FLUSH; 400 } 401 } else if (status == HttpStatus.NO_CONTENT_204 || status == HttpStatus.NOT_MODIFIED_304) { 402 _noContentResponse = true; 403 } 404 405 generateHeaders(metaData, header, content, last); 406 407 // handle the content. 408 int len = BufferUtils.length(content); 409 if (len > 0) { 410 _contentPrepared += len; 411 if (isChunking() && !head) 412 prepareChunk(header, len); 413 } 414 _state = last ? State.COMPLETING : State.COMMITTED; 415 } catch (BadMessageException e) { 416 throw e; 417 } catch (BufferOverflowException e) { 418 throw new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, "Request header too large", e); 419 } catch (Exception e) { 420 throw new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, cast(string)e.message(), e); 421 } finally { 422 BufferUtils.flipToFlush(header, pos); 423 } 424 425 version(HUNT_METRIC) { 426 // debug infof("_state: %s", _state); 427 if(_state == State.COMMITTED) { 428 Duration timeElapsed = MonoTime.currTime - startTime; 429 warningf("comitted in: %d microseconds", 430 timeElapsed.total!(TimeUnit.Microsecond)()); 431 } 432 } 433 return Result.FLUSH; 434 } 435 436 case State.COMMITTED: { 437 return committed(chunk, content, last); 438 } 439 440 case State.COMPLETING_1XX: { 441 reset(); 442 return Result.DONE; 443 } 444 445 case State.COMPLETING: { 446 return completing(chunk, content); 447 } 448 449 case State.END: 450 info("444444"); 451 if (BufferUtils.hasContent(content)) { 452 version(HUNT_DEBUG) { 453 tracef("discarding content in COMPLETING"); 454 } 455 BufferUtils.clear(content); 456 } 457 return Result.DONE; 458 459 default: { 460 string msg = format("bad generator state: %s", _state); 461 warning(msg); 462 throw new IllegalStateException(msg); 463 } 464 } 465 } 466 467 /* ------------------------------------------------------------ */ 468 private void prepareChunk(ByteBuffer chunk, int remaining) { 469 // if we need CRLF add this to header 470 if (_needCRLF) 471 BufferUtils.putCRLF(chunk); 472 473 // Add the chunk size to the header 474 if (remaining > 0) { 475 BufferUtils.putHexInt(chunk, remaining); 476 BufferUtils.putCRLF(chunk); 477 _needCRLF = true; 478 } else { 479 chunk.put(LAST_CHUNK); 480 _needCRLF = false; 481 } 482 } 483 484 /* ------------------------------------------------------------ */ 485 private void generateTrailers(ByteBuffer buffer, HttpFields trailer) { 486 // if we need CRLF add this to header 487 if (_needCRLF) 488 BufferUtils.putCRLF(buffer); 489 490 // Add the chunk size to the header 491 buffer.put(ZERO_CHUNK); 492 493 int n = trailer.size(); 494 for (int f = 0; f < n; f++) { 495 HttpField field = trailer.getField(f); 496 string v = field.getValue(); 497 if (v == null || v.length == 0) 498 continue; // rfc7230 does not allow no value 499 500 putTo(field, buffer); 501 } 502 503 BufferUtils.putCRLF(buffer); 504 } 505 506 /* ------------------------------------------------------------ */ 507 private void generateRequestLine(HttpRequest request, ByteBuffer header) { 508 header.put(StringUtils.getBytes(request.getMethod())); 509 header.put(cast(byte) ' '); 510 header.put(StringUtils.getBytes(request.getURIString())); 511 header.put(cast(byte) ' '); 512 header.put(request.getHttpVersion().toBytes()); 513 header.put(HttpTokens.CRLF); 514 } 515 516 /* ------------------------------------------------------------ */ 517 private void generateResponseLine(HttpResponse response, ByteBuffer header) { 518 // Look for prepared response line 519 int status = response.getStatus(); 520 // version(HUNT_HTTP_DEBUG) { 521 // infof("status code: %d", status); 522 // } 523 PreparedResponse preprepared = status < __preprepared.length ? __preprepared[status] : null; 524 string reason = response.getReason(); 525 if (preprepared !is null) { 526 if (reason.empty()) { 527 header.put(preprepared._responseLine); 528 } else { 529 header.put(preprepared._schemeCode); 530 header.put(getReasonBytes(reason)); 531 header.put(HttpTokens.CRLF); 532 } 533 } else { // generate response line 534 header.put(HTTP_1_1_SPACE); 535 header.put(cast(byte) ('0' + status / 100)); 536 header.put(cast(byte) ('0' + (status % 100) / 10)); 537 header.put(cast(byte) ('0' + (status % 10))); 538 header.put(cast(byte) ' '); 539 if (reason.empty()) { 540 header.put(cast(byte) ('0' + status / 100)); 541 header.put(cast(byte) ('0' + (status % 100) / 10)); 542 header.put(cast(byte) ('0' + (status % 10))); 543 } else { 544 header.put(getReasonBytes(reason)); 545 } 546 header.put(HttpTokens.CRLF); 547 } 548 } 549 550 /* ------------------------------------------------------------ */ 551 private byte[] getReasonBytes(string reason) { 552 if (reason.length > 1024) 553 reason = reason.substring(0, 1024); 554 byte[] _bytes = StringUtils.getBytes(reason); 555 556 for (size_t i = _bytes.length; i-- > 0; ) 557 if (_bytes[i] == '\r' || _bytes[i] == '\n') 558 _bytes[i] = '?'; 559 return _bytes; 560 } 561 562 /* ------------------------------------------------------------ */ 563 private void generateHeaders(HttpMetaData metaData, ByteBuffer header, ByteBuffer content, bool last) { 564 HttpRequest request = cast(HttpRequest) metaData; 565 HttpResponse response = cast(HttpResponse) metaData; 566 567 version(HUNT_HTTP_DEBUG) { 568 // tracef("Header fields:\n%s", metaData.getFields().toString()); 569 } 570 571 version(HUNT_HTTP_DEBUG_MORE) { 572 tracef("generateHeaders %s, last=%s, content=%s", metaData.toString(), 573 last, BufferUtils.toDetailString(content)); 574 if(content !is null) 575 tracef("content: %s", cast(string)content.peekRemaining()); 576 } 577 578 // default field values 579 int send = _send; 580 HttpField transfer_encoding = null; 581 bool http11 = metaData.getHttpVersion() == HttpVersion.HTTP_1_1; 582 bool close = false; 583 _trailers = http11 ? metaData.getTrailerSupplier() : null; 584 bool chunked_hint = _trailers != null; 585 bool content_type = false; 586 long content_length = metaData.getContentLength(); 587 bool content_length_field = false; 588 589 // Generate fields 590 HttpFields fields = metaData.getFields(); 591 if (fields !is null) { 592 for (int f = 0; f < fields.size(); f++) { 593 HttpField field = fields.getField(f); 594 string v = field.getValue(); 595 if (v == null || v.length == 0) 596 continue; // rfc7230 does not allow no value 597 598 HttpHeader h = field.getHeader(); 599 if (h == HttpHeader.Null) { 600 putTo(field, header); 601 } 602 else { 603 if(h == HttpHeader.CONTENT_LENGTH) { 604 if (content_length < 0) 605 content_length = field.getLongValue(); 606 else if (content_length != field.getLongValue()) { 607 throw new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, 608 format("Incorrect Content-Length %d!=%d", 609 content_length, field.getLongValue())); 610 } 611 content_length_field = true; 612 } 613 else if(h == HttpHeader.CONTENT_TYPE) { 614 // write the field to the header 615 content_type = true; 616 putTo(field, header); 617 } 618 else if(h == HttpHeader.TRANSFER_ENCODING) { 619 if (http11) { 620 // Don't add yet, treat this only as a hint that there is content 621 // with a preference to chunk if we can 622 transfer_encoding = field; 623 chunked_hint = field.contains(HttpHeaderValue.CHUNKED.asString()); 624 } 625 } 626 else if(h == HttpHeader.CONNECTION) { 627 putTo(field, header); 628 if (field.contains(HttpHeaderValue.CLOSE.asString())) { 629 close = true; 630 _persistent = false; 631 } 632 633 if (!http11 && field.contains(HttpHeaderValue.KEEP_ALIVE.asString())) { 634 _persistent = true; 635 } 636 } 637 else if(h == HttpHeader.SERVER) { 638 send = send & ~SEND_SERVER; 639 putTo(field, header); 640 } 641 else 642 putTo(field, header); 643 } 644 } 645 } 646 647 // Can we work out the content length? 648 if (last && content_length < 0 && _trailers == null) 649 content_length = _contentPrepared + BufferUtils.length(content); 650 651 // Calculate how to end _content and connection, _content length and transfer encoding 652 // settings from http://tools.ietf.org/html/rfc7230#section-3.3.3 653 bool assumed_content_request = false; 654 if(request !is null) { 655 string currentMethold = request.getMethod(); 656 bool* itemPtr = currentMethold in __assumedContentMethods; 657 if(itemPtr !is null ) 658 assumed_content_request = *itemPtr; 659 } 660 661 bool assumed_content = assumed_content_request || content_type || chunked_hint; 662 bool nocontent_request = request !is null && content_length <= 0 && !assumed_content; 663 664 // If the message is known not to have content 665 if (_noContentResponse || nocontent_request) { 666 // We don't need to indicate a body length 667 _endOfContent = EndOfContent.NO_CONTENT; 668 669 // But it is an error if there actually is content 670 if (_contentPrepared > 0 || content_length > 0) { 671 if (_contentPrepared == 0 && last) { 672 // TODO discard content for backward compatibility with 9.3 releases 673 // TODO review if it is still needed in 9.4 or can we just throw. 674 content.clear(); 675 content_length = 0; 676 } else 677 throw new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, "Content for no content response"); 678 } 679 } 680 // Else if we are HTTP/1.1 and the content length is unknown and we are either persistent 681 // or it is a request with content (which cannot EOF) or the app has requested chunking 682 else if (http11 && content_length < 0 && (_persistent || assumed_content_request || chunked_hint)) { 683 // we use chunking 684 _endOfContent = EndOfContent.CHUNKED_CONTENT; 685 686 // try to use user supplied encoding as it may have other values. 687 if (transfer_encoding is null) 688 header.put(TRANSFER_ENCODING_CHUNKED); 689 else if (transfer_encoding.toString().endsWith(HttpHeaderValue.CHUNKED.toString())) { 690 putTo(transfer_encoding, header); 691 transfer_encoding = null; 692 } else if (!chunked_hint) { 693 putTo(new HttpField(HttpHeader.TRANSFER_ENCODING, transfer_encoding.getValue() ~ ",chunked"), header); 694 transfer_encoding = null; 695 } else 696 throw new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, "Bad Transfer-Encoding"); 697 } 698 // Else if we known the content length and are a request or a persistent response, 699 else if (content_length >= 0 && (request !is null || _persistent)) { 700 // Use the content length 701 _endOfContent = EndOfContent.CONTENT_LENGTH; 702 putContentLength(header, content_length); 703 } 704 // Else if we are a response 705 else if (response !is null) { 706 // We must use EOF - even if we were trying to be persistent 707 _endOfContent = EndOfContent.EOF_CONTENT; 708 _persistent = false; 709 if (content_length >= 0 && (content_length > 0 || assumed_content || content_length_field)) 710 putContentLength(header, content_length); 711 712 if (http11 && !close) 713 header.put(CONNECTION_CLOSE); 714 } 715 // Else we must be a request 716 else { 717 // with no way to indicate body length 718 throw new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, "Unknown content length for request"); 719 } 720 721 // version(HUNT_DEBUG) { 722 // trace("End Of Content: ", _endOfContent.to!string()); 723 // } 724 // Add transfer encoding if it is not chunking 725 if (transfer_encoding !is null) { 726 if (chunked_hint) { 727 string v = transfer_encoding.getValue(); 728 int c = cast(int)v.lastIndexOf(','); 729 if (c > 0 && v.lastIndexOf(HttpHeaderValue.CHUNKED.toString(), c) > c) 730 putTo(new HttpField(HttpHeader.TRANSFER_ENCODING, v.substring(0, c).strip()), header); 731 } else { 732 putTo(transfer_encoding, header); 733 } 734 } 735 736 // Send server? 737 int status = response !is null ? response.getStatus() : -1; 738 if (status > 199) 739 header.put(SEND[send]); 740 741 // end the header. 742 header.put(HttpTokens.CRLF); 743 } 744 745 /* ------------------------------------------------------------------------------- */ 746 private static void putContentLength(ByteBuffer header, long contentLength) { 747 if (contentLength == 0) 748 header.put(CONTENT_LENGTH_0); 749 else { 750 header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace()); 751 BufferUtils.putDecLong(header, contentLength); 752 header.put(HttpTokens.CRLF); 753 } 754 } 755 756 /* ------------------------------------------------------------------------------- */ 757 static byte[] getReasonBuffer(int code) { 758 PreparedResponse status = code < __preprepared.length ? __preprepared[code] : null; 759 if (status !is null) 760 return status._reason; 761 return null; 762 } 763 764 /* ------------------------------------------------------------------------------- */ 765 override 766 string toString() { 767 return format("%s@%x{s=%s}", typeof(this).stringof, 768 toHash(), 769 _state); 770 } 771 772 /* ------------------------------------------------------------------------------- */ 773 /* ------------------------------------------------------------------------------- */ 774 /* ------------------------------------------------------------------------------- */ 775 // common _content 776 private enum byte[] ZERO_CHUNK = ['0', '\015', '\012']; 777 private enum byte[] LAST_CHUNK = ['0', '\015', '\012', '\015', '\012']; 778 private __gshared byte[] CONTENT_LENGTH_0; // = StringUtils.getBytes("Content-Length: 0\015\012"); 779 private __gshared byte[] CONNECTION_CLOSE; // = StringUtils.getBytes("Connection: close\015\012"); 780 private __gshared byte[] HTTP_1_1_SPACE; // = StringUtils.getBytes(HttpVersion.HTTP_1_1.toString() ~ " "); 781 private __gshared byte[] TRANSFER_ENCODING_CHUNKED; // = StringUtils.getBytes("Transfer-Encoding: chunked\015\012"); 782 private __gshared byte[][] SEND; 783 784 /* ------------------------------------------------------------------------------- */ 785 /* ------------------------------------------------------------------------------- */ 786 /* ------------------------------------------------------------------------------- */ 787 // Build cache of response lines for status 788 private static class PreparedResponse { 789 byte[] _reason; 790 byte[] _schemeCode; 791 byte[] _responseLine; 792 } 793 794 private __gshared PreparedResponse[] __preprepared; // = new PreparedResponse[HttpStatus.MAX_CODE + 1]; 795 796 shared static this() { 797 CONTENT_LENGTH_0 = StringUtils.getBytes("Content-Length: 0\015\012"); 798 CONNECTION_CLOSE = StringUtils.getBytes("Connection: close\015\012"); 799 HTTP_1_1_SPACE = StringUtils.getBytes(HttpVersion.HTTP_1_1.toString() ~ " "); 800 TRANSFER_ENCODING_CHUNKED = StringUtils.getBytes("Transfer-Encoding: chunked\015\012"); 801 SEND = [ 802 new byte[0], 803 StringUtils.getBytes("Server: hunt-http " ~ Version ~ "\015\012"), 804 StringUtils.getBytes("X-Powered-By: hunt-http " ~ Version ~ "\015\012"), 805 StringUtils.getBytes("Server: hunt-http " ~ Version ~ "\015\012X-Powered-By: hunt-http " ~ Version ~ "\015\012") 806 ]; 807 808 __preprepared = new PreparedResponse[HttpStatus.MAX_CODE + 1]; 809 810 string versionString = HttpVersion.HTTP_1_1.toString(); 811 int versionLength = cast(int)versionString.length; 812 813 for (int i = 0; i < __preprepared.length; i++) { 814 HttpStatus.Code code = HttpStatus.getCode(i); 815 if (code == HttpStatus.Code.Null) 816 continue; 817 string reason = code.getMessage(); 818 byte[] line = new byte[versionLength + 5 + reason.length + 2]; 819 line[0 .. versionLength] = cast(byte[])versionString[0 .. $]; 820 // HttpVersion.HTTP_1_1.toBuffer().get(line, 0, versionLength); 821 822 line[versionLength + 0] = ' '; 823 line[versionLength + 1] = cast(byte) ('0' + i / 100); 824 line[versionLength + 2] = cast(byte)('0' + (i % 100) / 10); 825 line[versionLength + 3] = cast(byte)('0' + (i % 10)); 826 line[versionLength + 4] = ' '; 827 for (int j = 0; j < reason.length; j++) 828 line[versionLength + 5 + j] = cast(byte)reason.charAt(j); 829 line[versionLength + 5 + reason.length] = HttpTokens.CARRIAGE_RETURN; 830 line[versionLength + 6 + reason.length] = HttpTokens.LINE_FEED; 831 832 __preprepared[i] = new PreparedResponse(); 833 __preprepared[i]._schemeCode = line[0 .. versionLength + 5].dup; 834 __preprepared[i]._reason = line[versionLength + 5 .. line.length - 2]; 835 __preprepared[i]._responseLine = line; 836 } 837 } 838 839 private static void putSanitisedName(string s, ByteBuffer buffer) { 840 int l = cast(int)s.length; 841 for (int i = 0; i < l; i++) { 842 char c = s[i]; 843 844 if (c < 0 || c > 0xff || c == '\r' || c == '\n' || c == ':') 845 buffer.put(cast(byte) '?'); 846 else 847 buffer.put(cast(byte) (0xff & c)); 848 } 849 } 850 851 private static void putSanitisedValue(string s, ByteBuffer buffer) { 852 int l = cast(int)s.length; 853 for (int i = 0; i < l; i++) { 854 char c = s[i]; 855 856 if (c < 0 || c > 0xff || c == '\r' || c == '\n') 857 buffer.put(cast(byte) ' '); 858 else 859 buffer.put(cast(byte) (0xff & c)); 860 } 861 } 862 863 static void putTo(HttpField field, ByteBuffer bufferInFillMode) { 864 // version(HUNT_HTTP_DEBUG) tracef(field.toString()); 865 PreEncodedHttpField pfield = cast(PreEncodedHttpField) field; 866 if (pfield !is null) { 867 pfield.putTo(bufferInFillMode, HttpVersion.HTTP_1_0); 868 } else { 869 HttpHeader header = field.getHeader(); 870 if (header != HttpHeader.Null) { 871 bufferInFillMode.put(header.getBytesColonSpace()); 872 putSanitisedValue(field.getValue(), bufferInFillMode); 873 } else { 874 putSanitisedName(field.getName(), bufferInFillMode); 875 bufferInFillMode.put(__colon_space); 876 putSanitisedValue(field.getValue(), bufferInFillMode); 877 } 878 879 BufferUtils.putCRLF(bufferInFillMode); 880 } 881 } 882 883 static void putTo(HttpFields fields, ByteBuffer bufferInFillMode) { 884 foreach (HttpField field ; fields) { 885 if (field !is null) 886 putTo(field, bufferInFillMode); 887 } 888 BufferUtils.putCRLF(bufferInFillMode); 889 } 890 }