1 /* 2 * Archttp - A highly performant web framework written in D. 3 * 4 * Copyright (C) 2021 Kerisy.com 5 * 6 * Website: https://www.kerisy.com 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module archttp.Archttp; 13 14 import nbuff; 15 16 import geario.codec; 17 18 import geario.event; 19 import geario.logging.ConsoleLogger; 20 21 import geario.net.TcpListener; 22 import geario.net.TcpStream; 23 24 import geario.util.worker; 25 import geario.util.DateTime; 26 27 import geario.system.Memory : totalCPUs; 28 29 // for gear http 30 import geario.codec.Framed; 31 import archttp.codec.HttpCodec; 32 33 public import archttp.HttpContext; 34 public import archttp.HttpRequest; 35 public import archttp.HttpResponse; 36 public import archttp.HttpStatusCode; 37 public import archttp.HttpContext; 38 public import archttp.HttpMethod; 39 40 import archttp.HttpRequestHandler; 41 import archttp.MiddlewareExecutor; 42 43 import std.socket; 44 import std.experimental.allocator; 45 46 import archttp.Router; 47 48 alias Router!(HttpRequestHandler, HttpRequestMiddlewareHandler) Routing; 49 50 class Archttp 51 { 52 private 53 { 54 uint _ioThreads; 55 uint _workerThreads; 56 57 Address _addr; 58 string _host; 59 ushort _port; 60 61 bool _isRunning = false; 62 63 TcpListener _listener; 64 EventLoop _loop; 65 66 Routing _router; 67 Routing[string] _mountedRouters; 68 ulong _mountedRoutersMaxLength; 69 } 70 71 this(uint ioThreads = totalCPUs, uint workerThreads = 0) 72 { 73 _ioThreads = ioThreads > 1 ? ioThreads : 1; 74 _workerThreads = workerThreads; 75 76 _router = new Routing; 77 _loop = new EventLoop; 78 } 79 80 static Routing newRouter() 81 { 82 return new Routing; 83 } 84 85 Archttp use(HttpRequestMiddlewareHandler handler) 86 { 87 _router.use(handler); 88 return this; 89 } 90 91 Archttp get(string route, HttpRequestHandler handler) 92 { 93 _router.add(route, HttpMethod.GET, handler); 94 return this; 95 } 96 97 Archttp post(string route, HttpRequestHandler handler) 98 { 99 _router.add(route, HttpMethod.POST, handler); 100 return this; 101 } 102 103 Archttp put(string route, HttpRequestHandler handler) 104 { 105 _router.add(route, HttpMethod.PUT, handler); 106 return this; 107 } 108 109 Archttp Delete(string route, HttpRequestHandler handler) 110 { 111 _router.add(route, HttpMethod.DELETE, handler); 112 return this; 113 } 114 115 Archttp use(string path, Routing router) 116 { 117 _mountedRouters[path] = router; 118 router.onMount(this); 119 120 return this; 121 } 122 123 private void handle(HttpContext httpContext) 124 { 125 import std.string : indexOf, stripRight; 126 127 Routing router; 128 string path = httpContext.request().path().length > 1 ? httpContext.request().path().stripRight("/") : httpContext.request().path(); 129 130 // check app mounted routers 131 if (_mountedRouters.length && path.length > 1) 132 { 133 string mountpath = path; 134 135 long index = path[1 .. $].indexOf("/"); 136 137 if (index > 0) 138 { 139 index++; 140 mountpath = path[0 .. index]; 141 } 142 143 router = _mountedRouters.get(mountpath, null); 144 if (router !is null) 145 { 146 if (mountpath.length == path.length) 147 path = "/"; 148 else 149 path = path[index .. $]; 150 } 151 152 // Tracef("mountpath: %s, path: %s", mountpath, path); 153 } 154 155 if (router is null) 156 router = _router; 157 158 // use middlewares for Router 159 MiddlewareExecutor(httpContext.request(), httpContext.response(), router.middlewareHandlers()).execute(); 160 161 auto handler = router.match(path, httpContext.request().method(), httpContext.request().middlewareHandlers, httpContext.request().params); 162 163 if (handler is null) 164 { 165 httpContext.response().code(HttpStatusCode.NOT_FOUND).send("404 Not Found."); 166 } 167 else 168 { 169 // use middlewares for HttpRequestHandler 170 MiddlewareExecutor(httpContext.request(), httpContext.response(), httpContext.request().middlewareHandlers).execute(); 171 handler(httpContext.request(), httpContext.response()); 172 173 if (!httpContext.response().headerSent()) 174 httpContext.response().send(); 175 } 176 177 if (!httpContext.keepAlive()) 178 httpContext.End(); 179 } 180 181 private void accepted(TcpListener listener, TcpStream connection) 182 { 183 auto codec = new HttpCodec(); 184 auto framed = codec.CreateFramed(connection); 185 auto context = new HttpContext(connection, framed); 186 187 framed.OnFrame((HttpRequest request) 188 { 189 context.request(request); 190 handle(context); 191 }); 192 193 connection.Error((IoError error) { 194 Errorf("Error occurred: %d %s", error.errorCode, error.errorMsg); 195 }); 196 } 197 198 Archttp bind(string host, ushort port) 199 { 200 _host = host; 201 _port = port; 202 _addr = new InternetAddress(host, port); 203 204 return this; 205 } 206 207 Archttp bind(ushort port) 208 { 209 return bind("0.0.0.0", port); 210 } 211 212 void listen(ushort port) 213 { 214 this.bind(port); 215 this.run(); 216 } 217 218 private void showHttpServiceInformation() 219 { 220 import std.stdio : writeln; 221 import std.conv : to; 222 223 string text = ` 224 # Archttp service has been started! 225 - IO threads: ` ~ _ioThreads.to!string ~ ` 226 - Listening: ` ~ _addr.toString() ~ "\n"; 227 228 writeln(text); 229 } 230 231 void run() 232 { 233 DateTime.StartClock(); 234 235 showHttpServiceInformation(); 236 237 TcpListener _listener = new TcpListener(_loop, _addr.addressFamily); 238 239 _listener.Threads(_ioThreads); 240 _listener.Bind(_addr).Listen(1024); 241 _listener.Accepted(&accepted); 242 _listener.Start(); 243 244 _isRunning = true; 245 _loop.Run(); 246 } 247 }