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;
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                 log.error("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 }