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 override protected void initialize() { 228 checkWorkingDirectory(); 229 _server.listen(host, port); 230 } 231 232 override protected void destroy() { 233 234 if (_server !is null && _server.isOpen()) { 235 version(HUNT_DEBUG) warning("stopping the HttpServer..."); 236 _server.close(); 237 } 238 239 // version(HUNT_DEBUG) warning("stopping the EventLoop..."); 240 // NetUtil.stopEventLoop(); 241 } 242 243 private void checkWorkingDirectory() { 244 // FIXME: Needing refactor or cleanup -@zhangxueping at 2019-10-18T10:52:02+08:00 245 // make sure more directories existed. 246 HttpRequestOptions requestOptions = _httpOptions.requestOptions(); 247 string path = requestOptions.getTempFileAbsolutePath(); 248 249 if (!path.exists()) 250 path.mkdirRecurse(); 251 } 252 253 /* ----------------------------- connect events ----------------------------- */ 254 255 HttpServer onOpened(Action handler) { 256 _openSucceededHandler = handler; 257 return this; 258 } 259 260 HttpServer onOpenFailed(ActionN!(Throwable) handler) { 261 _openFailedHandler = handler; 262 return this; 263 } 264 265 HttpServer onError(ServerErrorHandler handler) { 266 _errorHandler = handler; 267 return this; 268 } 269 270 271 /* -------------------------------------------------------------------------- */ 272 /* Builder */ 273 /* -------------------------------------------------------------------------- */ 274 275 /** 276 * @return A builder that can be used to create an Undertow server instance 277 */ 278 static Builder builder() { 279 return new Builder(); 280 } 281 282 static Builder builder(HttpServerOptions serverOptions) { 283 return new Builder(serverOptions); 284 } 285 286 /** 287 * 288 */ 289 static final class Builder { 290 291 enum PostMethod = HttpMethod.POST.asString(); 292 enum GetMethod = HttpMethod.GET.asString(); 293 enum PutMethod = HttpMethod.PUT.asString(); 294 enum DeleteMethod = HttpMethod.DELETE.asString(); 295 296 private HttpServerOptions _httpOptions; 297 private RouterManager _routerManager; // default route manager 298 private RouterManager[string] _hostManagerGroup; // route manager group for host 299 private RouterManager[string] _pathManagerGroup; // route manager group for path 300 private Router _currentRouter; 301 private WebSocketMessageHandler[string] _webSocketHandlers; 302 // private bool _canCopyBuffer = false; 303 304 // SSL/TLS settings 305 private bool _isCertificateAuth = false; 306 private string _tlsCaFile; 307 private string _tlsCaPassworld; 308 private string _tlsCertificate; 309 private string _tlsPrivateKey; 310 private string _tlsCertPassword; 311 private string _tlsKeyPassword; 312 313 314 this() { 315 this(new HttpServerOptions()); 316 } 317 318 this(HttpServerOptions options) { 319 _httpOptions = options; 320 _routerManager = RouterManager.create(); 321 322 // set the server options as a global variable 323 GlobalSettings.httpServerOptions = _httpOptions; 324 } 325 326 Builder setListener(ushort port) { 327 _httpOptions.setPort(port); 328 return this; 329 } 330 331 Builder setListener(ushort port, string host) { 332 _httpOptions.setPort(port); 333 _httpOptions.setHost(host); 334 return this; 335 } 336 337 Builder ioThreadSize(uint value) { 338 _httpOptions.getTcpConfiguration().ioThreadSize = value; 339 return this; 340 } 341 342 Builder workerThreadSize(uint value) { 343 _httpOptions.getTcpConfiguration().workerThreadSize = value; 344 return this; 345 } 346 347 // Certificate Authority (CA) certificate 348 Builder setCaCert(string caFile, string caPassword) { 349 _tlsCaFile = caFile; 350 _tlsCaPassworld = caPassword; 351 return this; 352 } 353 354 Builder setTLS(string certificate, string privateKey, string certPassword="", string keyPassword="") { 355 _isCertificateAuth = true; 356 _tlsCertificate = certificate; 357 _tlsPrivateKey = privateKey; 358 _tlsCertPassword = certPassword; 359 _tlsKeyPassword = keyPassword; 360 return this; 361 } 362 363 Builder requiresClientAuth() { 364 _httpOptions.setClientAuth(ClientAuth.REQUIRED); 365 return this; 366 } 367 368 Builder setHandler(RoutingHandler handler) { 369 addRoute(["*"], null, handler); 370 return this; 371 } 372 373 /** 374 * register a new router 375 */ 376 // Router newRouter() { 377 // return _routerManager.register(); 378 // } 379 380 Builder addRoute(string path, RoutingHandler handler) { 381 return addRoute([path], cast(string[])null, handler); 382 } 383 384 Builder addRoute(string path, HttpMethod method, RoutingHandler handler) { 385 return addRoute([path], [method.toString()], handler); 386 } 387 388 Builder addRoute(string path, HttpMethod[] methods, RoutingHandler handler) { 389 string[] ms = map!((m) => m.asString())(methods).array; 390 return addRoute([path], ms, handler); 391 } 392 393 // Builder addRoute(string path, string[] methods, RoutingHandler handler) { 394 // return addRoute([path], methods, handler); 395 // } 396 397 // Builder addRoute(string[] paths, HttpMethod[] methods, RoutingHandler handler) { 398 // string[] ms = map!((m) => m.asString())(methods).array; 399 // return addRoute(paths, ms, handler); 400 // } 401 402 Builder addRoute(string[] paths, string[] methods, RoutingHandler handler, 403 string groupName=null, RouteGroupType groupType = RouteGroupType.Host, 404 string attributeName = null, Object attributeObj = null) { 405 if(groupName.empty) { 406 _currentRouter = _routerManager.register(); 407 } else { 408 _currentRouter = getGroupRouterManager(groupName, groupType).register(); 409 } 410 411 version(HUNT_HTTP_DEBUG) { 412 tracef("routeid: %d, paths: %s, group: %s", _currentRouter.getId(), paths, groupName); 413 } 414 415 _currentRouter.paths(paths); 416 foreach(string m; methods) { 417 _currentRouter.method(m); 418 } 419 _currentRouter.handler( (RoutingContext ctx) { 420 if(!attributeName.empty() && attributeObj !is null) { 421 ctx.setAttribute(attributeName, attributeObj); 422 } 423 handlerWrap(handler, ctx); 424 }); 425 return this; 426 } 427 428 Builder addRoute(string[] paths, string[] methods, RouteHandler handler, 429 string groupName=null, RouteGroupType groupType = RouteGroupType.Host, 430 string attributeName = null, Object attributeObj = null) { 431 if(groupName.empty) { 432 _currentRouter = _routerManager.register(); 433 } else { 434 _currentRouter = getGroupRouterManager(groupName, groupType).register(); 435 } 436 version(HUNT_HTTP_DEBUG) tracef("routeid: %d, paths: %s", _currentRouter.getId(), paths); 437 _currentRouter.paths(paths); 438 foreach(string m; methods) { 439 _currentRouter.method(m); 440 } 441 _currentRouter.handler( (RoutingContext ctx) { 442 if(!attributeName.empty() && attributeObj !is null) { 443 ctx.setAttribute(attributeName, attributeObj); 444 } 445 handlerWrap(handler, ctx); 446 }); 447 return this; 448 } 449 450 // Builder addRegexRoute(string regex, RoutingHandler handler) { 451 // return addRegexRoute(regex, cast(string[])null, handler); 452 // } 453 454 // Builder addRegexRoute(string regex, HttpMethod[] methods, RoutingHandler handler) { 455 // string[] ms = map!((m) => m.asString())(methods).array; 456 // return addRegexRoute(regex, ms, handler); 457 // } 458 459 Builder addRegexRoute(string regex, string[] methods, RoutingHandler handler, 460 string groupName=null, RouteGroupType groupType = RouteGroupType.Host) { 461 if(groupName.empty) { 462 _currentRouter = _routerManager.register(); 463 } else { 464 _currentRouter = getGroupRouterManager(groupName, groupType).register(); 465 } 466 version(HUNT_HTTP_DEBUG) tracef("routeid: %d, paths: %s", _currentRouter.getId(), regex); 467 _currentRouter.pathRegex(regex); 468 foreach(string m; methods) { 469 _currentRouter.method(m); 470 } 471 _currentRouter.handler( (RoutingContext ctx) { handlerWrap(handler, ctx); }); 472 return this; 473 } 474 475 private RouterManager getGroupRouterManager(string groupName, RouteGroupType groupType) { 476 RouterManager manager; 477 if(groupType == RouteGroupType.Host) { 478 auto itemPtr = groupName in _hostManagerGroup; 479 if(itemPtr is null) { 480 manager = RouterManager.create(); 481 _hostManagerGroup[groupName] = manager; 482 } else { 483 manager = *itemPtr; 484 } 485 } else { 486 auto itemPtr = groupName in _pathManagerGroup; 487 if(itemPtr is null) { 488 manager = RouterManager.create(); 489 _pathManagerGroup[groupName] = manager; 490 } else { 491 manager = *itemPtr; 492 } 493 } 494 495 return manager; 496 } 497 498 Builder onGet(string path, RoutingHandler handler, 499 string groupName=null, RouteGroupType groupType = RouteGroupType.Host) { 500 return addRoute([path], [GetMethod], handler, groupName, groupType); 501 } 502 503 Builder onPost(string path, RoutingHandler handler, 504 string groupName=null, RouteGroupType groupType = RouteGroupType.Host) { 505 return addRoute([path], [PostMethod], handler, groupName, groupType); 506 } 507 508 Builder onPut(string path, RoutingHandler handler, 509 string groupName=null, RouteGroupType groupType = RouteGroupType.Host) { 510 return addRoute([path], [PutMethod], handler, groupName, groupType); 511 } 512 513 Builder onDelete(string path, RoutingHandler handler, 514 string groupName=null, RouteGroupType groupType = RouteGroupType.Host) { 515 return addRoute([path], [DeleteMethod], handler, groupName, groupType); 516 } 517 518 Builder onRequest(string method, string path, RoutingHandler handler, 519 string groupName=null, RouteGroupType groupType = RouteGroupType.Host) { 520 return addRoute([path], [method], handler, groupName, groupType); 521 } 522 523 Builder setDefaultRequest(RoutingHandler handler) { 524 _currentRouter = _routerManager.register(DEFAULT_LAST_ROUTER_ID-1); 525 version(HUNT_HTTP_DEBUG) tracef("routeid: %d, paths: %s", _currentRouter.getId(), "*"); 526 _currentRouter.path("*").handler( (RoutingContext ctx) { 527 ctx.setStatus(HttpStatus.NOT_FOUND_404); 528 handlerWrap(handler, ctx); 529 }); 530 return this; 531 } 532 533 alias addNotFoundRoute = setDefaultRequest; 534 535 Builder resource(string path, string localPath, bool canList = true, 536 string groupName=null, RouteGroupType groupType = RouteGroupType.Host) { 537 return resource(path, new DefaultResourceHandler(amendingPath(path), localPath).isListingEnabled(canList), 538 groupName, groupType); 539 } 540 541 private static string amendingPath(string path) { 542 if(path.empty) return ""; 543 544 string r; 545 if(path[$-1] != '/') { 546 r = path ~ "/"; 547 } else { 548 r = path; 549 } 550 551 return r; 552 } 553 554 Builder resource(string path, AbstractResourceHandler handler, 555 string groupName=null, RouteGroupType groupType = RouteGroupType.Host) { 556 path = path.strip(); 557 assert(path.length > 0, "The path can't be empty"); 558 559 if(path[0] != '/') 560 path = "/" ~ path; 561 562 string staticPath; 563 if(path[$-1] != '/') { 564 staticPath = path; 565 path ~= "/"; 566 } else { 567 staticPath = path[0..$-1]; 568 } 569 570 path ~= "*"; 571 version(HUNT_HTTP_DEBUG) { 572 tracef("path: %s, staticPath: %s, canList: %s", path, staticPath, handler.isListingEnabled()); 573 } 574 575 if(path == staticPath || staticPath.empty()) { 576 return addRoute([path], cast(string[])null, handler, groupName, groupType); 577 } else { 578 return addRoute([path, staticPath], cast(string[])null, handler, groupName, groupType); 579 } 580 } 581 582 Builder websocket(string path, WebSocketMessageHandler handler) { 583 auto itemPtr = path in _webSocketHandlers; 584 if(itemPtr !is null) 585 throw new Exception("Handler registered on path: " ~ path); 586 587 // WebSocket path should be skipped 588 Router webSocketRouter = _routerManager.register().path(path); 589 version(HUNT_HTTP_DEBUG) tracef("routeid: %d, paths: %s", webSocketRouter.getId(), path); 590 _webSocketHandlers[path] = handler; 591 592 version(HUNT_HTTP_DEBUG) { 593 webSocketRouter.handler( (ctx) { 594 infof("Skip a router path for WebSocket: %s", path); 595 }); 596 } 597 598 return this; 599 } 600 601 Builder enableLocalSessionStore() { 602 LocalHttpSessionHandler sessionHandler = new LocalHttpSessionHandler(_httpOptions); 603 _currentRouter = _routerManager.register(); 604 version(HUNT_HTTP_DEBUG) tracef("routeid: %d, paths: *", _currentRouter.getId()); 605 _currentRouter.path("*").handler(sessionHandler); 606 return this; 607 } 608 609 private void handlerWrap(RouteHandler handler, RoutingContext ctx) { 610 try { 611 if(handler !is null) handler.handle(ctx); 612 } catch (Exception ex) { 613 errorf("route handler exception: %s", ex.msg); 614 version(HUNT_HTTP_DEBUG) error(ex); 615 if(!ctx.isCommitted()) { 616 HttpServerResponse res = ctx.getResponse(); 617 if(res !is null) { 618 res.setStatus(HttpStatus.BAD_REQUEST_400); 619 } 620 ctx.end(ex.msg); 621 } 622 ctx.fail(ex); 623 } 624 } 625 626 private void handlerWrap(RoutingHandler handler, RoutingContext ctx) { 627 try { 628 if(handler !is null) handler(ctx); 629 } catch (Exception ex) { 630 errorf("route handler exception: %s", ex.msg); 631 version(HUNT_DEBUG) error(ex); 632 if(!ctx.isCommitted()) { 633 HttpServerResponse res = ctx.getResponse(); 634 if(res !is null) { 635 res.setStatus(HttpStatus.BAD_REQUEST_400); 636 } 637 ctx.end(ex.msg); 638 } 639 ctx.fail(ex); 640 } 641 } 642 643 Builder onError(BadRequestHandler handler) { 644 _badRequestHandler = handler; 645 return this; 646 } 647 648 private void onError(HttpServerContext context) { 649 if(_badRequestHandler !is null) { 650 _badRequestHandler(context); 651 } 652 653 if(!context.isCommitted()) 654 context.end(); 655 } 656 657 private BadRequestHandler _badRequestHandler; 658 659 660 version(WITH_HUNT_TRACE) { 661 private string _localServiceName; 662 private bool _isB3HeaderRequired = true; 663 // private TracingOptions _tracingOptions; 664 665 Builder localServiceName(string name) { 666 _localServiceName = name; 667 return this; 668 } 669 670 Builder isB3HeaderRequired(bool flag) { 671 _isB3HeaderRequired = flag; 672 return this; 673 } 674 675 private void initializeTracer(HttpRequest request, HttpConnection connection) { 676 if(!isTraceEnabled) return; 677 678 Tracer tracer; 679 string reqPath = request.getURI().getPath(); 680 string b3 = request.header("b3"); 681 if(b3.empty()) { 682 // if(_isB3HeaderRequired) return; 683 version(HUNT_HTTP_DEBUG) { 684 warningf("No B3 headers found for %s, use the defaults now.", reqPath); 685 } 686 tracer = new Tracer(reqPath, KindOfServer); 687 } else { 688 version(HUNT_HTTP_DEBUG) { 689 warningf("initializing tracer for %s, with %s", reqPath, b3); 690 } 691 692 tracer = new Tracer(reqPath, KindOfServer, b3); 693 } 694 695 Span span = tracer.root; 696 span.localEndpoint = _localEndpoint; 697 698 import std.socket; 699 700 // 701 Address remote = connection.getRemoteAddress; 702 EndPoint remoteEndpoint = new EndPoint(); 703 remoteEndpoint.ipv4 = remote.toAddrString(); 704 remoteEndpoint.port = remote.toPortString().to!int; 705 span.remoteEndpoint = remoteEndpoint; 706 // 707 708 span.start(); 709 request.tracer = tracer; 710 } 711 712 private EndPoint _localEndpoint; 713 714 private void endTraceSpan(HttpRequest request, int status, string message = null) { 715 Tracer tracer = request.tracer; 716 if(tracer is null) { 717 version(HUNT_TRACE_DEBUG) warning("no tracer found"); 718 return; 719 } 720 721 HttpURI uri = request.getURI(); 722 string[string] tags; 723 tags[HTTP_HOST] = uri.getHost(); 724 tags[HTTP_URL] = uri.getPathQuery(); 725 tags[HTTP_PATH] = uri.getPath(); 726 tags[HTTP_REQUEST_SIZE] = request.getContentLength().to!string(); 727 tags[HTTP_METHOD] = request.getMethod(); 728 729 Span span = tracer.root; 730 if(span !is null) { 731 tags[HTTP_STATUS_CODE] = to!string(status); 732 traceSpanAfter(span, tags, message); 733 httpSender().sendSpans(span); 734 } 735 } 736 } 737 private ServerHttpHandler buildServerHttpHandler() { 738 ServerHttpHandlerAdapter adapter = new ServerHttpHandlerAdapter(_httpOptions); 739 740 adapter.acceptHttpTunnelConnection((request, response, ot, connection) { 741 version(HUNT_HTTP_DEBUG) info("acceptHttpTunnelConnection!!!"); 742 // if (tunnel !is null) { 743 // tunnel(r, connection); 744 // } 745 return true; 746 }) 747 .headerComplete((request, response, ot, connection) { 748 version(HUNT_HTTP_DEBUG) info("headerComplete!"); 749 HttpServerRequest serverRequest = cast(HttpServerRequest)request; 750 version(WITH_HUNT_TRACE) { 751 initializeTracer(serverRequest, connection); 752 } 753 754 HttpServerContext context = new HttpServerContext( 755 serverRequest, cast(HttpServerResponse)response, 756 ot, cast(HttpServerConnection)connection); 757 758 serverRequest.onHeaderComplete(); 759 dispatchRoute(context); 760 761 // request.setAttachment(context); 762 763 return false; 764 }) 765 .content((buffer, request, response, ot, connection) { 766 version(HUNT_HTTP_DEBUG) tracef("content: %s", BufferUtils.toDetailString(buffer)); 767 768 HttpServerRequest serverRequest = cast(HttpServerRequest)request; 769 serverRequest.onContent(buffer); 770 return false; 771 }) 772 .contentComplete((request, response, ot, connection) { 773 version(HUNT_HTTP_DEBUG) info("contentComplete!"); 774 HttpServerRequest serverRequest = cast(HttpServerRequest)request; 775 serverRequest.onContentComplete(); 776 // HttpServerContext context = cast(HttpServerContext) request.getAttachment(); 777 // context.onContentComplete(); 778 // if (r.contentComplete !is null) { 779 // r.contentComplete(r); 780 // } 781 return false; 782 }) 783 .messageComplete((request, response, ot, connection) { 784 version(HUNT_HTTP_DEBUG) info("messageComplete!"); 785 HttpServerRequest serverRequest = cast(HttpServerRequest)request; 786 serverRequest.onMessageComplete(); 787 // if (!r.getResponse().isAsynchronous()) { 788 // IOUtils.close(r.getResponse()); 789 // } 790 version(HUNT_HTTP_DEBUG) info("end of a request on ", request.getURI().getPath()); 791 version(WITH_HUNT_TRACE) endTraceSpan(request, response.getStatus()); 792 return true; 793 }) 794 .badMessage((status, reason, request, response, ot, connection) { 795 version(HUNT_DEBUG) warningf("badMessage: status=%d reason=%s", status, reason); 796 797 version(HUNT_HTTP_DEBUG) tracef("response is null: %s", response is null); 798 if(response is null) { 799 response = new HttpServerResponse(status, reason, null, -1); 800 } 801 802 HttpServerContext context = new HttpServerContext( 803 cast(HttpServerRequest) request, 804 cast(HttpServerResponse)response, 805 ot, cast(HttpServerConnection)connection); 806 807 version(WITH_HUNT_TRACE) endTraceSpan(request, status, reason); 808 onError(context); 809 }) 810 .earlyEOF((request, response, ot, connection) { 811 version(HUNT_HTTP_DEBUG) info("earlyEOF!"); 812 }); 813 814 return adapter; 815 } 816 817 private WebSocketHandler buildWebSocketHandler() { 818 WebSocketHandlerAdapter adapter = new WebSocketHandlerAdapter(); 819 820 adapter 821 .onAcceptUpgrade((HttpRequest request, HttpResponse response, 822 HttpOutputStream output, HttpConnection connection) { 823 string path = request.getURI().getPath(); 824 WebSocketMessageHandler handler = _webSocketHandlers.get(path, null); 825 if (handler is null) { 826 response.setStatus(HttpStatus.BAD_REQUEST_400); 827 try { 828 output.write(cast(byte[])("No websocket handler for url: " ~ path)); 829 } catch (IOException e) { 830 version(HUNT_DEBUG) errorf("Write http message exception", e.msg); 831 } 832 return false; 833 } else { 834 // return handler.acceptUpgrade(request, response, output, connection); 835 return true; 836 } 837 }) 838 .onOpen((connection) { 839 string path = connection.getPath(); 840 WebSocketMessageHandler handler = _webSocketHandlers.get(path, null); 841 if(handler !is null) 842 handler.onOpen(connection); 843 }) 844 .onFrame((WebSocketFrame frame, WebSocketConnection connection) { 845 string path = connection.getPath(); 846 WebSocketMessageHandler handler = _webSocketHandlers.get(path, null); 847 if(handler is null) { 848 return; 849 } 850 851 switch (frame.getType()) { 852 case WebSocketFrameType.TEXT: 853 handler.onText(connection, (cast(DataFrame) frame).getPayloadAsUTF8()); 854 break; 855 856 case WebSocketFrameType.BINARY: 857 handler.onBinary(connection, frame.getPayload()); 858 break; 859 860 case WebSocketFrameType.CLOSE: 861 handler.onClosed(connection); 862 break; 863 864 case WebSocketFrameType.PING: 865 handler.onPing(connection); 866 break; 867 868 case WebSocketFrameType.PONG: 869 handler.onPong(connection); 870 break; 871 872 case WebSocketFrameType.CONTINUATION: 873 handler.onContinuation(connection, frame.getPayload()); 874 break; 875 876 default: break; 877 } 878 }) 879 .onError((Exception ex, WebSocketConnection connection) { 880 string path = connection.getPath(); 881 WebSocketMessageHandler handler = _webSocketHandlers.get(path, null); 882 if(handler !is null) 883 handler.onError(connection, ex); 884 }); 885 886 return adapter; 887 } 888 889 private void dispatchRoute(HttpServerContext context) { 890 bool isHandled = false; 891 if(_hostManagerGroup.length > 0) { 892 HttpServerRequest request = context.httpRequest(); 893 string host = request.host(); 894 if(!host.empty()) { 895 host = split(host, ":")[0]; 896 auto itemPtr = host in _hostManagerGroup; 897 if(itemPtr !is null) { 898 isHandled = true; 899 itemPtr.accept(context); 900 version(HUNT_HTTP_DEBUG) { 901 tracef("host group, host: %s, path: %s", host, request.path()); 902 } 903 } 904 } 905 } 906 907 if(_pathManagerGroup.length > 0) { 908 HttpServerRequest request = context.httpRequest(); 909 string path = request.originalPath(); 910 // if(path.length > 1 && path[$-1] != '/') { 911 // path ~= "/"; 912 // } 913 914 string groupPath = split(path, "/")[1]; 915 916 // warningf("full path: %s, group path: %s", path, groupPath); 917 auto itemPtr = groupPath in _pathManagerGroup; 918 if(itemPtr !is null) { 919 isHandled = true; 920 path = path[groupPath.length + 1 .. $]; // skip the group path 921 version(HUNT_HTTP_DEBUG) { 922 tracef("full path: %s, group path: %s, new path: %s", request.path(), groupPath, path); 923 } 924 925 request.path = path; // Reset the rquest path 926 itemPtr.accept(context); 927 version(HUNT_HTTP_DEBUG_MORE) { 928 tracef("path group, original path: %s, revised path: %s, group path: %s", 929 request.originalPath(), request.path(), groupPath); 930 } 931 } 932 } 933 934 if(!isHandled) { 935 _routerManager.accept(context); 936 } 937 } 938 939 /* ---------------------------- Options operation --------------------------- */ 940 941 Builder maxRequestSize(int size) { 942 _httpOptions.requestOptions.setMaxRequestSize(size); 943 return this; 944 } 945 946 Builder maxFileSize(int size) { 947 _httpOptions.requestOptions.setMaxFileSize(size); 948 return this; 949 } 950 951 HttpServer build() { 952 953 string basePath = dirName(thisExePath); 954 955 if(!_tlsCertificate.empty()) { 956 PemKeyCertOptions certOptions = new PemKeyCertOptions(buildPath(basePath, _tlsCertificate), 957 buildPath(basePath, _tlsPrivateKey), _tlsCertPassword, _tlsKeyPassword); 958 959 if(!_tlsCaFile.empty()) { 960 certOptions.setCaFile(buildPath(basePath, _tlsCaFile)); 961 certOptions.setCaPassword(_tlsCaPassworld); 962 } 963 _httpOptions.setKeyCertOptions(certOptions); 964 } 965 966 _httpOptions.setSecureConnectionEnabled(_isCertificateAuth); 967 version(WITH_HUNT_TRACE) { 968 _localEndpoint = Span.buildLocalEndPoint(_localServiceName); 969 } 970 971 return new HttpServer(_httpOptions, buildServerHttpHandler(), buildWebSocketHandler()); 972 } 973 } 974 }