1 module hunt.http.server.HttpServerRequest; 2 3 import hunt.http.server.GlobalSettings; 4 import hunt.http.server.Http1ServerConnection; 5 import hunt.http.server.HttpRequestOptions; 6 import hunt.http.server.HttpServerOptions; 7 import hunt.http.server.HttpServerResponse; 8 import hunt.http.server.ServerHttpHandler; 9 10 import hunt.http.codec.http.decode.HttpParser; 11 import hunt.http.codec.http.decode.MultipartFormParser; 12 import hunt.http.codec.http.model; 13 14 import hunt.http.Cookie; 15 import hunt.http.HttpFields; 16 import hunt.http.HttpHeader; 17 import hunt.http.HttpMethod; 18 import hunt.http.HttpRequest; 19 import hunt.http.HttpVersion; 20 import hunt.http.MultipartForm; 21 22 import hunt.collection; 23 import hunt.concurrency.atomic; 24 import hunt.Exceptions; 25 import hunt.Functions; 26 import hunt.io.BufferUtils; 27 import hunt.stream.PipedStream; 28 import hunt.stream.ByteArrayInputStream; 29 import hunt.stream.Common; 30 import hunt.logging.ConsoleLogger; 31 import hunt.net.util.HttpURI; 32 import hunt.net.util.UrlEncoded; 33 import hunt.text.Common; 34 import hunt.util.Common; 35 import hunt.util.MimeTypeUtils; 36 37 import std.algorithm; 38 import std.array; 39 import std.conv; 40 import std.container.array; 41 import std.json; 42 import std.string : icmp; 43 import std.range; 44 45 46 /** 47 * 48 */ 49 class HttpServerRequest : HttpRequest { 50 51 private string _stringBody; 52 private string _mimeType; 53 private HttpServerOptions _options; 54 55 private Cookie[] _cookies; 56 private PipedStream _pipedStream; 57 private MultipartFormParser _multipartFormParser; 58 private UrlEncoded _urlEncodedMap; 59 60 private Action1!HttpServerRequest _messageCompleteHandler; 61 // private Action1!ByteBuffer _contentHandler; 62 private Action1!HttpServerRequest _contentCompleteHandler; 63 64 this(string method, string uri, HttpVersion ver, HttpServerOptions options) { 65 enum string connect = HttpMethod.CONNECT.asString(); 66 _options = options; 67 68 super(method, new HttpURI(icmp(method, connect) == 0 ? "http://" ~ uri : uri), 69 ver, new HttpFields()); 70 // _originalPath = uri; 71 _originalPath = getURI().getPath(); 72 // super(method, new HttpURI(HttpMethod.fromString(method) == HttpMethod.CONNECT ? "http://" ~ uri : uri), ver, new HttpFields()); 73 } 74 75 @property string host() { 76 return header(HttpHeader.HOST); 77 } 78 79 string getParameter(string name) { 80 decodeUrl(); 81 return _urlEncodedMap.getString(name); 82 } 83 84 List!(string) getParameterValues(string name) { 85 decodeUrl(); 86 return _urlEncodedMap.getValues(name); 87 } 88 89 Map!(string, List!(string)) getParameterMap() { 90 decodeUrl(); 91 return _urlEncodedMap; 92 } 93 94 string getMimeType() { 95 if(_mimeType is null) { 96 _mimeType = MimeTypeUtils.getContentTypeMIMEType(getContentType()); 97 } 98 99 return _mimeType; 100 } 101 102 private void decodeUrl() { 103 if(_urlEncodedMap !is null) 104 return; 105 106 _urlEncodedMap = new UrlEncoded(); 107 108 string queryString = getURI().getQuery(); 109 if (!queryString.empty()) { 110 _urlEncodedMap.decode(queryString); 111 } 112 113 if ("application/x-www-form-urlencoded".equalsIgnoreCase(getMimeType())) { 114 string bodyString = getStringBody(_options.requestOptions.getCharset()); 115 _urlEncodedMap.decode(bodyString); 116 } 117 } 118 119 // void setPipedStream(PipedStream stream) { 120 // this._pipedStream = stream; 121 // } 122 123 // void closeOutputStream() { 124 // if(this._pipedStream !is null) 125 // this._pipedStream.getOutputStream().close(); 126 // } 127 128 private OutputStream getOutputStream() { 129 return this._pipedStream.getOutputStream(); 130 } 131 132 string getStringBody(string charset) { 133 if (_stringBody !is null) { 134 return _stringBody; 135 } else { 136 if (_pipedStream is null) // no content 137 return null; 138 139 InputStream inputStream = this._pipedStream.getInputStream(); 140 scope(exit) 141 inputStream.close(); 142 143 try { 144 // inputStream.position(0); // The InputStream may be read, so reset it before reading it again. 145 int size = inputStream.available(); 146 version(HUNT_HTTP_DEBUG) tracef("available: %d", size); 147 // TODO: Tasks pending completion -@zhangxueping at 2019-10-23T15:40:56+08:00 148 // encoding convertion: to UTF-8 149 150 // skip the read for better performance 151 byte[] buffer; 152 ByteArrayInputStream arrayStream = cast(ByteArrayInputStream)inputStream; 153 if(arrayStream is null) { 154 buffer = new byte[size]; 155 int number = inputStream.read(buffer); 156 if(number == -1) { 157 version(HUNT_DEBUG) warning("no data read"); 158 } 159 } else { 160 buffer = arrayStream.getRawBuffer(); 161 } 162 _stringBody = cast(string)buffer; 163 return _stringBody; 164 } catch (IOException e) { 165 version(HUNT_DEBUG) warning("get string body exception: ", e.msg); 166 version(HUNT_HTTP_DEBUG) warning(e); 167 return null; 168 } 169 } 170 } 171 172 string getStringBody() { 173 return getStringBody("UTF-8"); 174 } 175 176 package(hunt.http) void onHeaderComplete() { 177 // _options = options; 178 auto options = _options.requestOptions; 179 if (isChunked()) { 180 this._pipedStream = new ByteArrayPipedStream(4 * 1024); 181 } else { 182 long contentLength = getContentLength(); 183 184 if (contentLength > options.getBodyBufferThreshold()) { 185 this._pipedStream = new FilePipedStream(options.getTempFilePath()); 186 } else if(contentLength > 0) { 187 this._pipedStream = new ByteArrayPipedStream(cast(int) contentLength); 188 } 189 } 190 } 191 192 private shared long chunkedEncodingContentLength; 193 194 package(hunt.http) void onContent(ByteBuffer buffer) { 195 version(HUNT_HTTP_DEBUG) { 196 tracef("received content size -> %s bytes", buffer.remaining()); 197 } 198 199 try { 200 OutputStream outStream = getOutputStream(); 201 if (isChunked()) { 202 auto options = _options.requestOptions; 203 long length = AtomicHelper.increment(chunkedEncodingContentLength, buffer.remaining()); 204 ByteArrayPipedStream bytesStream = cast(ByteArrayPipedStream)_pipedStream; 205 206 // switch PipedStream from ByteArrayPipedStream to FilePipedStream. 207 if (length > options.getBodyBufferThreshold() && bytesStream !is null) { 208 // FIXME: Needing refactor or cleanup -@zhangxueping at 2019-10-17T23:05:10+08:00 209 // better performance. 210 211 version(HUNT_HTTP_DEBUG) { 212 warningf("dump to temp file, ContentLength:%d, BufferThreshold: %d, file: %s", 213 length, 214 options.getBodyBufferThreshold(), 215 options.getTempFilePath()); 216 } 217 218 // chunked encoding content dump to temp file 219 _pipedStream.getOutputStream().close(); 220 FilePipedStream filePipedStream = new FilePipedStream(options.getTempFilePath()); 221 byte[] bufferedBytes = bytesStream.getOutputStream().toByteArray(false); 222 OutputStream fileOutStream = filePipedStream.getOutputStream(); 223 fileOutStream.write(bufferedBytes); 224 fileOutStream.write(BufferUtils.toArray(buffer, false)); 225 _pipedStream = filePipedStream; 226 } else { 227 outStream.write(BufferUtils.toArray(buffer, false)); 228 } 229 } else { 230 outStream.write(BufferUtils.toArray(buffer, false)); 231 } 232 } catch (IOException e) { 233 version(HUNT_DEBUG) warning("http server receives http body exception: ", e.msg); 234 version(HUNT_HTTP_DEBUG) warning(e); 235 } 236 } 237 238 package(hunt.http) void onContentComplete() { 239 if(_pipedStream is null || (getContentLength() <=0 && !isChunked()) ) 240 return; 241 242 try { 243 string mimeType = getMimeType(); 244 version(HUNT_HTTP_DEBUG) trace("mimeType: ", mimeType); 245 getOutputStream().close(); 246 247 if ("multipart/form-data".equalsIgnoreCase(mimeType)) { 248 _multipartFormParser = new MultipartFormParser( 249 _pipedStream.getInputStream(), 250 getContentType(), 251 GlobalSettings.getMultipartOptions(_options.requestOptions), 252 _options.requestOptions.getTempFilePath()); 253 } else if("application/x-www-form-urlencoded".equalsIgnoreCase(mimeType)) { 254 _isXFormUrlencoded = true; 255 } 256 257 if(_contentCompleteHandler !is null) { 258 _contentCompleteHandler(this); 259 } 260 } catch (IOException e) { 261 version(HUNT_DEBUG) warning("http server ends receiving data exception: ", e.msg); 262 version(HUNT_HTTP_DEBUG) warning(e); 263 } 264 } 265 266 package(hunt.http) void onMessageComplete() { 267 if(_messageCompleteHandler !is null) 268 _messageCompleteHandler(this); 269 } 270 271 // HttpRequest onContent(Action1!ByteBuffer handler) { 272 // _contentHandler = handler; 273 // return this; 274 // } 275 276 HttpRequest onContentComplete(Action1!HttpServerRequest handler) { 277 _contentCompleteHandler = handler; 278 return this; 279 } 280 281 HttpServerRequest onMessageComplete(Action1!HttpServerRequest handler) { 282 _messageCompleteHandler = handler; 283 return this; 284 } 285 286 287 HttpServerRequest onBadMessage(Action1!HttpServerRequest handler) { 288 _messageCompleteHandler = handler; 289 return this; 290 } 291 292 293 Cookie[] getCookies() { 294 if (_cookies is null) { 295 296 Array!(Cookie) list; 297 foreach (string v; getFields().getValuesList(HttpHeader.COOKIE)) { 298 if (v.empty) 299 continue; 300 foreach (Cookie c; parseCookie(v)) 301 list.insertBack(c); 302 } 303 _cookies = list.array(); 304 // _cookies = getFields().getValuesList(HttpHeader.COOKIE) 305 // .map!(parseSetCookie).array; 306 } 307 308 return _cookies; 309 } 310 311 bool isMultipartForm() { 312 return _multipartFormParser !is null; 313 } 314 315 bool isXFormUrlencoded() { 316 return _isXFormUrlencoded; 317 } 318 319 Part[] getParts() { 320 if (_multipartFormParser is null) { 321 throw new RuntimeException("The reqest is not a multipart form."); 322 // return null; 323 } 324 325 // try { 326 return _multipartFormParser.getParts(); 327 // } catch (IOException e) { 328 // version(HUNT_DEBUG) warning("get multi part exception: ", e.msg); 329 // version(HUNT_HTTP_DEBUG) warning(e); 330 // return null; 331 // } 332 } 333 334 Part getPart(string name) { 335 if (_multipartFormParser is null) { 336 throw new RuntimeException("The reqest is not a multipart form."); 337 // return null; 338 } 339 340 // try { 341 return _multipartFormParser.getPart(name); 342 // } catch (IOException e) { 343 // version(HUNT_DEBUG) warning("get multi part exception: ", e.msg); 344 // version(HUNT_HTTP_DEBUG) warning(e); 345 // return null; 346 // } 347 } 348 349 350 // dfmtoff Some Helpers 351 352 import std.string; 353 354 @property string originalPath() { 355 return _originalPath; 356 } 357 private string _originalPath; 358 359 @property string path() { 360 return getURI().getPath(); 361 } 362 363 @property void path(string value) { 364 getURI().setPath(value); 365 } 366 367 @property string decodedPath() { 368 return getURI().getDecodedPath(); 369 } 370 371 string opDispatch(string key)() { 372 version(HUNT_HTTP_DEBUG) { 373 tracef("Get the query parameter: %s", key); 374 } 375 return query(key); 376 } 377 378 /** 379 * Retrieve a query string item from the request. 380 * 381 * @param string key 382 * @param string|array|null default 383 * @return string|array 384 */ 385 string query(string key, string defaults = null) { 386 return queries().get(key, defaults); 387 } 388 389 /// get a query 390 T get(T = string)(string key, T v = T.init) { 391 version(HUNT_HTTP_DEBUG) { 392 tracef("key: %s, value: %s", key, v); 393 } 394 395 auto tmp = queries(); 396 if (tmp is null) { 397 return v; 398 } 399 400 static if(is(T == string)) { 401 if(v is null) v = ""; 402 } 403 404 auto _v = tmp.get(key, ""); 405 version(HUNT_HTTP_DEBUG) { 406 tracef("key: %s, value: %s, target: %s, default: %s", key, _v, T.stringof, v); 407 } 408 409 if (_v.length) { 410 try { 411 return to!T(_v); 412 } catch(Exception ex) { 413 string msg = format("Failed converting from <%s> to %s", _v, T.stringof); 414 // warning(msg); 415 throw new Exception(msg); 416 } 417 } 418 419 return v; 420 } 421 422 /** 423 * Retrieve a request payload item from the request. 424 * 425 * @param string key 426 * @param string|array|null default 427 * 428 * @return string|array 429 */ 430 T post(T = string)(string key, T v = T.init) { 431 string[][string] form = xFormData(); 432 if (form is null) 433 return v; 434 if(key in form) { 435 string[] _v = form[key]; 436 if (_v.length > 0) { 437 static if(is(T == string)) 438 v = _v[0]; 439 else { 440 v = to!T(_v[0]); 441 } 442 } 443 } 444 445 return v; 446 } 447 448 T[] posts(T = string)(string key, T[] v = null) { 449 string[][string] form = xFormData(); 450 if (form is null) 451 return v; 452 453 if(key in form) { 454 string[] _v = form[key]; 455 if (_v.length > 0) { 456 static if(is(T == string)) 457 v = _v[]; 458 else { 459 v = new T[_v.length]; 460 for(size i =0; i<v.length; i++) { 461 v[i] = to!T(_v[i]); 462 } 463 } 464 } 465 } 466 467 return v; 468 } 469 470 // get queries 471 @property ref string[string] queries() { 472 if (!_isQueryParamsSet) { 473 MultiMap!string map = new MultiMap!string(); 474 getURI().decodeQueryTo(map); 475 foreach (string key, List!(string) values; map) { 476 version(HUNT_DEBUG) { 477 infof("query parameter: key=%s, values=%s", key, values[0]); 478 } 479 if(values is null || values.size()<1) { 480 _queryParams[key] = ""; 481 } else { 482 _queryParams[key] = values[0]; 483 } 484 } 485 _isQueryParamsSet = true; 486 } 487 return _queryParams; 488 } 489 490 void putQueryParameter(string key, string value) { 491 version(HUNT_HTTP_DEBUG) infof("query parameter: key=%s, values=%s", key, value); 492 _queryParams[key] = value; 493 } 494 495 /** 496 * Replace the input for the current request. 497 * 498 * @param array input 499 * @return Request 500 */ 501 void replace(string[string] input) { 502 if (isContained(this.getMethod(), ["GET", "HEAD"])) 503 _queryParams = input; 504 else { 505 foreach(string k, string v; input) { 506 _xFormData[k] ~= v; 507 } 508 } 509 } 510 511 private static bool isContained(string source, string[] keys) { 512 foreach (string k; keys) { 513 if (canFind(source, k)) 514 return true; 515 } 516 return false; 517 } 518 519 T bindForm(T)() if(is(T == class) || is(T == struct)) { 520 521 if(getMethod() != "POST") 522 return T.init; 523 import hunt.serialization.JsonSerializer; 524 // import hunt.util.Serialize; 525 526 JSONValue jv; 527 string[][string] forms = xFormData(); 528 if(forms is null) { 529 static if(is(T == class)) { 530 return new T(); 531 } else { 532 return T.init; 533 } 534 } 535 536 foreach(string k, string[] values; forms) { 537 if(values.length > 1) { 538 jv[k] = JSONValue(values); 539 } else if(values.length == 1) { 540 jv[k] = JSONValue(values[0]); 541 } else { 542 warningf("null value for %s in form data: ", k); 543 } 544 } 545 546 import hunt.serialization.Common; 547 return JsonSerializer.toObject!(T, SerializationOptions.Default.canThrow(false))(jv); 548 } 549 550 @property string[][string] xFormData() { 551 if (_xFormData is null && _isXFormUrlencoded) { 552 UrlEncoded map = new UrlEncoded(); 553 map.decode(getStringBody()); 554 foreach (string key; map.byKey()) { 555 foreach(string v; map.getValues(key)) { 556 key = key.strip(); 557 _xFormData[key] ~= v.strip(); 558 } 559 } 560 } 561 return _xFormData; 562 } 563 564 /** 565 * Retrieve a cookie from the request. 566 * 567 * @param string key 568 * @param string|array|null default 569 * @return string|array 570 */ 571 string cookie(string key, string defaultValue = null) { 572 // return cookieManager.get(key, defaultValue); 573 foreach (Cookie c; getCookies()) { 574 if (c.getName == key) 575 return c.getValue(); 576 } 577 return defaultValue; 578 } 579 580 /** 581 * Retrieve users' own preferred language. 582 */ 583 string locale() { 584 string l; 585 l = cookie("Content-Language"); 586 if(l is null) 587 l = _options.requestOptions.defaultLanguage(); 588 589 return toLower(l); 590 } 591 592 private string[][string] _xFormData; 593 private string[string] _queryParams; 594 595 private bool _isMultipart = false; 596 private bool _isXFormUrlencoded = false; 597 private bool _isQueryParamsSet = false; 598 599 // dfmton 600 601 602 }