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 }