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 }