1 module hunt.http.server.HttpServer; 2 3 import hunt.http.server.ClientAuth; 4 import hunt.http.server.GlobalSettings; 5 import hunt.http.server.Http1ServerDecoder; 6 import hunt.http.server.Http2ServerDecoder; 7 import hunt.http.server.Http2ServerRequestHandler; 8 import hunt.http.server.HttpRequestOptions; 9 import hunt.http.server.HttpServerConnection; 10 import hunt.http.server.HttpServerContext; 11 import hunt.http.server.HttpServerHandler; 12 import hunt.http.server.HttpServerOptions; 13 import hunt.http.server.HttpServerRequest; 14 import hunt.http.server.HttpServerResponse; 15 import hunt.http.server.ServerHttpHandler; 16 import hunt.http.server.ServerSessionListener; 17 import hunt.http.server.WebSocketHandler; 18 19 import hunt.http.codec.CommonDecoder; 20 import hunt.http.codec.CommonEncoder; 21 import hunt.http.codec.websocket.decode.WebSocketDecoder; 22 import hunt.http.codec.websocket.frame; 23 24 import hunt.http.routing; 25 import hunt.http.HttpConnection; 26 import hunt.http.HttpHeader; 27 import hunt.http.HttpMethod; 28 import hunt.http.HttpOutputStream; 29 import hunt.http.HttpRequest; 30 import hunt.http.HttpResponse; 31 import hunt.http.HttpStatus; 32 import hunt.http.WebSocketConnection; 33 import hunt.http.WebSocketPolicy; 34 35 import hunt.io.BufferUtils; 36 import hunt.io.ByteBuffer; 37 import hunt.event.EventLoop; 38 import hunt.Exceptions; 39 import hunt.Functions; 40 import hunt.logging; 41 import hunt.net; 42 import hunt.util.DateTime; 43 import hunt.util.AbstractLifecycle; 44 45 import core.time; 46 47 import std.array; 48 import std.algorithm; 49 import std.file; 50 import std.path; 51 import std.string; 52 53 54 version(WITH_HUNT_TRACE) { 55 import hunt.net.util.HttpURI; 56 import hunt.trace.Constrants; 57 import hunt.trace.Endpoint; 58 import hunt.trace.HttpSender; 59 import hunt.trace.Span; 60 import hunt.trace.Tracer; 61 import hunt.trace.TracingOptions; 62 63 import std.conv; 64 import std.format; 65 } 66 67 alias BadRequestHandler = ActionN!(HttpServerContext); 68 alias ServerErrorHandler = ActionN!(HttpServer, Exception); 69 70 /** 71 * 72 */ 73 class HttpServer : AbstractLifecycle { 74 75 private NetServer _server; 76 private NetServerOptions _serverOptions; 77 private HttpServerOptions _httpOptions; 78 private string host; 79 private int port; 80 81 private Action _openSucceededHandler; 82 private ActionN!(Throwable) _openFailedHandler; 83 private ServerErrorHandler _errorHandler; 84 85 this(HttpServerOptions httpOptions, 86 ServerHttpHandler serverHttpHandler, WebSocketHandler webSocketHandler) { 87 this(httpOptions, new Http2ServerRequestHandler(serverHttpHandler), 88 serverHttpHandler, webSocketHandler); 89 } 90 91 this(string host, ushort port, HttpServerOptions httpOptions, 92 ServerHttpHandler serverHttpHandler) { 93 httpOptions.setHost(host); 94 httpOptions.setPort(port); 95 this(httpOptions, new Http2ServerRequestHandler(serverHttpHandler), 96 serverHttpHandler, new WebSocketHandlerAdapter()); 97 } 98 99 this(string host, ushort port, HttpServerOptions httpOptions, 100 ServerHttpHandler serverHttpHandler, WebSocketHandler webSocketHandler) { 101 if (host is null) 102 throw new IllegalArgumentException("the http server host is empty"); 103 httpOptions.setHost(host); 104 httpOptions.setPort(port); 105 this(httpOptions, new Http2ServerRequestHandler(serverHttpHandler), 106 serverHttpHandler, webSocketHandler); 107 } 108 109 this(HttpServerOptions options, ServerSessionListener listener, 110 ServerHttpHandler serverHttpHandler, WebSocketHandler webSocketHandler) { 111 if (options is null) 112 throw new IllegalArgumentException("the http configuration is null"); 113 114 this.host = options.getHost(); 115 this.port = options.getPort(); 116 _serverOptions = options.getTcpConfiguration(); 117 if(_serverOptions is null ) { 118 _serverOptions = new NetServerOptions(); 119 options.setTcpConfiguration(_serverOptions); 120 } 121 this._httpOptions = options; 122 123 // httpServerHandler = new HttpServerHandler(options, listener, 124 // serverHttpHandler, webSocketHandler); 125 126 if (options.isSecureConnectionEnabled()) { 127 version(WITH_HUNT_SECURITY) { 128 import hunt.net.secure.SecureUtils; 129 KeyCertOptions certOptions = options.getKeyCertOptions(); 130 if(certOptions is null) { 131 warningf("No certificate files found. Using the defaults."); 132 } else { 133 SecureUtils.setServerCertificate(certOptions); 134 } 135 } else { 136 assert(false, "Please, add subConfigurations for hunt-http with TLS in dub.json."); 137 } 138 } 139 140 _server = NetUtil.createNetServer!(ThreadMode.Single)(_serverOptions); 141 142 // set codec 143 _server.setCodec(new class Codec { 144 private CommonEncoder encoder; 145 private CommonDecoder decoder; 146 147 this() { 148 encoder = new CommonEncoder(); 149 150 Http1ServerDecoder httpServerDecoder = new Http1ServerDecoder( 151 new WebSocketDecoder(), 152 new Http2ServerDecoder()); 153 decoder = new CommonDecoder(httpServerDecoder); 154 } 155 156 Encoder getEncoder() { 157 return encoder; 158 } 159 160 Decoder getDecoder() { 161 return decoder; 162 } 163 }); 164 165 // set handler 166 HttpServerHandler serverHandler = new HttpServerHandler(options, listener, serverHttpHandler, webSocketHandler); 167 168 serverHandler.onOpened((Connection conn) { 169 version(HUNT_HTTP_DEBUG) { 170 infof("A new http connection %d opened: %s", conn.getId, typeid(conn)); 171 } 172 version (HUNT_DEBUG) { 173 if (options.isSecureConnectionEnabled()) 174 infof("Opend a secured http connection: %s", conn.getRemoteAddress()); 175 else 176 infof("Opend a http connection: %s", conn.getRemoteAddress()); 177 } 178 179 // TODO: Tasks pending completion -@zhangxueping at 2020-05-18T16:27:44+08:00 180 // Pass the connection to _openSucceededHandler(); 181 if(_openSucceededHandler !is null) 182 _openSucceededHandler(); 183 }); 184 185 serverHandler.onOpenFailed((int id, Throwable ex) { 186 version(HUNT_HTTP_DEBUG) warning(ex.msg); 187 if(_openFailedHandler !is null) 188 _openFailedHandler(ex); 189 stop(); 190 }); 191 192 _server.setHandler(serverHandler); 193 // _server.setHandler(new HttpServerHandler(options, listener, serverHttpHandler, webSocketHandler)); 194 195 // For test 196 // string responseString = `HTTP/1.1 000 197 // Server: Hunt-HTTP/1.0 198 // Date: Tue, 11 Dec 2018 08:17:36 GMT 199 // Content-Type: text/plain 200 // Content-Length: 13 201 // Connection: keep-alive 202 203 // Hello, World!`; 204 } 205 206 207 HttpServerOptions getHttpOptions() { 208 return _httpOptions; 209 } 210 211 string getHost() { 212 return host; 213 } 214 215 int getPort() { 216 return port; 217 } 218 219 bool isOpen() { 220 return _server.isOpen(); 221 } 222 223 bool isTLS() { 224 return _httpOptions.isSecureConnectionEnabled(); 225 } 226 227 NetServer netServer() { 228 return _server; 229 } 230 231 override protected void initialize() { 232 checkWorkingDirectory(); 233 _server.listen(host, port); 234 } 235 236 override protected void destroy() { 237 238 if (_server !is null && _server.isOpen()) { 239 version(HUNT_DEBUG) warning("stopping the HttpServer..."); 240 _server.close(); 241 } 242 243 // version(HUNT_DEBUG) warning("stopping the EventLoop..."); 244 // NetUtil.stopEventLoop(); 245 } 246 247 private void checkWorkingDirectory() { 248 // FIXME: Needing refactor or cleanup -@zhangxueping at 2019-10-18T10:52:02+08:00 249 // make sure more directories existed. 250 HttpRequestOptions requestOptions = _httpOptions.requestOptions(); 251 string path = requestOptions.getTempFileAbsolutePath(); 252 253 if (!path.exists()) 254 path.mkdirRecurse(); 255 } 256 257 /* ----------------------------- connect events ----------------------------- */ 258 259 HttpServer onOpened(Action handler) { 260 _openSucceededHandler = handler; 261 return this; 262 } 263 264 HttpServer onOpenFailed(ActionN!(Throwable) handler) { 265 _openFailedHandler = handler; 266 return this; 267 } 268 269 HttpServer onError(ServerErrorHandler handler) { 270 _errorHandler = handler; 271 return this; 272 } 273 274 275 /* -------------------------------------------------------------------------- */ 276 /* Builder */ 277 /* -------------------------------------------------------------------------- */ 278 279 /** 280 * @return A builder that can be used to create an Undertow server instance 281 */ 282 static Builder builder() { 283 return new Builder(); 284 } 285 286 static Builder builder(HttpServerOptions serverOptions) { 287 return new Builder(serverOptions); 288 } 289 290 /** 291 * 292 */ 293 static final class Builder { 294 295 enum PostMethod = HttpMethod.POST.asString(); 296 enum GetMethod = HttpMethod.GET.asString(); 297 enum PutMethod = HttpMethod.PUT.asString(); 298 enum DeleteMethod = HttpMethod.DELETE.asString(); 299 300 private HttpServerOptions _httpOptions; 301 private RouterManager _routerManager; // default route manager 302 private RouterManager[string] _hostManagerGroup; // route manager group for host 303 private RouterManager[string] _pathManagerGroup; // route manager group for path 304 private Router _currentRouter; 305 private WebSocketMessageHandler[string] _webSocketHandlers; 306 // private bool _canCopyBuffer = false; 307 private Duration _resourceCacheTime = -1.seconds; 308 309 // SSL/TLS settings 310 private bool _isCertificateAuth = false; 311 private string _tlsCaFile; 312 private string _tlsCaPassworld; 313 private string _tlsCertificate; 314 private string _tlsPrivateKey; 315 private string _tlsCertPassword; 316 private string _tlsKeyPassword; 317 318 319 this() { 320 this(new HttpServerOptions()); 321 } 322 323 this(HttpServerOptions options) { 324 _httpOptions = options; 325 _routerManager = RouterManager.create(); 326 327 // set the server options as a global variable 328 GlobalSettings.httpServerOptions = _httpOptions; 329 } 330 331 Builder setListener(ushort port) { 332 _httpOptions.setPort(port); 333 return this; 334 } 335 336 Builder setListener(ushort port, string host) { 337 _httpOptions.setPort(port); 338 _httpOptions.setHost(host); 339 return this; 340 } 341 342 Builder ioThreadSize(uint value) { 343 _httpOptions.getTcpConfiguration().ioThreadSize = value; 344 return this; 345 } 346 347 Builder workerThreadSize(uint value) { 348 _httpOptions.getTcpConfiguration().workerThreadSize = value; 349 return this; 350 } 351 352 Builder canUpgrade(bool value) { 353 _httpOptions.canUpgrade = value; 354 return this; 355 } 356 357 Builder resourceCacheTime(Duration value) { 358 _resourceCacheTime = value; 359 return this; 360 } 361 362 // Certificate Authority (CA) certificate 363 Builder setCaCert(string caFile, string caPassword) { 364 _tlsCaFile = caFile; 365 _tlsCaPassworld = caPassword; 366 return this; 367 } 368 369 Builder setTLS(string certificate, string privateKey, string certPassword="", string keyPassword="") { 370 _isCertificateAuth = true; 371 _tlsCertificate = certificate; 372 _tlsPrivateKey = privateKey; 373 _tlsCertPassword = certPassword; 374 _tlsKeyPassword = keyPassword; 375 return this; 376 } 377 378 Builder requiresClientAuth() { 379 _httpOptions.setClientAuth(ClientAuth.REQUIRED); 380 return this; 381 } 382 383 Builder setHandler(RoutingHandler handler) { 384 addRoute(["*"], null, handler); 385 return this; 386 } 387 388 /** 389 * register a new router 390 */ 391 // Router newRouter() { 392 // return _routerManager.register(); 393 // } 394 395 Builder addRoute(string path, RoutingHandler handler) { 396 return addRoute([path], cast(string[])null, handler); 397 } 398 399 Builder addRoute(string path, HttpMethod method, RoutingHandler handler) { 400 return addRoute([path], [method.toString()], handler); 401 } 402 403 Builder addRoute(string path, HttpMethod[] methods, RoutingHandler handler) { 404 string[] ms = map!((m) => m.asString())(methods).array; 405 return addRoute([path], ms, handler); 406 } 407 408 // Builder addRoute(string path, string[] methods, RoutingHandler handler) { 409 // return addRoute([path], methods, handler); 410 // } 411 412 // Builder addRoute(string[] paths, HttpMethod[] methods, RoutingHandler handler) { 413 // string[] ms = map!((m) => m.asString())(methods).array; 414 // return addRoute(paths, ms, handler); 415 // } 416 417 Builder addRoute(string[] paths, string[] methods, RoutingHandler handler, 418 string groupName=null, RouteGroupType groupType = RouteGroupType.Host, 419 string attributeName = null, Object attributeObj = null) { 420 if(groupName.empty) { 421 _currentRouter = _routerManager.register(); 422 } else { 423 _currentRouter = getGroupRouterManager(groupName, groupType).register(); 424 } 425 426 version(HUNT_HTTP_DEBUG) { 427 tracef("routeid: %d, paths: %s, group: %s", _currentRouter.getId(), paths, groupName); 428 } 429 430 _currentRouter.paths(paths); 431 foreach(string m; methods) { 432 _currentRouter.method(m); 433 } 434 _currentRouter.handler( (RoutingContext ctx) { 435 if(!attributeName.empty() && attributeObj !is null) { 436 ctx.setAttribute(attributeName, attributeObj); 437 } 438 handlerWrap(handler, ctx); 439 }); 440 return this; 441 } 442 443 Builder addRoute(string[] paths, string[] methods, RouteHandler handler, 444 string groupName=null, RouteGroupType groupType = RouteGroupType.Host, 445 string attributeName = null, Object attributeObj = null) { 446 if(groupName.empty) { 447 _currentRouter = _routerManager.register(); 448 } else { 449 _currentRouter = getGroupRouterManager(groupName, groupType).register(); 450 } 451 version(HUNT_HTTP_DEBUG) tracef("routeid: %d, paths: %s", _currentRouter.getId(), paths); 452 _currentRouter.paths(paths); 453 foreach(string m; methods) { 454 _currentRouter.method(m); 455 } 456 _currentRouter.handler( (RoutingContext ctx) { 457 if(!attributeName.empty() && attributeObj !is null) { 458 ctx.setAttribute(attributeName, attributeObj); 459 } 460 handlerWrap(handler, ctx); 461 }); 462 return this; 463 } 464 465 // Builder addRegexRoute(string regex, RoutingHandler handler) { 466 // return addRegexRoute(regex, cast(string[])null, handler); 467 // } 468 469 // Builder addRegexRoute(string regex, HttpMethod[] methods, RoutingHandler handler) { 470 // string[] ms = map!((m) => m.asString())(methods).array; 471 // return addRegexRoute(regex, ms, handler); 472 // } 473 474 Builder addRegexRoute(string regex, string[] methods, RoutingHandler handler, 475 string groupName=null, RouteGroupType groupType = RouteGroupType.Host) { 476 if(groupName.empty) { 477 _currentRouter = _routerManager.register(); 478 } else { 479 _currentRouter = getGroupRouterManager(groupName, groupType).register(); 480 } 481 version(HUNT_HTTP_DEBUG) tracef("routeid: %d, paths: %s", _currentRouter.getId(), regex); 482 _currentRouter.pathRegex(regex); 483 foreach(string m; methods) { 484 _currentRouter.method(m); 485 } 486 _currentRouter.handler( (RoutingContext ctx) { handlerWrap(handler, ctx); }); 487 return this; 488 } 489 490 private RouterManager getGroupRouterManager(string groupName, RouteGroupType groupType) { 491 RouterManager manager; 492 if(groupType == RouteGroupType.Host) { 493 auto itemPtr = groupName in _hostManagerGroup; 494 if(itemPtr is null) { 495 manager = RouterManager.create(); 496 _hostManagerGroup[groupName] = manager; 497 } else { 498 manager = *itemPtr; 499 } 500 } else { 501 auto itemPtr = groupName in _pathManagerGroup; 502 if(itemPtr is null) { 503 manager = RouterManager.create(); 504 _pathManagerGroup[groupName] = manager; 505 } else { 506 manager = *itemPtr; 507 } 508 } 509 510 return manager; 511 } 512 513 Builder onGet(string path, RoutingHandler handler, 514 string groupName=null, RouteGroupType groupType = RouteGroupType.Host) { 515 return addRoute([path], [GetMethod], handler, groupName, groupType); 516 } 517 518 Builder onPost(string path, RoutingHandler handler, 519 string groupName=null, RouteGroupType groupType = RouteGroupType.Host) { 520 return addRoute([path], [PostMethod], handler, groupName, groupType); 521 } 522 523 Builder onPut(string path, RoutingHandler handler, 524 string groupName=null, RouteGroupType groupType = RouteGroupType.Host) { 525 return addRoute([path], [PutMethod], handler, groupName, groupType); 526 } 527 528 Builder onDelete(string path, RoutingHandler handler, 529 string groupName=null, RouteGroupType groupType = RouteGroupType.Host) { 530 return addRoute([path], [DeleteMethod], handler, groupName, groupType); 531 } 532 533 Builder onRequest(string method, string path, RoutingHandler handler, 534 string groupName=null, RouteGroupType groupType = RouteGroupType.Host) { 535 return addRoute([path], [method], handler, groupName, groupType); 536 } 537 538 Builder setDefaultRequest(RoutingHandler handler) { 539 _currentRouter = _routerManager.register(DEFAULT_LAST_ROUTER_ID-1); 540 version(HUNT_HTTP_DEBUG) tracef("routeid: %d, paths: %s", _currentRouter.getId(), "*"); 541 _currentRouter.path("*").handler( (RoutingContext ctx) { 542 ctx.setStatus(HttpStatus.NOT_FOUND_404); 543 handlerWrap(handler, ctx); 544 }); 545 return this; 546 } 547 548 alias addNotFoundRoute = setDefaultRequest; 549 550 Builder resource(string path, string localPath, bool canList = true, 551 string groupName=null, RouteGroupType groupType = RouteGroupType.Host) { 552 DefaultResourceHandler handler = new DefaultResourceHandler(amendingPath(path), localPath); 553 handler.isListingEnabled = canList; 554 handler.cacheTime = _resourceCacheTime; 555 556 return resource(path, handler, groupName, groupType); 557 } 558 559 private static string amendingPath(string path) { 560 if(path.empty) return ""; 561 562 string r; 563 if(path[$-1] != '/') { 564 r = path ~ "/"; 565 } else { 566 r = path; 567 } 568 569 return r; 570 } 571 572 Builder resource(string path, AbstractResourceHandler handler, 573 string groupName=null, RouteGroupType groupType = RouteGroupType.Host) { 574 path = path.strip(); 575 assert(path.length > 0, "The path can't be empty"); 576 577 if(path[0] != '/') 578 path = "/" ~ path; 579 580 string staticPath; 581 if(path[$-1] != '/') { 582 staticPath = path; 583 path ~= "/"; 584 } else { 585 staticPath = path[0..$-1]; 586 } 587 588 path ~= "*"; 589 version(HUNT_HTTP_DEBUG) { 590 tracef("path: %s, staticPath: %s, canList: %s", path, staticPath, handler.isListingEnabled()); 591 } 592 593 if(path == staticPath || staticPath.empty()) { 594 return addRoute([path], cast(string[])null, handler, groupName, groupType); 595 } else { 596 return addRoute([path, staticPath], cast(string[])null, handler, groupName, groupType); 597 } 598 } 599 600 Builder websocket(string path, WebSocketMessageHandler handler) { 601 auto itemPtr = path in _webSocketHandlers; 602 if(itemPtr !is null) 603 throw new Exception("Handler registered on path: " ~ path); 604 605 // WebSocket path should be skipped 606 Router webSocketRouter = _routerManager.register().path(path); 607 version(HUNT_HTTP_DEBUG) tracef("routeid: %d, paths: %s", webSocketRouter.getId(), path); 608 _webSocketHandlers[path] = handler; 609 610 version(HUNT_HTTP_DEBUG) { 611 webSocketRouter.handler( (ctx) { 612 infof("Skip a router path for WebSocket: %s", path); 613 }); 614 } 615 616 return this; 617 } 618 619 Builder enableLocalSessionStore() { 620 LocalHttpSessionHandler sessionHandler = new LocalHttpSessionHandler(_httpOptions); 621 _currentRouter = _routerManager.register(); 622 version(HUNT_HTTP_DEBUG) tracef("routeid: %d, paths: *", _currentRouter.getId()); 623 _currentRouter.path("*").handler(sessionHandler); 624 return this; 625 } 626 627 private void handlerWrap(RouteHandler handler, RoutingContext ctx) { 628 try { 629 if(handler !is null) handler.handle(ctx); 630 } catch (Exception ex) { 631 errorf("route handler exception: %s", ex.msg); 632 version(HUNT_HTTP_DEBUG) error(ex); 633 if(!ctx.isCommitted()) { 634 HttpServerResponse res = ctx.getResponse(); 635 if(res !is null) { 636 res.setStatus(HttpStatus.BAD_REQUEST_400); 637 } 638 ctx.end(ex.msg); 639 } 640 ctx.fail(ex); 641 } 642 } 643 644 private void handlerWrap(RoutingHandler handler, RoutingContext ctx) { 645 try { 646 if(handler !is null) handler(ctx); 647 } catch (Exception ex) { 648 errorf("route handler exception: %s", ex.msg); 649 version(HUNT_DEBUG) error(ex); 650 if(!ctx.isCommitted()) { 651 HttpServerResponse res = ctx.getResponse(); 652 if(res !is null) { 653 res.setStatus(HttpStatus.BAD_REQUEST_400); 654 } 655 ctx.end(ex.msg); 656 } 657 ctx.fail(ex); 658 } 659 } 660 661 Builder onError(BadRequestHandler handler) { 662 _badRequestHandler = handler; 663 return this; 664 } 665 666 private void onError(HttpServerContext context) { 667 if(_badRequestHandler !is null) { 668 _badRequestHandler(context); 669 } 670 671 if(!context.isCommitted()) 672 context.end(); 673 } 674 675 private BadRequestHandler _badRequestHandler; 676 677 678 version(WITH_HUNT_TRACE) { 679 private string _localServiceName; 680 private bool _isB3HeaderRequired = true; 681 // private TracingOptions _tracingOptions; 682 683 Builder localServiceName(string name) { 684 _localServiceName = name; 685 return this; 686 } 687 688 Builder isB3HeaderRequired(bool flag) { 689 _isB3HeaderRequired = flag; 690 return this; 691 } 692 693 private void initializeTracer(HttpRequest request, HttpConnection connection) { 694 if(!isTraceEnabled) return; 695 696 Tracer tracer; 697 string reqPath = request.getURI().getPath(); 698 string b3 = request.header("b3"); 699 if(b3.empty()) { 700 // if(_isB3HeaderRequired) return; 701 version(HUNT_HTTP_DEBUG) { 702 warningf("No B3 headers found for %s, use the defaults now.", reqPath); 703 } 704 tracer = new Tracer(reqPath, KindOfServer); 705 } else { 706 version(HUNT_HTTP_DEBUG) { 707 warningf("initializing tracer for %s, with %s", reqPath, b3); 708 } 709 710 tracer = new Tracer(reqPath, KindOfServer, b3); 711 } 712 713 Span span = tracer.root; 714 span.localEndpoint = _localEndpoint; 715 716 import std.socket; 717 718 // 719 Address remote = connection.getRemoteAddress; 720 EndPoint remoteEndpoint = new EndPoint(); 721 remoteEndpoint.ipv4 = remote.toAddrString(); 722 remoteEndpoint.port = remote.toPortString().to!int; 723 span.remoteEndpoint = remoteEndpoint; 724 // 725 726 span.start(); 727 request.tracer = tracer; 728 } 729 730 private EndPoint _localEndpoint; 731 732 private void endTraceSpan(HttpRequest request, int status, string message = null) { 733 Tracer tracer = request.tracer; 734 if(tracer is null) { 735 version(HUNT_TRACE_DEBUG) warning("no tracer found"); 736 return; 737 } 738 739 HttpURI uri = request.getURI(); 740 string[string] tags; 741 tags[HTTP_HOST] = uri.getHost(); 742 tags[HTTP_URL] = uri.getPathQuery(); 743 tags[HTTP_PATH] = uri.getPath(); 744 tags[HTTP_REQUEST_SIZE] = request.getContentLength().to!string(); 745 tags[HTTP_METHOD] = request.getMethod(); 746 747 Span span = tracer.root; 748 if(span !is null) { 749 tags[HTTP_STATUS_CODE] = to!string(status); 750 traceSpanAfter(span, tags, message); 751 httpSender().sendSpans(span); 752 } 753 } 754 } 755 private ServerHttpHandler buildServerHttpHandler() { 756 ServerHttpHandlerAdapter adapter = new ServerHttpHandlerAdapter(_httpOptions); 757 758 adapter.acceptHttpTunnelConnection((request, response, ot, connection) { 759 version(HUNT_HTTP_DEBUG) info("acceptHttpTunnelConnection!!!"); 760 // if (tunnel !is null) { 761 // tunnel(r, connection); 762 // } 763 return true; 764 }) 765 .headerComplete((request, response, ot, connection) { 766 version(HUNT_HTTP_DEBUG) info("headerComplete!"); 767 HttpServerRequest serverRequest = cast(HttpServerRequest)request; 768 version(WITH_HUNT_TRACE) { 769 initializeTracer(serverRequest, connection); 770 } 771 772 HttpServerContext context = new HttpServerContext( 773 serverRequest, cast(HttpServerResponse)response, 774 ot, cast(HttpServerConnection)connection); 775 776 serverRequest.onHeaderComplete(); 777 dispatchRoute(context); 778 779 // request.setAttachment(context); 780 781 return false; 782 }) 783 .content((buffer, request, response, ot, connection) { 784 version(HUNT_HTTP_DEBUG) tracef("content: %s", BufferUtils.toDetailString(buffer)); 785 786 HttpServerRequest serverRequest = cast(HttpServerRequest)request; 787 serverRequest.onContent(buffer); 788 return false; 789 }) 790 .contentComplete((request, response, ot, connection) { 791 version(HUNT_HTTP_DEBUG) info("contentComplete!"); 792 HttpServerRequest serverRequest = cast(HttpServerRequest)request; 793 serverRequest.onContentComplete(); 794 // HttpServerContext context = cast(HttpServerContext) request.getAttachment(); 795 // context.onContentComplete(); 796 // if (r.contentComplete !is null) { 797 // r.contentComplete(r); 798 // } 799 return false; 800 }) 801 .messageComplete((request, response, ot, connection) { 802 version(HUNT_HTTP_DEBUG) info("messageComplete!"); 803 HttpServerRequest serverRequest = cast(HttpServerRequest)request; 804 serverRequest.onMessageComplete(); 805 // if (!r.getResponse().isAsynchronous()) { 806 // IOUtils.close(r.getResponse()); 807 // } 808 version(HUNT_HTTP_DEBUG) info("end of a request on ", request.getURI().getPath()); 809 version(WITH_HUNT_TRACE) endTraceSpan(request, response.getStatus()); 810 return true; 811 }) 812 .badMessage((status, reason, request, response, ot, connection) { 813 version(HUNT_DEBUG) warningf("badMessage: status=%d reason=%s", status, reason); 814 815 version(HUNT_HTTP_DEBUG) tracef("response is null: %s", response is null); 816 if(response is null) { 817 response = new HttpServerResponse(status, reason, null, -1); 818 } 819 820 HttpServerContext context = new HttpServerContext( 821 cast(HttpServerRequest) request, 822 cast(HttpServerResponse)response, 823 ot, cast(HttpServerConnection)connection); 824 825 version(WITH_HUNT_TRACE) endTraceSpan(request, status, reason); 826 onError(context); 827 }) 828 .earlyEOF((request, response, ot, connection) { 829 version(HUNT_HTTP_DEBUG) info("earlyEOF!"); 830 }); 831 832 return adapter; 833 } 834 835 private WebSocketHandler buildWebSocketHandler() { 836 WebSocketHandlerAdapter adapter = new WebSocketHandlerAdapter(); 837 838 adapter 839 .onAcceptUpgrade((HttpRequest request, HttpResponse response, 840 HttpOutputStream output, HttpConnection connection) { 841 string path = request.getURI().getPath(); 842 WebSocketMessageHandler handler = _webSocketHandlers.get(path, null); 843 if (handler is null) { 844 response.setStatus(HttpStatus.BAD_REQUEST_400); 845 try { 846 output.write(cast(byte[])("No websocket handler for url: " ~ path)); 847 } catch (IOException e) { 848 version(HUNT_DEBUG) errorf("Write http message exception", e.msg); 849 } 850 return false; 851 } else { 852 // return handler.acceptUpgrade(request, response, output, connection); 853 return true; 854 } 855 }) 856 .onOpen((connection) { 857 string path = connection.getPath(); 858 WebSocketMessageHandler handler = _webSocketHandlers.get(path, null); 859 if(handler !is null) 860 handler.onOpen(connection); 861 }) 862 .onFrame((WebSocketFrame frame, WebSocketConnection connection) { 863 string path = connection.getPath(); 864 WebSocketMessageHandler handler = _webSocketHandlers.get(path, null); 865 if(handler is null) { 866 return; 867 } 868 869 switch (frame.getType()) { 870 case WebSocketFrameType.TEXT: 871 handler.onText(connection, (cast(DataFrame) frame).getPayloadAsUTF8()); 872 break; 873 874 case WebSocketFrameType.BINARY: 875 handler.onBinary(connection, frame.getPayload()); 876 break; 877 878 case WebSocketFrameType.CLOSE: 879 handler.onClosed(connection); 880 break; 881 882 case WebSocketFrameType.PING: 883 handler.onPing(connection); 884 break; 885 886 case WebSocketFrameType.PONG: 887 handler.onPong(connection); 888 break; 889 890 case WebSocketFrameType.CONTINUATION: 891 handler.onContinuation(connection, frame.getPayload()); 892 break; 893 894 default: break; 895 } 896 }) 897 .onError((Exception ex, WebSocketConnection connection) { 898 string path = connection.getPath(); 899 WebSocketMessageHandler handler = _webSocketHandlers.get(path, null); 900 if(handler !is null) 901 handler.onError(connection, ex); 902 }); 903 904 return adapter; 905 } 906 907 private void dispatchRoute(HttpServerContext context) { 908 bool isHandled = false; 909 if(_hostManagerGroup.length > 0) { 910 HttpServerRequest request = context.httpRequest(); 911 string host = request.host(); 912 if(!host.empty()) { 913 host = split(host, ":")[0]; 914 auto itemPtr = host in _hostManagerGroup; 915 if(itemPtr !is null) { 916 isHandled = true; 917 itemPtr.accept(context); 918 version(HUNT_HTTP_DEBUG) { 919 tracef("host group, host: %s, path: %s", host, request.path()); 920 } 921 } 922 } 923 } 924 925 if(_pathManagerGroup.length > 0) { 926 HttpServerRequest request = context.httpRequest(); 927 string path = request.originalPath(); 928 // if(path.length > 1 && path[$-1] != '/') { 929 // path ~= "/"; 930 // } 931 932 string groupPath = split(path, "/")[1]; 933 934 // warningf("full path: %s, group path: %s", path, groupPath); 935 auto itemPtr = groupPath in _pathManagerGroup; 936 if(itemPtr !is null) { 937 isHandled = true; 938 path = path[groupPath.length + 1 .. $]; // skip the group path 939 version(HUNT_HTTP_DEBUG) { 940 tracef("full path: %s, group path: %s, new path: %s", request.path(), groupPath, path); 941 } 942 943 request.path = path; // Reset the rquest path 944 itemPtr.accept(context); 945 version(HUNT_HTTP_DEBUG_MORE) { 946 tracef("path group, original path: %s, revised path: %s, group path: %s", 947 request.originalPath(), request.path(), groupPath); 948 } 949 } 950 } 951 952 if(!isHandled) { 953 _routerManager.accept(context); 954 } 955 } 956 957 /* ---------------------------- Options operation --------------------------- */ 958 959 Builder maxRequestSize(int size) { 960 _httpOptions.requestOptions.setMaxRequestSize(size); 961 return this; 962 } 963 964 Builder maxFileSize(int size) { 965 _httpOptions.requestOptions.setMaxFileSize(size); 966 return this; 967 } 968 969 HttpServer build() { 970 971 string basePath = dirName(thisExePath); 972 973 if(!_tlsCertificate.empty()) { 974 PemKeyCertOptions certOptions = new PemKeyCertOptions(buildPath(basePath, _tlsCertificate), 975 buildPath(basePath, _tlsPrivateKey), _tlsCertPassword, _tlsKeyPassword); 976 977 if(!_tlsCaFile.empty()) { 978 certOptions.setCaFile(buildPath(basePath, _tlsCaFile)); 979 certOptions.setCaPassword(_tlsCaPassworld); 980 } 981 _httpOptions.setKeyCertOptions(certOptions); 982 } 983 984 _httpOptions.setSecureConnectionEnabled(_isCertificateAuth); 985 version(WITH_HUNT_TRACE) { 986 _localEndpoint = Span.buildLocalEndPoint(_localServiceName); 987 } 988 989 return new HttpServer(_httpOptions, buildServerHttpHandler(), buildWebSocketHandler()); 990 } 991 } 992 }