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 gear.codec;
17 
18 import gear.event;
19 import gear.logging.ConsoleLogger;
20 
21 import gear.net.TcpListener;
22 import gear.net.TcpStream;
23 
24 import gear.system.Memory : totalCPUs;
25 
26 // for gear http
27 import gear.codec.Framed;
28 import archttp.codec.HttpCodec;
29 
30 public import archttp.HttpContext;
31 public import archttp.HttpRequest;
32 public import archttp.HttpResponse;
33 public import archttp.HttpStatusCode;
34 public import archttp.HttpContext;
35 
36 import archttp.HttpRequestHandler;
37 import archttp.Router;
38 
39 import std.socket;
40 import std.experimental.allocator;
41 
42 class Archttp
43 {
44     private
45     {
46         uint _ioThreads;
47         uint _workerThreads;
48 
49         Address _addr;
50         string _host;
51         ushort _port;
52 
53         bool _isRunning = false;
54 
55         // for multi-threaded
56         TcpListener[] _listeners;
57         EventLoopGroup _loopGroup;
58         
59         // for single-thread
60         TcpListener _listener;
61         EventLoop _loop;
62 
63         Router!HttpRequestHandler _router;
64     }
65 
66     this(uint ioThreads = (totalCPUs - 1), uint workerThreads = 0)
67     {
68         _ioThreads = ioThreads > 1 ? ioThreads : 1;
69         _workerThreads = workerThreads;
70         _router = new Router!HttpRequestHandler;
71 
72         if (_ioThreads > 1)
73             _loopGroup = new EventLoopGroup(ioThreads);
74         else
75             _loop = new EventLoop();
76     }
77 
78     Archttp Get(string route, HttpRequestHandler handler)
79     {
80         _router.add(route, HttpMethod.GET, handler);
81         return this;
82     }
83 
84     Archttp Post(string route, HttpRequestHandler handler)
85     {
86         _router.add(route, HttpMethod.POST, handler);
87         return this;
88     }
89 
90     Archttp Put(string route, HttpRequestHandler handler)
91     {
92         _router.add(route, HttpMethod.PUT, handler);
93         return this;
94     }
95 
96     Archttp Delete(string route, HttpRequestHandler handler)
97     {
98         _router.add(route, HttpMethod.DELETE, handler);
99         return this;
100     }
101 
102     private void Handle(HttpContext httpContext)
103     {
104         auto handler = _router.match(httpContext.request().path(), httpContext.request().method(), httpContext.request().parameters);
105 
106         if (handler is null)
107         {
108             httpContext.Send(httpContext.response().status(HttpStatusCode.NOT_FOUND).body("404 Not Found."));
109         }
110         else
111         {
112             handler(httpContext);
113             httpContext.Send(httpContext.response());
114         }
115 
116         httpContext.End();
117     }
118 
119     private TcpListener CreateListener(EventLoop loop)
120     {
121 		TcpListener listener = new TcpListener(loop, _addr.addressFamily);
122 
123         if ( _ioThreads > 0 )
124 		    listener.ReusePort(true);
125 
126 		listener.Bind(_addr).Listen(1024);
127         listener.Accepted(&Accepted);
128 		listener.Start();
129 
130         return listener;
131 	}
132 
133     private void Accepted(TcpListener listener, TcpStream connection)
134     {
135         auto codec = new HttpCodec();
136         auto framed = codec.CreateFramed(connection);
137 
138         framed.OnFrame((HttpRequest request)
139             {
140                 HttpContext ctx = new HttpContext(framed);
141                 ctx.request(request);
142                 Handle(ctx);
143             });
144 
145         connection.Error((IoError error) { 
146                 Errorf("Error occurred: %d  %s", error.errorCode, error.errorMsg); 
147             });
148     }
149 
150     Archttp Bind(string host, ushort port)
151     {
152         _host = host;
153         _port = port;
154         _addr = new InternetAddress(host, port);
155 
156         return this;
157     }
158 
159     Archttp Bind(ushort port)
160     {
161         return Bind("0.0.0.0", port);
162     }
163 
164     void Run()
165     {
166 		Infof("io threads: %d", _ioThreads);
167 		Infof("worker threads: %d", _workerThreads);
168 
169         if (_ioThreads > 1)
170         {
171             _loopGroup.Start();
172 
173             foreach ( loop ; _loopGroup.Loops() )
174             {
175                 _listeners ~= CreateListener(loop);
176             }
177 
178             _isRunning = true;
179         }
180         else
181         {
182             _listener = CreateListener(_loop);
183             _isRunning = true;
184             _loop.Run();
185         }
186     }
187 }