1 /* 2 * Collie - An asynchronous event-driven network framework using Dlang development 3 * 4 * Copyright (C) 2015-2017 Shanghai Putao Technology Co., Ltd 5 * 6 * Developer: putao's Dlang team 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 module collie.codec.http.httpmessage; 12 13 import collie.codec.http.headers; 14 import collie.codec.http.exception; 15 16 import std.typecons; 17 import std.typetuple; 18 import std.socket; 19 import std.variant; 20 import std.conv; 21 import std.exception; 22 import std..string; 23 public 24 25 final class HTTPMessage 26 { 27 this() 28 { 29 _version[0] = 1; 30 _version[1] = 1; 31 } 32 33 /* Setter and getter for the SPDY priority value (0 - 7). When serialized 34 * to SPDY/2, Codecs will collpase 0,1 -> 0, 2,3 -> 1, etc. 35 * 36 * Negative values of pri are interpreted much like negative array 37 * indexes in python, so -1 will be the largest numerical priority 38 * value for this SPDY version (i.e. 3 for SPDY/2 or 7 for SPDY/3), 39 * -2 the second largest (i.e. 2 for SPDY/2 or 6 for SPDY/3). 40 */ 41 enum byte kMaxPriority = 7; 42 43 // static byte normalizePriority(byte pri) { 44 // if (pri > kMaxPriority || pri < -kMaxPriority) { 45 // // outside [-7, 7] => highest priority 46 // return kMaxPriority; 47 // } else if (pri < 0) { 48 // return pri + kMaxPriority + 1; 49 // } 50 // return pri; 51 // } 52 53 /** 54 * Is this a chunked message? (fpreq, fpresp) 55 */ 56 @property void chunked(bool chunked) { _chunked = chunked; } 57 @property bool chunked() const { return _chunked; } 58 59 /** 60 * Is this an upgraded message? (fpreq, fpresp) 61 */ 62 @property void upgraded(bool upgraded) { _upgraded = upgraded; } 63 @property bool upgraded() const { return _upgraded; } 64 65 /** 66 * Set/Get client address 67 */ 68 @property void clientAddress(Address addr) { 69 request()._clientAddress = addr; 70 request()._clientIP = addr.toAddrString(); 71 request()._clientPort = addr.toPortString; 72 } 73 74 @property Address clientAddress() { 75 return request()._clientAddress; 76 } 77 78 string getClientIP() { 79 return request()._clientIP; 80 } 81 82 string getClientPort() { 83 return request()._clientPort; 84 } 85 86 /** 87 * Set/Get destination (vip) address 88 */ 89 @property void dstAddress(Address addr) { 90 _dstAddress = addr; 91 _dstIP = addr.toAddrString; 92 _dstPort = addr.toPortString; 93 } 94 95 @property Address dstAddress() { 96 return _dstAddress; 97 } 98 99 string getDstIP() { 100 return _dstIP; 101 } 102 103 string getDstPort() { 104 return _dstPort; 105 } 106 107 /** 108 * Set/Get the local IP address 109 */ 110 @property void localIp(string ip) { 111 _localIP = ip; 112 } 113 @property string localIp() { 114 return _localIP; 115 } 116 117 @property void method(HTTPMethod method) 118 { 119 request()._method = method; 120 } 121 122 @property HTTPMethod method() 123 { 124 return request()._method; 125 } 126 //void setMethod(folly::StringPiece method); 127 128 string methodString(){ 129 return method_strings[request()._method]; 130 } 131 132 void setHTTPVersion(ubyte maj, ubyte min) 133 { 134 _version[0] = maj; 135 _version[1] = min; 136 } 137 138 auto getHTTPVersion() 139 { 140 Tuple!(ubyte, "maj", ubyte, "min") tv; 141 tv.maj = _version[0]; 142 tv.min = _version[1]; 143 return tv; 144 } 145 146 @property void url(string url){ 147 auto idx = url.indexOf('?'); 148 if (idx > 0){ 149 request()._path = url[0..idx]; 150 request()._query = url[idx+1..$]; 151 } else { 152 request()._path = url; 153 } 154 request()._url = url; 155 } 156 157 @property string url(){return request()._url;} 158 159 160 @property wantsKeepAlive(){return _wantsKeepalive;} 161 @property wantsKeepAlive(bool klive){_wantsKeepalive = klive;} 162 /** 163 * Access the path component (fpreq) 164 */ 165 string getPath() 166 { 167 return request()._path; 168 } 169 170 /** 171 * Access the query component (fpreq) 172 */ 173 string getQueryString() 174 { 175 return request()._query; 176 } 177 178 @property void statusMessage(string msg) { 179 response()._statusMsg = msg; 180 } 181 @property string statusMessage() 182 { 183 return response()._statusMsg; 184 } 185 186 /** 187 * Access the status code (fpres) 188 */ 189 @property void statusCode(ushort status) 190 { 191 response()._status = status; 192 } 193 194 @property ushort statusCode() 195 { 196 return response()._status; 197 } 198 199 @property string host() 200 { 201 return _headers.getSingleOrEmpty(HTTPHeaderCode.HOST); 202 } 203 204 /** 205 * Access the headers (fpreq, fpres) 206 */ 207 ref HTTPHeaders getHeaders(){ return _headers; } 208 209 /** 210 * Decrements Max-Forwards header, when present on OPTIONS or logDebug methods. 211 * 212 * Returns HTTP status code. 213 */ 214 int processMaxForwards() 215 { 216 auto m = method(); 217 if (m == HTTPMethod.HTTP_logDebug || m == HTTPMethod.HTTP_OPTIONS) { 218 string value = _headers.getSingleOrEmpty(HTTPHeaderCode.MAX_FORWARDS); 219 if (value.length > 0) { 220 long max_forwards = -1; 221 222 collectException(to!long(value),max_forwards); 223 224 if (max_forwards < 0) { 225 return 400; 226 } else if (max_forwards == 0) { 227 return 501; 228 } else { 229 _headers.set(HTTPHeaderCode.MAX_FORWARDS,to!string(max_forwards - 1)); 230 } 231 } 232 } 233 return 0; 234 } 235 236 /** 237 * Returns true if the version of this message is HTTP/1.0 238 */ 239 bool isHTTP1_0() const 240 { 241 return _version[0] == 1 && _version[1] == 0; 242 } 243 244 /** 245 * Returns true if the version of this message is HTTP/1.1 246 */ 247 bool isHTTP1_1() const 248 { 249 return _version[0] == 1 && _version[1] == 1; 250 } 251 252 /** 253 * Returns true if this is a 1xx response. 254 */ 255 bool is1xxResponse(){ return (statusCode() / 100) == 1; } 256 257 /** 258 * Fill in the fields for a response message header that the server will 259 * send directly to the client. 260 * 261 * @param version HTTP version (major, minor) 262 * @param statusCode HTTP status code to respond with 263 * @param msg textual message to embed in "message" status field 264 * @param contentLength the length of the data to be written out through 265 * this message 266 */ 267 void constructDirectResponse(ubyte maj,ubyte min,const int statucode,string statusMsg,int contentLength = 0) 268 { 269 statusCode(cast(ushort)statucode); 270 statusMessage(statusMsg); 271 constructDirectResponse(maj,min, contentLength); 272 } 273 274 /** 275 * Fill in the fields for a response message header that the server will 276 * send directly to the client. This function assumes the status code and 277 * status message have already been set on this HTTPMessage object 278 * 279 * @param version HTTP version (major, minor) 280 * @param contentLength the length of the data to be written out through 281 * this message 282 */ 283 void constructDirectResponse(ubyte maj,ubyte min,int contentLength = 0) 284 { 285 setHTTPVersion(maj,min); 286 _headers.set(HTTPHeaderCode.CONTENT_LENGTH,to!string(contentLength)); 287 if(!_headers.exists(HTTPHeaderCode.CONTENT_TYPE)){ 288 _headers.add(HTTPHeaderCode.CONTENT_TYPE, "text/plain"); 289 } 290 chunked(false); 291 upgraded(false); 292 } 293 294 /** 295 * Check if query parameter with the specified name exists. 296 */ 297 bool hasQueryParam(string name) 298 { 299 parseQueryParams(); 300 return _queryParams.get(name,string.init) != string.init; 301 } 302 /** 303 * Get the query parameter with the specified name. 304 * 305 * Returns a reference to the query parameter value, or 306 * proxygen::empty_string if there is no parameter with the 307 * specified name. The returned value is only valid as long as this 308 * HTTPMessage object. 309 */ 310 string getQueryParam(string name) 311 { 312 parseQueryParams(); 313 return _queryParams.get(name,string.init); 314 } 315 /** 316 * Get the query parameter with the specified name after percent decoding. 317 * 318 * Returns empty string if parameter is missing or folly::uriUnescape 319 * query param 320 */ 321 string getDecodedQueryParam(string name) 322 { 323 import std.uri; 324 parseQueryParams(); 325 string v = _queryParams.get(name,string.init); 326 if(v == string.init) 327 return v; 328 return decodeComponent(v); 329 } 330 331 /** 332 * Get the query parameter with the specified name after percent decoding. 333 * 334 * Returns empty string if parameter is missing or folly::uriUnescape 335 * query param 336 */ 337 string[string] queryParam(){parseQueryParams();return _queryParams;} 338 339 /** 340 * Set the query string to the specified value, and recreate the url_. 341 * 342 */ 343 void setQueryString(string query) 344 { 345 unparseQueryParams(); 346 request._query = query; 347 } 348 /** 349 * Remove the query parameter with the specified name. 350 * 351 */ 352 void removeQueryParam(string name) 353 { 354 parseQueryParams(); 355 _queryParams.remove(name); 356 } 357 358 /** 359 * Sets the query parameter with the specified name to the specified value. 360 * 361 * Returns true if the query parameter was successfully set. 362 */ 363 void setQueryParam(string name, string value) 364 { 365 parseQueryParams(); 366 _queryParams[name] = value; 367 } 368 369 370 /** 371 * @returns true if this HTTPMessage represents an HTTP request 372 */ 373 bool isRequest() const { 374 return _isRequest == MegType.Request_; 375 } 376 377 /** 378 * @returns true if this HTTPMessage represents an HTTP response 379 */ 380 bool isResponse() const { 381 return _isRequest == MegType.Response_; 382 } 383 384 static string statusText(int code) 385 { 386 switch (code) 387 { 388 case 100: 389 return "Continue"; 390 case 101: 391 return "Switching Protocols"; 392 case 102: 393 return "Processing"; // RFC2518 394 case 200: 395 return "OK"; 396 case 201: 397 return "Created"; 398 case 202: 399 return "Accepted"; 400 case 203: 401 return "Non-Authoritative logInformation"; 402 case 204: 403 return "No Content"; 404 case 205: 405 return "Reset Content"; 406 case 206: 407 return "Partial Content"; 408 case 207: 409 return "Multi-Status"; // RFC4918 410 case 208: 411 return "Already Reported"; // RFC5842 412 case 226: 413 return "IM Used"; // RFC3229 414 case 300: 415 return "Multiple Choices"; 416 case 301: 417 return "Moved Permanently"; 418 case 302: 419 return "Found"; 420 case 303: 421 return "See Other"; 422 case 304: 423 return "Not Modified"; 424 case 305: 425 return "Use Proxy"; 426 case 306: 427 return "Reserved"; 428 case 307: 429 return "Temporary Redirect"; 430 case 308: 431 return "Permanent Redirect"; // RFC7238 432 case 400: 433 return "Bad Request"; 434 case 401: 435 return "Unauthorized"; 436 case 402: 437 return "Payment Required"; 438 case 403: 439 return "Forbidden"; 440 case 404: 441 return "Not Found"; 442 case 405: 443 return "Method Not Allowed"; 444 case 406: 445 return "Not Acceptable"; 446 case 407: 447 return "Proxy Authentication Required"; 448 case 408: 449 return "Request Timeout"; 450 case 409: 451 return "Conflict"; 452 case 410: 453 return "Gone"; 454 case 411: 455 return "Length Required"; 456 case 412: 457 return "Precondition Failed"; 458 case 413: 459 return "Request Entity Too Large"; 460 case 414: 461 return "Request-URI Too Long"; 462 case 415: 463 return "Unsupported Media Type"; 464 case 416: 465 return "Requested Range Not Satisfiable"; 466 case 417: 467 return "Expectation Failed"; 468 case 418: 469 return "I\"m a teapot"; // RFC2324 470 case 422: 471 return "Unprocessable Entity"; // RFC4918 472 case 423: 473 return "Locked"; // RFC4918 474 case 424: 475 return "Failed Dependency"; // RFC4918 476 case 425: 477 return "Reserved for WebDAV advanced collections expired proposal"; // RFC2817 478 case 426: 479 return "Upgrade Required"; // RFC2817 480 case 428: 481 return "Precondition Required"; // RFC6585 482 case 429: 483 return "Too Many Requests"; // RFC6585 484 case 431: 485 return "Request Header Fields Too Large"; // RFC6585 486 case 500: 487 return "Internal Server Error"; 488 case 501: 489 return "Not Implemented"; 490 case 502: 491 return "Bad Gateway"; 492 case 503: 493 return "Service Unavailable"; 494 case 504: 495 return "Gateway Timeout"; 496 case 505: 497 return "HTTP Version Not Supported"; 498 case 506: 499 return "Variant Also Negotiates (Experimental)"; // RFC2295 500 case 507: 501 return "Insufficient Storage"; // RFC4918 502 case 508: 503 return "Loop Detected"; // RFC5842 504 case 510: 505 return "Not Extended"; // RFC2774 506 case 511: 507 return "Network Authentication Required"; // RFC6585 508 default: 509 return " "; 510 } 511 } 512 513 protected: 514 /** The 12 standard fields for HTTP messages. Use accessors. 515 * An HTTPMessage is either a Request or Response. 516 * Once an accessor for either is used, that fixes the type of HTTPMessage. 517 * If an access is then used for the other type, a DCHECK will fail. 518 */ 519 struct Request 520 { 521 Address _clientAddress; 522 string _clientIP; 523 string _clientPort; 524 HTTPMethod _method = HTTPMethod.HTTP_INVAILD; 525 string _path; 526 string _query; 527 string _url; 528 529 //ushort _pushStatus; 530 //string _pushStatusStr; 531 } 532 533 struct Response 534 { 535 ushort _status = 200; 536 string _statusStr; 537 string _statusMsg; 538 } 539 540 ref Request request() 541 { 542 if(_isRequest == MegType.Null_) { 543 _isRequest = MegType.Request_; 544 _resreq.req = Request(); 545 } else if(_isRequest == MegType.Response_){ 546 throw new HTTPMessageTypeException("the message type is Response not Request"); 547 } 548 return _resreq.req; 549 } 550 551 ref Response response() 552 { 553 if(_isRequest == MegType.Null_) { 554 _isRequest = MegType.Response_; 555 _resreq.res = Response(); 556 } else if(_isRequest == MegType.Request_){ 557 throw new HTTPMessageTypeException("the message type is Request not Response"); 558 } 559 560 return _resreq.res; 561 } 562 563 protected: 564 //void parseCookies(){} 565 566 void parseQueryParams(){ 567 import collie.utils..string; 568 if(_parsedQueryParams) return; 569 _parsedQueryParams = true; 570 string query = getQueryString(); 571 if(query.length == 0) return; 572 splitNameValue(query, '&', '=',(string name,string value){ 573 name = strip(name); 574 value = strip(value); 575 _queryParams[name] = value; 576 return true; 577 }); 578 } 579 void unparseQueryParams(){ 580 _queryParams.clear(); 581 _parsedQueryParams = false; 582 } 583 584 union Req_Res 585 { 586 Request req; 587 Response res; 588 } 589 590 enum MegType : ubyte{ 591 Null_, 592 Request_, 593 Response_, 594 } 595 596 private: 597 Address _dstAddress; 598 string _dstIP; 599 string _dstPort; 600 601 string _localIP; 602 string _versionStr; 603 MegType _isRequest = MegType.Null_; 604 Req_Res _resreq; 605 private: 606 ubyte[2] _version; 607 HTTPHeaders _headers; 608 // string[string] _cookies; 609 string[string] _queryParams; 610 611 private: 612 bool _parsedCookies = false; 613 bool _parsedQueryParams = false; 614 bool _chunked = false; 615 bool _upgraded = false; 616 bool _wantsKeepalive = true; 617 } 618