├── LICENSE ├── README.md ├── examples ├── http_all.cpp ├── http_fileserver.cpp ├── http_helloworld.cpp ├── public_html │ ├── a.txt │ ├── b.txt │ ├── index.html │ └── socktest.html └── tcp_echo.cpp └── net11 ├── base64.hpp ├── http.hpp ├── sha1.hpp ├── tcp.hpp └── util.hpp /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jonas Lund 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # net11 2 | Simple embeddable C++11 async tcp,http and websocket serving. 3 | 4 | # What is it? 5 | An easily embeddable C++11 networking library designed to make building application specific 6 | servers a breeze, small yet with a focus on enabling high performance with modern tools. 7 | -------------------------------------------------------------------------------- /examples/http_all.cpp: -------------------------------------------------------------------------------- 1 | // Uncomment the line below to get verbose header information 2 | 3 | //#define NET11_VERBOSE 4 | 5 | #include 6 | #include 7 | 8 | static net11::scheduler sched; 9 | 10 | int main(int argc,char **argv) { 11 | net11::tcp l; 12 | 13 | std::string big; 14 | for (int i = 0;i < 1024 * 1024;i++) { 15 | big.push_back('a' + (i % 25)); 16 | } 17 | 18 | // start listening for http requests 19 | if (net11::http::start_server(l,8080, 20 | // the routing function 21 | [&](net11::http::connection &c)->net11::http::action { 22 | #ifdef NET11_VERBOSE 23 | std::cout< &msg){ 38 | #ifdef NET11_VERBOSE 39 | std::cout<<"WebSockMsg["<send(reply2); 53 | }); 54 | 55 | return true; 56 | })) { 57 | r->set_header("Sec-Websocket-Protocol","beta"); 58 | return r; 59 | } 60 | 61 | if (c.url()=="/echo") { 62 | if (auto r=net11::http::make_websocket(c,16*1024*1024,[](net11::http::websocket &ws,std::vector &msg) { 63 | ws.send(ws.get_input_type(),msg.data(),msg.size()); 64 | return true; 65 | })) { 66 | return r; 67 | } 68 | } 69 | 70 | // change the / url to the indexpage 71 | if (c.url()=="/") { 72 | c.url()="/index.html"; 73 | } 74 | 75 | // if no other urls has hit yet try running a file matching service. 76 | if (auto r=net11::http::match_file(c,"/","public_html/")) { 77 | return r; 78 | } 79 | 80 | // return null for a 404 response 81 | return nullptr; 82 | } 83 | ) 84 | ) { 85 | printf("Error listening\n"); 86 | return -1; 87 | } 88 | 89 | while(l.poll()) { 90 | sched.poll(); 91 | net11::yield(); 92 | } 93 | 94 | return 0; 95 | } 96 | -------------------------------------------------------------------------------- /examples/http_fileserver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc,char **argv) { 5 | net11::tcp tcp; 6 | 7 | if (argc<3) { 8 | printf("Usage: servdir portnum rootdir\n"); 9 | return -1; 10 | } 11 | 12 | if (tcp.listen(atoi(argv[1]),net11::http::make_server( 13 | [argv](net11::http::connection &c) { 14 | if (c.url()=="/") 15 | c.url()="/index.html"; 16 | return net11::http::match_file(c,"/",argv[2]); 17 | } 18 | ))) { 19 | printf("Could not listen on %s\n",argv[1]); 20 | return -2; 21 | } 22 | while(tcp.poll()) { 23 | net11::yield(); 24 | } 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /examples/http_helloworld.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | int main(int argc,char **argv) { 6 | // the tcp object manages all connections 7 | net11::tcp tcp; 8 | 9 | // start listening for http requests on port 8080 10 | if (net11::http::start_server(tcp,8080, 11 | // the routing function translates from an URL to a response 12 | [&](net11::http::connection &c)->net11::http::action { 13 | // right now we only respond with the URL text. 14 | return net11::http::make_text_response(200, "Hello world at "+c.url()); 15 | } 16 | ) 17 | ) { 18 | // should not fail unless port 8080 is busy 19 | printf("Error listening\n"); 20 | return -1; 21 | } 22 | 23 | // request the TCP system to do som work 24 | while(tcp.poll()) { 25 | // then sleep for a short while to make sure we don't eat up all CPU time. 26 | net11::yield(); 27 | } 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /examples/public_html/a.txt: -------------------------------------------------------------------------------- 1 | Hello 2 | -------------------------------------------------------------------------------- /examples/public_html/b.txt: -------------------------------------------------------------------------------- 1 | World 2 | -------------------------------------------------------------------------------- /examples/public_html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | httpsimple service example 4 | 5 | 6 | simple index page from hte httpsimple service
7 | click here for the websocket test 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/public_html/socktest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 56 | 57 | 58 |
59 |
60 | 61 | 64 | 65 | -------------------------------------------------------------------------------- /examples/tcp_echo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Inefficient example of a data sink that reads in data before sending it back 5 | class echosink : public net11::sink { 6 | 7 | // we need to keep a reference to a connection so we know where to send data back 8 | net11::tcp::connection * conn; 9 | 10 | // temp storage vector 11 | std::vector tmp; 12 | public: 13 | echosink(net11::tcp::connection * in_conn) : conn(in_conn) {} 14 | ~echosink()=default; 15 | 16 | // the drain function is invoked on sinks as soon as data has been made available. 17 | bool drain(net11::buffer &buf) { 18 | // keep an ok flag as true as long as we haven't encountered 19 | bool ok=true; 20 | 21 | // now go through the buf as long as we have data to read. 22 | while(buf.usage()) { 23 | // read one byte at a time 24 | char c=buf.consume(); 25 | 26 | // put it in our temp vector 27 | tmp.push_back(c); 28 | } 29 | 30 | // send data back if we got any 31 | if (tmp.size()) { 32 | // let's surround our data with [] characters so we can see the packet extent. 33 | tmp.insert(tmp.begin(),'['); 34 | tmp.push_back(']'); 35 | 36 | // sending data is done by pushing a producer that contains the data we want. 37 | // (A more advanced producer could be written that reads in data from a file for example) 38 | conn->producers.push_back(net11::make_data_producer(tmp)); 39 | 40 | // reset our tmp buf for next time. 41 | tmp.clear(); 42 | } 43 | 44 | // as long as there was no parse problems we want to continue receiving data. 45 | return ok; 46 | } 47 | }; 48 | 49 | int main(int argc,char **argv) { 50 | // the tcp object manages all connections 51 | net11::tcp tcp; 52 | 53 | // start listening for raw requests on some port. 54 | if (tcp.listen(1234, 55 | // this lambda below is invoked each time a connection is received 56 | // it's job is to setup listeners and/or pass data to connecting clients. 57 | [](net11::tcp::connection * conn){ 58 | // in this case we install a small echo sink that sends back slightly modified data. 59 | conn->current_sink=std::make_shared(conn); 60 | } 61 | )) { 62 | printf("Error listening\n"); 63 | return -1; 64 | } 65 | 66 | // request the TCP system to do some work 67 | while(tcp.poll()) { 68 | // then sleep for a short while to make sure we don't eat up all CPU time. 69 | net11::yield(); 70 | } 71 | 72 | return 0; 73 | } 74 | 75 | -------------------------------------------------------------------------------- /net11/base64.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __INCLUDED_NET11_BASE64_H__ 2 | #define __INCLUDED_NET11_BASE64_H__ 3 | 4 | #pragma once 5 | 6 | namespace net11 { 7 | static const char base64chars[65]= 8 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 9 | static const signed char base64lookup[256]={ 10 | // 0 11 | -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, 12 | -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,62,-1,-1,-1,63, 52,53,54,55,56,57,58,59, 60,61,-1,-1,-1,-1,-1,-1, 13 | // 64 14 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, 15,16,17,18,19,20,21,22, 23,24,25,-1,-1,-1,-1,-1, 15 | -1,26,27,28,29,30,31,32, 33,34,35,36,37,38,39,40, 41,42,43,44,45,46,47,48, 49,50,51,-1,-1,-1,-1,-1, 16 | // 128 17 | -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, 18 | -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, 19 | // 192 20 | -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, 21 | -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1 22 | }; 23 | 24 | class base64encoder { 25 | int bits; 26 | int count; 27 | char tmp[4]; 28 | public: 29 | base64encoder():bits(0),count(0) {} 30 | char* encode(char c) { 31 | bits=(bits<<8)|(c&0xff); 32 | count+=8; 33 | int i=0; 34 | while (count>=6) { 35 | count-=6; 36 | tmp[i++]=base64chars[(bits>>count)&0x3f]; 37 | } 38 | tmp[i]=0; 39 | return tmp; 40 | } 41 | char* end() { 42 | int align=count; 43 | int i=0; 44 | while(align) { 45 | if (count) { 46 | tmp[i++]=base64chars[(bits<<(6-count))&0x3f]; 47 | count=0; 48 | } else { 49 | tmp[i++]='='; 50 | } 51 | align=(align-6)&7; 52 | } 53 | tmp[i]=0; 54 | return tmp; 55 | } 56 | // stl compatible helper template 57 | template 58 | void encode(DT &d,ST &s){ 59 | encode(d,s,s.size(),true); 60 | } 61 | // stl compatible helper template 62 | template 63 | void encode(DT &d,ST &s,int count,bool end) { 64 | for (int i=0;iend(); 71 | while(*p) 72 | d.push_back(*(p++)); 73 | } 74 | } 75 | }; 76 | 77 | class base64decoder { 78 | int bits; 79 | int count; 80 | public: 81 | base64decoder():bits(0),count(0) {} 82 | int decode(char i) { 83 | int lu=base64lookup[i&0xff]; 84 | if (lu<0) 85 | return -1; 86 | bits=(bits<<6)|lu; 87 | count+=6; 88 | if (count<8) 89 | return -1; 90 | count-=8; 91 | return (bits>>count)&0xff; 92 | } 93 | template 94 | DT decode(DT &s) { 95 | DT out; 96 | decode(out,s); 97 | return out; 98 | } 99 | // stl compatible helper template 100 | template 101 | void decode(DT &d,ST &s) { 102 | decode(d,s,s.size()); 103 | } 104 | // stl compatible helper template 105 | template 106 | void decode(DT &d,ST &s,int count) { 107 | for (int i=0;idec) 110 | continue; 111 | d.push_back((char)dec); 112 | } 113 | } 114 | }; 115 | }; 116 | 117 | #endif // __INCLUDED_NET11_BASE64_H__ 118 | -------------------------------------------------------------------------------- /net11/http.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "util.hpp" 10 | #include "tcp.hpp" 11 | #include "base64.hpp" 12 | #include "sha1.hpp" 13 | 14 | namespace net11 { 15 | namespace http { 16 | // The HTTP module of net11 is a state-machine built on sinks managing each state. 17 | 18 | // The state machine owner class that becomes the context of a tcp connection 19 | // Killing it will destroy references to the rest of the HTTP system 20 | class connection; 21 | 22 | // When a HTTP request is made the router produces actions on how to proceed 23 | // processing each request. All responses are actions but actions can also be 24 | // to read/process an input before responding (such as PUT,POST,etc) 25 | class actiondata; 26 | class responsedata; 27 | class websocket_response; 28 | 29 | class consume_action; 30 | using action=std::unique_ptr; 31 | using response=std::unique_ptr; 32 | using wsresponse=std::unique_ptr; 33 | 34 | // Websocket support is built in and initiated as a response 35 | class websocket_response; 36 | class websocket_sink; 37 | class websocket; 38 | 39 | // RFC 2616 sec2 Token 40 | bool parse_token_byte(std::string *out,buffer &in) { 41 | switch(in.peek()) { 42 | case -1 : 43 | case '(' : case ')' : case '<' : case '>' : case '@' : 44 | case ',' : case ';' : case ':' : case '\\' : case '\"' : 45 | case '/' : case '[' : case ']' : case '?' : case '=' : 46 | case '{' : case '}' : case ' ' : case '\t' : 47 | return false; 48 | } 49 | char v=in.consume(); 50 | if (out) 51 | out->push_back(v); 52 | return true; 53 | } 54 | 55 | // RFC 2616 sec2 quoted-string (requires 3 consecutive state values) 56 | bool parse_quoted_string_byte(std::string *out,buffer &in, int &state,int base_state=0) { 57 | int lstate=state-base_state; 58 | if (lstate==0) { 59 | switch(in.peek()) { 60 | case -1 : 61 | return true; 62 | case '\"' : 63 | in.consume(); 64 | state++; 65 | return true; 66 | default: 67 | return false; 68 | } 69 | } else if (lstate==1) { 70 | switch(in.peek()) { 71 | case -1 : 72 | return true; 73 | case '\\' : 74 | in.consume(); 75 | state++; 76 | return true; 77 | case '\"' : 78 | in.consume(); 79 | state--; 80 | return false; 81 | } 82 | char ov=in.consume(); 83 | if (out) 84 | out->push_back(ov); 85 | return true; 86 | } else if (lstate==2) { 87 | char ov=in.consume(); 88 | if (out) 89 | out->push_back(ov); 90 | state--; 91 | return true; 92 | } 93 | // this is an error state 94 | abort(); 95 | } 96 | 97 | response make_text_response(int code,const std::string &data); 98 | 99 | // actiondata instances are implemention private and decides how the machine should proceed. 100 | class actiondata { 101 | friend class responsedata; 102 | friend class connection; 103 | friend class consume_action; 104 | actiondata() {} 105 | protected: 106 | virtual bool produce(connection &conn)=0; 107 | public: 108 | virtual ~actiondata() {} 109 | }; 110 | 111 | // responses are a specialization of actions tht produce headers and output content 112 | class responsedata : public actiondata { 113 | friend class connection; 114 | friend class websocket_response; 115 | friend response make_stream_response(int code,std::function prod); 116 | 117 | std::map head; 118 | std::function prod; 119 | 120 | responsedata(){} 121 | protected: 122 | int code; 123 | 124 | // header helper producer (common for most responses) 125 | virtual void produce_headers(connection &conn); 126 | // could be specialized 127 | virtual bool produce(connection &conn); 128 | 129 | public: 130 | void set_header(const std::string &k,const std::string &v) { 131 | head[k]=v; 132 | } 133 | virtual ~responsedata() {} 134 | }; 135 | 136 | // the main HTTP connection managing class 137 | class connection { 138 | friend std::function make_server(const std::function& route); 139 | friend wsresponse make_websocket(connection &c,int max_packet,std::function&)> on_data,std::function on_close); 140 | friend class responsedata; 141 | friend class consume_action; 142 | friend class websocket; 143 | friend class websocket_response; 144 | 145 | // reference to the actual tcp connection that does input/output 146 | tcp::connection *tconn; 147 | 148 | // a weak this-ptr used to provide the shared ptr to things that needs a reference. 149 | std::weak_ptr wthis; 150 | 151 | // the reqlinesink parses request lines (finds method, url and http version data) 152 | std::shared_ptr reqlinesink; 153 | // contain the found data 154 | std::string reqline[3]; 155 | 156 | // decides the next default request sink (could be overridden by HTTP-upgrades) 157 | std::shared_ptr nextreqsink() { 158 | // by default we work with keep-alive connections on HTTP/1.1 159 | // so we proceed to read another request. 160 | if (reqline[2].size() && reqline[2]=="HTTP/1.1") { 161 | return reqlinesink; 162 | } else { 163 | // on older HTTP versions we don't do more requests. 164 | return nullptr; 165 | } 166 | } 167 | 168 | // the header sink is responsible for parsing the http headers received. 169 | std::shared_ptr headsink; 170 | // the headers are stored here 171 | std::map headers; 172 | 173 | std::shared_ptr postchunkedsink; 174 | std::shared_ptr m_chunkedcontentsink; 175 | 176 | // The router function, this function 177 | const std::function router; 178 | 179 | // remenant of older consumption code? 180 | // std::function dataconsumer; 181 | 182 | // a function that is enabled by consume actions to pass over read data to 183 | // end consumers when the server is expecting data. 184 | std::function consume_fun; 185 | 186 | // only produce once per request 187 | bool produced; 188 | // actual function to invoke the requested production 189 | bool produce(action&& act); 190 | 191 | class sizedcontentsink : public sink { 192 | friend class connection; 193 | connection *conn; 194 | size_t clen; 195 | sizedcontentsink(connection *in_conn) : conn(in_conn),clen(0) {} 196 | public: 197 | virtual bool drain(buffer &buf) { 198 | bool rv=true; 199 | int amount=buf.usage()consume_fun) 201 | { 202 | buffer view(buf.to_consume(),amount); 203 | response r=conn->consume_fun(&view); 204 | if (r) 205 | rv=conn->produce((action)std::move(r)); 206 | } 207 | buf.consumed(amount); 208 | clen-=amount; 209 | if (clen==0) { 210 | conn->tconn->current_sink=conn->nextreqsink(); 211 | response r=0; 212 | if (conn->consume_fun) { 213 | r=conn->consume_fun(NULL); 214 | } 215 | bool rv=conn->produce(std::move(r)); 216 | return rv; 217 | } 218 | return rv; 219 | } 220 | }; 221 | 222 | class chunkedcontentsink : public sink { 223 | friend class connection; 224 | int state; 225 | int sstate; 226 | size_t clen; 227 | chunkedcontentsink() : state(0),clen(0),sstate(0) {} 228 | int hexcharvalue(int v) { 229 | if (v>='0' && v<='9') 230 | return v-'0'; 231 | if (v>='a' && v<='f') 232 | return v-'a'+10; 233 | if (v>='A' && v<='F') 234 | return v-'A'+10; 235 | return -1; 236 | } 237 | connection *conn; 238 | chunkedcontentsink(connection *in_conn) : conn(in_conn),state(0),sstate(0),clen(0) {} 239 | public: 240 | virtual bool drain(buffer &buf) { 241 | bool rv=true; 242 | while(buf.usage()) { 243 | int cv=buf.peek(); 244 | switch(state) { 245 | case 0 : // parsing chunk size 246 | { 247 | int hv=hexcharvalue(cv); 248 | if (hv==-1) { // check if it wasn't a hex-char 249 | state=1; // decide on ext or content 250 | continue; 251 | } 252 | buf.consume(); 253 | clen=clen*16 + hv; 254 | continue; 255 | } 256 | case 1 : // post size/ext, decide on next action 257 | buf.consume(); 258 | if (cv==';') { 259 | state=5; // expect ext-name 260 | continue; 261 | } else if (cv==13) { 262 | state=2; // expect LF 263 | continue; 264 | } else return false; // syntax error here 265 | case 2 : // chunk-LF 266 | buf.consume(); // always consume 267 | if (cv!=10) 268 | return false; // syntax error 269 | if (clen==0) { 270 | state=0; sstate=0; clen=0; 271 | conn->tconn->current_sink=conn->postchunkedsink; 272 | return rv; 273 | } else { 274 | state=9; // content 275 | continue; 276 | } 277 | case 3 : // content-CR 278 | buf.consume(); 279 | if (cv!=13) 280 | return false; // syntax error 281 | state=4; 282 | continue; 283 | case 4 : // content-LF 284 | buf.consume(); 285 | if (cv!=10) 286 | return false; // syntax error 287 | state=0; // go back to reading a chunk-size 288 | continue; 289 | case 5 : // ext-name 290 | if (parse_token_byte(NULL,buf)) // consume as much of a name as possible 291 | continue; 292 | // but after the name token we have something else. 293 | cv=buf.consume(); 294 | if (cv=='=') { 295 | // either require a value 296 | state=6; 297 | continue; 298 | } else if (cv==13) { 299 | // or a CRLF to go to the content 300 | state=2; 301 | continue; 302 | } else return false; // syntax error otherwise 303 | case 6 : // start of ext value, could be... 304 | if (cv=='\"') { 305 | state=8; // a quoted string 306 | continue; 307 | } else { 308 | state=7; // or a simple token 309 | continue; 310 | } 311 | case 7 : // parsing token ext value 312 | if (parse_token_byte(NULL,buf)) 313 | continue; 314 | state=1; // decide on what to do next 315 | continue; 316 | case 8 : // parsing quoted string ext value 317 | if (parse_quoted_string_byte(NULL,buf,sstate)) 318 | continue; 319 | state=1; // decide on what to do next 320 | continue; 321 | case 9 : // X bytes of content 322 | { 323 | int amount=buf.usage()consume_fun) 325 | { 326 | buffer view(buf.to_consume(),amount); 327 | response r=conn->consume_fun(&view); 328 | if (r) 329 | rv=conn->produce(std::move(r)); 330 | } 331 | buf.consumed(amount); 332 | clen-=amount; 333 | if (clen==0) { 334 | state=3; 335 | } 336 | continue; 337 | } 338 | default: // illegal state 339 | abort(); 340 | } 341 | } 342 | return rv; 343 | } 344 | }; 345 | 346 | std::shared_ptr m_sizedcontentsink; 347 | 348 | connection( 349 | tcp::connection* tcp_conn, 350 | const std::function& in_router 351 | ):tconn(tcp_conn),router(in_router) { 352 | reqlinesink=std::shared_ptr(new net11::line_parser_sink("\r\n",4096,[this](std::string &l){ 353 | bool in_white=false; 354 | int outidx=0; 355 | reqline[0].resize(0); 356 | reqline[1].resize(0); 357 | reqline[2].resize(0); 358 | headers.clear(); 359 | produced=false; 360 | for (int i=0;i0 && reqline[0].size() && reqline[1].size()) { 373 | this->tconn->current_sink=headsink; 374 | return true; 375 | } else { 376 | // TODO: error handling?! 377 | return false; 378 | } 379 | })); 380 | headsink=std::shared_ptr(new header_parser_sink(128*1024,tolower, 381 | [this](std::string &k,std::string &v){ 382 | #ifdef NET11_VERBOSE 383 | std::cout<<"HeadKey=["<header("transfer-encoding"); 396 | if ( tehead && *tehead!="identity" ) { 397 | // Chunked encoding if transfer-encoding header exists and isn't set to identity 398 | //std::cerr<<"CHUNKED!\n"; 399 | this->tconn->current_sink=m_chunkedcontentsink; 400 | } else if (auto clhead=this->header("content-length")) { 401 | //std::cerr<<"Content LEN\n"; 402 | // only allow content-length influence IFF no transfer-enc is present 403 | net11::trim(*clhead); 404 | size_t clen=std::stoi(*clhead); 405 | m_sizedcontentsink->clen=clen>=0?clen:0; 406 | this->tconn->current_sink=m_sizedcontentsink; 407 | } else { 408 | // this server doesn't handle other kinds of content 409 | this->tconn->current_sink=nextreqsink(); 410 | } 411 | // TODO: urlencodings? 412 | action act=router(*this); 413 | bool rv=produce(std::move(act)); 414 | return rv; 415 | } 416 | )); 417 | m_chunkedcontentsink=std::shared_ptr(new chunkedcontentsink(this)); 418 | m_sizedcontentsink=std::shared_ptr(new sizedcontentsink(this)); 419 | postchunkedsink=std::shared_ptr(new header_parser_sink(128*1024,tolower, 420 | [this](std::string &k,std::string &v) { 421 | headers[k]=v; 422 | return true; 423 | }, 424 | [this](const char *err){ 425 | response r=0; 426 | if (consume_fun) { 427 | r=consume_fun(NULL); 428 | } 429 | this->tconn->current_sink=nextreqsink(); 430 | bool rv=produce(std::move(r)); 431 | return rv; 432 | } 433 | )); 434 | tconn->current_sink=reqlinesink; 435 | } 436 | virtual ~connection() { 437 | //printf("Killed http connection\n"); 438 | } 439 | // small helper for the template expansion of has_headers 440 | bool has_headers() { 441 | return true; 442 | } 443 | 444 | public: 445 | std::string& method() { 446 | return reqline[0]; 447 | } 448 | std::string& url() { 449 | return reqline[1]; // TODO should it be pre-decoded? 450 | } 451 | std::string* header(const char *in_k) { 452 | std::string k(in_k); 453 | return header(k); 454 | } 455 | std::string* header(std::string &k) { 456 | auto f=headers.find(k); 457 | if (f!=headers.end()) 458 | return &f->second; 459 | else 460 | return 0; 461 | } 462 | std::unique_ptr> get_basic_auth() { 463 | //auto bad=std::make_pair(std::string(""),std::string("")); 464 | auto authhead=header("authorization"); 465 | if (!authhead) 466 | return nullptr; 467 | //std::cerr<<"To split:"<<(*authhead)<>( 479 | new std::pair(net11::split(tmp,':')) 480 | ); 481 | } 482 | std::string lowerheader(const char *k) { 483 | std::string ok(k); 484 | return lowerheader(ok); 485 | } 486 | std::string lowerheader(std::string &k) { 487 | std::string out; 488 | auto f=headers.find(k); 489 | if (f!=headers.end()) { 490 | for (int i=0;isecond.size();i++) 491 | out.push_back(tolower(f->second[i])); 492 | } 493 | return out; 494 | } 495 | template 496 | void csvheaders(const std::string &k,T& fn,bool param_tolower=false) { 497 | auto f=headers.find(k); 498 | if (f==headers.end()) 499 | return; 500 | std::string h; 501 | auto & is=f->second; 502 | for(int i=0;i<=is.size();) { 503 | bool end=i==is.size(); 504 | if (!end && !h.size() && isspace(is[i])) { 505 | i++; 506 | continue; 507 | } 508 | if (end || is[i]==',') { 509 | if (h.size()) 510 | fn(h); 511 | h.clear(); 512 | i++; 513 | continue; 514 | } 515 | if (param_tolower) { 516 | h+=tolower(is[i++]); 517 | } else { 518 | h+=is[i++]; 519 | } 520 | } 521 | } 522 | bool has_header(std::string &k) { 523 | return headers.count(k)!=0; 524 | } 525 | bool has_header(const char *p) { 526 | #ifdef NET11_VERBOSE 527 | using namespace std::literals; 528 | std::cout 529 | <<"HasHeader:" 530 | <

" 532 | <<(headers.count(std::string(p))!=0) 533 | <<" " 534 | <<((headers.count(std::string(p))!=0)?("[[["+lowerheader(p)+"]]]"): ""s) 535 | <<"\n"; 536 | #endif 537 | return headers.count(std::string(p))!=0; 538 | } 539 | template 540 | bool has_headers(HEAD head,REST... rest) { 541 | return has_header(head)&&has_headers(rest...); 542 | } 543 | }; 544 | 545 | std::function make_server(const std::function& route) { 546 | // now create a connection spawn function 547 | return [route](net11::tcp::connection* tconn) { 548 | std::shared_ptr conn( 549 | new connection(tconn,route), 550 | [](auto p) { delete p; } 551 | ); 552 | conn->wthis=conn; 553 | tconn->ctx=conn; 554 | }; 555 | }; 556 | 557 | bool start_server(net11::tcp& l,int port,const std::function& route) { 558 | return l.listen(port,make_server(route)); 559 | } 560 | 561 | class consume_action : public action { 562 | protected: 563 | std::function fn; 564 | virtual bool produce(connection &conn) { 565 | //std::cerr<<"Nil production right now from consume action\n"; 566 | conn.consume_fun=fn; 567 | return true; 568 | } 569 | public: 570 | consume_action(const std::function & in_fn) : fn(in_fn) {} 571 | ~consume_action(){} 572 | }; 573 | 574 | response make_stream_response(int code,std::function prod) { 575 | //auto out=new response(); 576 | response out(new responsedata()); //,[](auto p){delete p;} ); 577 | out->code=code; 578 | out->prod=prod; 579 | return out; 580 | } 581 | 582 | response make_blob_response(int code,const std::vector &in_data) { 583 | auto rv=make_stream_response(code,make_data_producer(in_data)); 584 | rv->set_header(std::string("content-length"),std::to_string(in_data.size())); 585 | return rv; 586 | } 587 | response make_text_response(int code,const std::string &in_data) { 588 | auto rv=make_stream_response(code,make_data_producer(in_data)); 589 | rv->set_header(std::string("content-length"),std::to_string(in_data.size())); 590 | return rv; 591 | } 592 | 593 | class websocket : public std::enable_shared_from_this { 594 | friend websocket_sink; 595 | std::weak_ptr conn; 596 | int input_type=-1; 597 | websocket(std::weak_ptr in_conn):conn(in_conn),input_type(-1) {} 598 | public: 599 | std::shared_ptr ctx; // auxillary shared ptr to hold ownership of things to be destroyed with the connection 600 | 601 | int get_input_type() { 602 | return input_type; 603 | } 604 | static const int text=1; 605 | static const int binary=2; 606 | bool send(int ty,const char *data,uint64_t sz) { 607 | if (auto c=conn.lock()) { 608 | int shift; 609 | int firstsize; 610 | if (sz<126) { 611 | shift=0; 612 | firstsize=sz; 613 | } else if (sz<65536) { 614 | shift=16; 615 | firstsize=126; 616 | } else { 617 | shift=64; 618 | firstsize=127; 619 | } 620 | std::shared_ptr b(new buffer(2+(shift/8)+sz)); 621 | b->produce(0x80|ty); 622 | b->produce(firstsize); 623 | while(shift) { 624 | shift-=8; 625 | b->produce( (sz>>shift)&0xff ); 626 | } 627 | std::memcpy(b->to_produce(),data,sz); 628 | b->produced(sz); 629 | c->tconn->producers.push_back([b](buffer& out) { 630 | out.produce(*b); 631 | return 0!=b->usage(); 632 | }); 633 | return true; 634 | } else { 635 | // Sending to killed connection! 636 | return false; 637 | } 638 | } 639 | bool send(const std::string& data) { 640 | return send(text,data.data(),data.size()); 641 | } 642 | bool send(const std::vector& data) { 643 | return send(binary,data.data(),data.size()); 644 | } 645 | }; 646 | 647 | class websocket_sink : public sink { 648 | friend websocket_response; 649 | enum wsstate { 650 | firstbyte=0, 651 | sizebyte, 652 | sizeextra, 653 | maskbytes, 654 | bodybytes 655 | }; 656 | wsstate state; 657 | uint8_t info; 658 | uint64_t count; 659 | uint64_t size; 660 | bool want_mask; 661 | uint32_t mask; 662 | //std::vector data; 663 | 664 | char control_data[125]; 665 | 666 | bool endit() { 667 | bool fin=info&0x80; 668 | if ((info&0xf)<=2) { 669 | int type=info&0xf; 670 | if (websock->input_type==-1) { 671 | if (type==0) 672 | return false; // must get a new type! 673 | websock->input_type=type; 674 | } else { 675 | if (type!=0) 676 | return false; // already a set type! 677 | } 678 | if (!packet_end(fin,info&0xf)) 679 | return false; 680 | if (fin) 681 | websock->input_type=-1; 682 | } else { 683 | // processing for non-data packets. 684 | if ((info&0xf)==8) { 685 | websock->send(8,control_data,0); 686 | return false; 687 | } else if ((info&0xf)==9) { 688 | // got a ping, need to do pong 689 | websock->send(10,control_data,size); 690 | } else if ((info&0xf)==10) { 691 | // 10, we ignore pongs.. 692 | } else { 693 | // unknown packet type 694 | return false; 695 | } 696 | } 697 | state=firstbyte; 698 | return true; 699 | } 700 | bool advance() { 701 | count=0; 702 | if (state==sizebyte || state==sizeextra) { 703 | if (want_mask) { 704 | state=maskbytes; 705 | return true; 706 | } 707 | } 708 | state=bodybytes; 709 | bool ok=true; 710 | if (info&0x70) 711 | return false; // Not allowed to use reserved bits 712 | if (!(info&0x80) && ((info&0xf)>7)) 713 | return false; 714 | if ((info&0xf)<=2) { 715 | ok&=packet_start(info&0x80,info&0xf,size); 716 | } else { 717 | if (size>sizeof(control_data)) { 718 | return false; 719 | } 720 | switch(info&0xf) { 721 | case 8 : // close 722 | ok=true; 723 | break; 724 | case 9 : case 10 : // ping-pong 725 | ok=true; 726 | break; 727 | default: 728 | ok=false; // do not know how to handle packet 729 | break; 730 | } 731 | } 732 | if (size==0) { 733 | return ok&endit(); 734 | } else { 735 | return ok; 736 | } 737 | } 738 | std::shared_ptr websock; 739 | protected: 740 | websocket_sink(std::weak_ptr conn):state(firstbyte),websock(new websocket(conn)) {} 741 | public: 742 | virtual bool drain(buffer &buf) { 743 | while(buf.usage()) { 744 | switch(state) { 745 | case firstbyte : 746 | info=buf.consume(); 747 | state=sizebyte; 748 | mask=0; 749 | count=0; 750 | continue; 751 | case sizebyte : 752 | { 753 | int tmp=buf.consume(); 754 | want_mask=tmp&0x80; 755 | if ((tmp&0x7f)<126) { 756 | size=tmp&0x7f; 757 | if (!advance()) 758 | return false; 759 | continue; 760 | } else { 761 | if((tmp&0x7f)==126) { 762 | count=6; // skip "initial" 6 bytes since we only want a 2 byte size 763 | } else { 764 | count=0; 765 | } 766 | size=0; 767 | state=sizeextra; 768 | continue; 769 | } 770 | } 771 | case sizeextra: 772 | size=(size<<8)|(buf.consume()&0xff); 773 | if ((++count)==8) { 774 | if (!advance()) 775 | return false; 776 | } 777 | continue; 778 | case maskbytes : 779 | mask=(mask<<8)|(buf.consume()&0xff); 780 | if ((++count)==4) { 781 | if (!advance()) 782 | return false; 783 | } 784 | continue; 785 | case bodybytes : 786 | { 787 | int b=(buf.consume()^( mask >> ( 8*(3^(count&3))) ))&0xff; 788 | if ((info&0xf)<=2) { 789 | packet_data(b); 790 | } else { 791 | control_data[count]=b; 792 | } 793 | if (++count==size) { 794 | if (!endit()) 795 | return false; 796 | } 797 | continue; 798 | } 799 | } 800 | } 801 | return true; 802 | } 803 | std::weak_ptr get_websocket() { 804 | return websock; 805 | } 806 | virtual bool packet_start(bool fin,int type,uint64_t size)=0; 807 | virtual void packet_data(char c)=0; 808 | virtual bool packet_end(bool fin,int type)=0; 809 | virtual void websocket_closing() {} 810 | }; 811 | 812 | class websocket_response : public responsedata { 813 | friend wsresponse make_websocket(connection &c,std::shared_ptr wssink); 814 | std::shared_ptr sink; 815 | websocket_response(std::shared_ptr in_sink):sink(in_sink) { 816 | code=101; 817 | } 818 | bool produce(connection &conn) { 819 | produce_headers(conn); 820 | conn.tconn->current_sink=sink; 821 | return true; 822 | } 823 | public: 824 | std::weak_ptr get_websocket() { 825 | return sink->websock; 826 | } 827 | }; 828 | 829 | wsresponse make_websocket(connection &c,std::shared_ptr wssink) { 830 | bool has_heads=c.has_headers( 831 | "connection", 832 | "upgrade", 833 | //"origin", 834 | "sec-websocket-version", 835 | "sec-websocket-key"); 836 | //printf("Has heads?:%d\n",has_heads); 837 | if (!has_heads) 838 | return 0; 839 | bool has_upgrade=false; 840 | c.csvheaders("connection",[&](auto& hval){ if (hval=="upgrade") has_upgrade=true; },true); 841 | if (!has_upgrade) { 842 | return 0; 843 | } 844 | if (c.lowerheader("upgrade")!="websocket") { 845 | return 0; 846 | } 847 | if (*c.header("sec-websocket-version")!="13") { 848 | return 0; 849 | } 850 | // hash the key and guid to know the response hash 851 | net11::sha1 s; 852 | char hash[20]; 853 | s.addbytes(*c.header("sec-websocket-key")); 854 | s.addbytes("258EAFA5-E914-47DA-95CA-C5AB0DC85B11",36); 855 | s.digest(hash); 856 | // base64 encode the hash into a response token 857 | std::string rkey; 858 | net11::base64encoder().encode(rkey,hash,20,true); 859 | // now setup the response! 860 | websocket_response *ws=new websocket_response(wssink); 861 | ws->set_header("Upgrade","websocket"); 862 | ws->set_header("Connection","upgrade"); 863 | ws->set_header("Sec-Websocket-Accept",rkey); 864 | //ws.set_header("sec-websocket-protocol") // proto?! 865 | return wsresponse(ws); 866 | } 867 | 868 | wsresponse make_websocket(connection &c,int max_packet,std::function&)> on_data,std::function on_close=std::function()) { 869 | struct websocket_packet_sink : public websocket_sink { 870 | int max_packet; 871 | std::vector data; 872 | std::function&)> on_data; 873 | std::function on_close; 874 | websocket_packet_sink( 875 | std::weak_ptr c, 876 | int in_max_packet, 877 | std::function&)> in_on_data, 878 | std::function in_on_close) 879 | :websocket_sink(c),max_packet(in_max_packet),on_data(in_on_data),on_close(in_on_close) { 880 | } 881 | bool packet_start(bool fin,int type,uint64_t size) { 882 | if ((data.size()+size)>max_packet) 883 | return false; 884 | return true; 885 | } 886 | void packet_data(char b) { 887 | data.push_back(b); 888 | } 889 | bool packet_end(bool fin,int type) { 890 | std::shared_ptr ws(get_websocket()); 891 | bool ok=true; 892 | if (fin) { 893 | on_data(*ws,data); 894 | data.clear(); 895 | } 896 | return ok; 897 | } 898 | void websocket_closing() { 899 | if (on_close) { 900 | on_close(); 901 | on_close=std::function(); 902 | } 903 | } 904 | }; 905 | //std::shared_ptr sc=c.wthis; //=std::static_pointer_cast(c.shared_from_this()); 906 | std::shared_ptr sink( 907 | new websocket_packet_sink(c.wthis,max_packet,on_data,on_close) 908 | ); 909 | return make_websocket(c,sink); 910 | } 911 | 912 | response match_file(connection &c,std::string urlprefix,std::string filepath) { 913 | if (0!=c.url().find(urlprefix)) 914 | return 0; // not matching the prefix. 915 | std::string checked=c.url().substr(urlprefix.size()); 916 | struct stat stbuf; 917 | int last='/'; 918 | int end=checked.size(); 919 | for (int i=0;i fp(new fh(f)); 965 | 966 | auto out=make_stream_response(200,[fp](buffer &ob) { 967 | int osz=ob.usage(); 968 | int tr=ob.compact(); 969 | int rc=fread(ob.to_produce(),1,tr,fp->f); 970 | if (rc>=0) { 971 | ob.produced(rc); 972 | } 973 | if (rc<=0) { 974 | //if (feof(fp->f)) 975 | return false; // always stop sending on error 976 | } 977 | return true; 978 | }); 979 | out->set_header("content-length",std::to_string(stbuf.st_size)); 980 | return out; 981 | } 982 | 983 | 984 | inline bool connection::produce(action&& act) { 985 | if (produced) 986 | return true; 987 | consume_fun=nullptr; 988 | if (!act) { 989 | //std::map head; //{{"connection","close"}}; 990 | std::string msg="Error 404, "+url()+" not found"; 991 | act=(action)make_text_response(404,msg); 992 | } 993 | // TODO: make sure that connection lines are there? 994 | bool rv=act->produce(*this); 995 | // delete act; no longer needed.. 996 | return rv; 997 | } 998 | 999 | inline void responsedata::produce_headers(connection &conn) { 1000 | conn.produced=true; 1001 | std::string resline="HTTP/1.1 "+std::to_string(code)+" OK\r\n"; 1002 | for(auto kv:head) { 1003 | resline=resline+kv.first+": "+kv.second+"\r\n"; 1004 | } 1005 | resline+="\r\n"; 1006 | conn.tconn->producers.push_back(make_data_producer(resline)); 1007 | } 1008 | 1009 | inline bool responsedata::produce(connection &conn) { 1010 | produce_headers(conn); 1011 | if (head.count("content-length")) { 1012 | conn.tconn->producers.push_back(prod); 1013 | } else { 1014 | // TODO: implement chunked responses? 1015 | abort(); //conn.producers.push_back([this]( 1016 | } 1017 | return true; 1018 | } 1019 | 1020 | 1021 | } 1022 | } 1023 | 1024 | -------------------------------------------------------------------------------- /net11/sha1.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __INCLUDED_NET11_SHA1_HPP__ 2 | #define __INCLUDED_NET11_SHA1_HPP__ 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | namespace net11 { 9 | 10 | // fast C++ sha1 class 11 | // most calculations is done with 32bit integers directly on the fly to 12 | // minimize the number of instructions and data copies. 13 | class sha1 { 14 | // accumulator used for odd bytes 15 | uint32_t acc; 16 | // input word storage 17 | uint32_t w[80]; 18 | // hash state 19 | uint32_t h[5]; 20 | // length of hash in BYTES 21 | uint64_t ml; 22 | 23 | // rotation utility routine. 24 | static inline uint32_t rol(uint32_t v,int sh) { 25 | return (v<>(32-sh)); 26 | } 27 | 28 | // do a 512 byte block 29 | void block() { 30 | // stretch out our input words 31 | for (int i=16;i<80;i++) { 32 | w[i]=rol(w[i-3]^w[i-8]^w[i-14]^w[i-16],1); 33 | } 34 | // initialize local state for rounds 35 | uint32_t 36 | a=h[0], 37 | b=h[1], 38 | c=h[2], 39 | d=h[3], 40 | e=h[4]; 41 | 42 | // setup macros to do 20 rounds at a time 43 | #define NET11_SHA1_ITER(i,code) { \ 44 | code; \ 45 | uint32_t tmp=rol(a,5) + f + e + k + w[i]; \ 46 | e = d; d = c; c = rol(b,30); b=a; a=tmp; } 47 | 48 | #define NET11_SHA1_ITER4(i,c) \ 49 | NET11_SHA1_ITER(i,c) NET11_SHA1_ITER(i+1,c) \ 50 | NET11_SHA1_ITER(i+2,c) NET11_SHA1_ITER(i+3,c) 51 | #define NET11_SHA1_ITER20(i,c) \ 52 | NET11_SHA1_ITER4(i,c) NET11_SHA1_ITER4(i+4,c) NET11_SHA1_ITER4(i+8,c) \ 53 | NET11_SHA1_ITER4(i+12,c) NET11_SHA1_ITER4(i+16,c) 54 | 55 | // rounds 0-19 with their specific parameters 56 | NET11_SHA1_ITER20(0, uint32_t f=(b&c)^((~b)&d); uint32_t k=0x5a827999) 57 | // rounds 20-39 with their specific parameters 58 | NET11_SHA1_ITER20(20,uint32_t f=b^c^d; uint32_t k=0x6ed9eba1) 59 | // rounds 40-59 with their specific parameters 60 | NET11_SHA1_ITER20(40,uint32_t f=(b&c)^(b&d)^(c&d); uint32_t k=0x8f1bbcdc) 61 | // rounds 60-79 with their specific parameters 62 | NET11_SHA1_ITER20(60,uint32_t f=b^c^d; uint32_t k=0xca62c1d6) 63 | 64 | #undef NET11_SHA1_ITER20 65 | #undef NET11_SHA1_ITER4 66 | #undef NET11_SHA1_ITER 67 | 68 | // update the main state with the local words 69 | h[0]+=a; 70 | h[1]+=b; 71 | h[2]+=c; 72 | h[3]+=d; 73 | h[4]+=e; 74 | } 75 | public: 76 | // initialize default parameters 77 | sha1() { 78 | reinit(); 79 | } 80 | // utility constructors to hash some input data directly. 81 | template 82 | sha1(const T d,int count) { 83 | reinit(); 84 | addbytes(d,count); 85 | } 86 | template 87 | sha1(T &in) { 88 | reinit(); 89 | addbytes(in); 90 | } 91 | 92 | void reinit() { 93 | acc = 0; 94 | ml = 0; 95 | h[0]=0x67452301; 96 | h[1]=0xefcdab89; 97 | h[2]=0x98badcfe; 98 | h[3]=0x10325476; 99 | h[4]=0xc3d2e1f0; 100 | } 101 | 102 | // add a single byte to the hash calculations 103 | inline void addbyte(uint8_t b) { 104 | acc = (acc << 8) | (b & 0xff); 105 | // note ml preincrement below, correct placement needed for comparison and performance. 106 | if (0 == (++ml & 3)) { 107 | int wi = ((ml-1) >> 2) & 0xf; 108 | w[wi] = acc; 109 | if (0 == (ml & 0x3f)) { 110 | // 512 bits added, do block 111 | block(); 112 | } 113 | } 114 | } 115 | // function to add a counted number of bytes quickly. 116 | template 117 | void addbytes(const T b,int count) { 118 | //uint8_t *b=(uint8_t*)b; 119 | int i = 0; 120 | // pre-roll out any odd bytes 121 | while (i < count && (ml & 3)) 122 | addbyte(b[i++]); 123 | // calculate number of fast iters 124 | int fc = count>>2; 125 | // now do the direct word iters 126 | while (fc--) { 127 | // set the word 128 | w[(ml >> 2) & 0xf] = (((uint8_t)b[i]) << 24) | (((uint8_t)b[i + 1]) << 16) | (((uint8_t)b[i + 2]) << 8) | ((uint8_t)b[i + 3]); 129 | // increment size and dest ptr 130 | ml += 4; 131 | i += 4; 132 | // if we've collected 64 bytes for a block, calculate it. 133 | if (0 == (ml & 0x3f)) 134 | block(); 135 | } 136 | // and finally the end roll for any trailing bytes 137 | while (i < count) 138 | addbyte(b[i++]); 139 | } 140 | // utility proxies to unpack bytes from a stl like container 141 | template 142 | void addbytes(T &cnt) { 143 | addbytes(cnt.data(),cnt.size()); 144 | } 145 | // outputs a digest 146 | void digest(char *o) { 147 | uint64_t oml = ml << 3; // this needs to be in bits. 148 | addbyte(0x80); 149 | while((ml&0x3f)!=56) { 150 | addbyte(0); 151 | } 152 | for (int i=56;i>=0;i-=8) 153 | addbyte((uint8_t)(oml>>i)); 154 | for (int i=0;i<5;i++) { 155 | uint32_t t=h[i]; 156 | o[(i<<2)+0]=(t>>24)&0xff; 157 | o[(i<<2)+1]=(t>>16)&0xff; 158 | o[(i<<2)+2]=(t>>8)&0xff; 159 | o[(i<<2)+3]=(t)&0xff; 160 | } 161 | } 162 | template 163 | T digest() { 164 | T t; 165 | char tmp[20]; 166 | digest(tmp); 167 | for (int i=0;i 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | //#include 13 | 14 | //#define NET11_VERBOSE 15 | 16 | #ifdef _MSC_VER 17 | 18 | //#define NET11_OVERLAPPED 19 | #ifdef NET11_VERBOSE 20 | #define NET11_TCP_LOG(...) fprintf(stderr,__VA_ARGS__); 21 | #else 22 | #define NET11_TCP_LOG(...) 23 | #endif 24 | 25 | // remove problematic windows min / max macros 26 | #ifndef NOMINMAX 27 | #define NOMINMAX 28 | #endif 29 | 30 | #include 31 | #include 32 | //#include 33 | //#pragma comment(lib,"wsock32.lib") 34 | #pragma comment(lib,"ws2_32.lib") 35 | #else 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #define closesocket(x) close(x) 46 | #endif 47 | 48 | // include net11 utilities 49 | #include "util.hpp" 50 | 51 | namespace net11 { 52 | // the tcp class is a container for listeners and connections 53 | class tcp; 54 | // a single connection, protocols should inherit this class 55 | class connection; 56 | 57 | class tcp { 58 | public: 59 | class connection; 60 | private: 61 | std::vector>> listeners; 62 | public: 63 | class connection { 64 | friend tcp; 65 | int sock; 66 | //std::shared_ptr conn; 67 | bool want_input; 68 | buffer input; 69 | buffer output; 70 | #ifdef NET11_OVERLAPPED 71 | bool ol_input_pending = false; 72 | bool ol_output_pending = false; 73 | WSABUF wsa_input; 74 | WSAOVERLAPPED overlapped_input; 75 | WSABUF wsa_output; 76 | WSAOVERLAPPED overlapped_output; 77 | #endif 78 | connection(int insize, int outsize) :input(insize), output(outsize) {} 79 | //public: 80 | ~connection() { 81 | NET11_TCP_LOG("Socket %x killed\n", sock); 82 | } 83 | struct deleter { 84 | void operator()(connection* p) { 85 | delete p; 86 | } 87 | }; 88 | public: 89 | std::shared_ptr current_sink; 90 | std::function terminate; 91 | std::vector > producers; 92 | std::shared_ptr ctx; 93 | }; 94 | private: 95 | std::vector> conns; 96 | int input_buffer_size; 97 | int output_buffer_size; 98 | 99 | static void set_non_blocking_socket(int socket) { 100 | #ifdef _MSC_VER 101 | unsigned long nbl = 1; 102 | ioctlsocket(socket, FIONBIO, &nbl); 103 | #else 104 | int flags = fcntl(socket, F_GETFL); 105 | flags |= O_NONBLOCK; 106 | fcntl(socket, F_SETFL, flags); 107 | #endif 108 | } 109 | 110 | static bool was_block() { 111 | #ifdef _MSC_VER 112 | return (WSAGetLastError() == WSAEWOULDBLOCK); 113 | #else 114 | return (errno == EAGAIN); 115 | #endif 116 | } 117 | 118 | #ifdef NET11_OVERLAPPED 119 | static void CALLBACK completion_input(DWORD err, DWORD count, WSAOVERLAPPED *ol, DWORD flags) { 120 | tcpconn *c = (tcpconn*)ol->hEvent; 121 | if (err == 0) { 122 | NET11_TCP_LOG("Pending read completed %x %d (overlap:%p)\n", c->sock, count,ol); 123 | if (count) { 124 | c->input.produced(count); 125 | c->ol_input_pending = false; 126 | } else { 127 | // a nil count means that we've finished the data stream, let's just ignore it to avoid touching sensitive data. 128 | c->want_input = false; 129 | } 130 | } else if (c) { 131 | NET11_TCP_LOG("Error on pending read %x\n", c->sock); 132 | c->want_input = false; 133 | c->ol_input_pending = false; 134 | } else { 135 | NET11_TCP_LOG("Error with invalid handle?\n"); 136 | } 137 | } 138 | 139 | static void CALLBACK completion_output(DWORD err, DWORD count, WSAOVERLAPPED *ol, DWORD flags) { 140 | tcpconn *c = (tcpconn*)ol->hEvent; 141 | if (err == 0) { 142 | NET11_TCP_LOG("Ok on pending write %x %d (overlap:%p)\n", c->sock,count,ol); 143 | if (c->ol_output_pending && count) { 144 | c->output.consumed(count); 145 | } 146 | //if (count) { 147 | // c->output.consumed(count); 148 | //} else { 149 | // // flag problem on output write 150 | //} 151 | c->ol_output_pending = false; 152 | } else if (c) { 153 | NET11_TCP_LOG("Error on pending write %x\n", c->sock); 154 | c->ol_output_pending = false; 155 | } else { 156 | NET11_TCP_LOG("Error on invalid handle for write completion\n"); 157 | } 158 | } 159 | 160 | bool work_conn(tcpconn &c) { 161 | int fill_count = 0; 162 | while (c.want_input && fill_count < 10) { 163 | // don't try to parse and produce more data if we have too much pending output. 164 | if (c.conn->producers.size() > 5) 165 | break; 166 | // as long as we still have input we will try to parse 167 | if (c.input.usage()) { 168 | NET11_TCP_LOG("Processing data on socket %x\n", c.sock); 169 | c.want_input = c.conn->current_sink->drain(c.input); 170 | c.want_input &= bool(c.conn->current_sink); 171 | continue; 172 | } 173 | // already an overlapped read in progress. 174 | if (c.ol_input_pending) 175 | break; 176 | // no input to read and no operation in progress, try or schedule a read. 177 | { 178 | memset(&c.overlapped_input, 0, sizeof(c.overlapped_input)); 179 | c.overlapped_input.hEvent = &c; 180 | DWORD numRecv; 181 | DWORD flags = 0; 182 | c.wsa_input.len = c.input.compact(); 183 | c.wsa_input.buf = c.input.to_produce(); 184 | 185 | NET11_TCP_LOG("WSA Read on overlapped %p\n", &c.overlapped_input); 186 | int rr = WSARecv(c.sock, &(c.wsa_input), 1, &numRecv, &flags, &c.overlapped_input, completion_input); 187 | if (rr == 0) { 188 | // immediate completion 189 | NET11_TCP_LOG("Read immediate completion on %x %d\n", c.sock, numRecv); 190 | c.input.produced(numRecv); 191 | fill_count++; 192 | continue; 193 | } else if (rr == SOCKET_ERROR) { 194 | if (WSAGetLastError() == WSA_IO_PENDING) { 195 | // ok, pending read. 196 | c.ol_input_pending = true; 197 | NET11_TCP_LOG("Pending read on %x\n", c.sock); 198 | break; 199 | } else { 200 | #ifdef NET11_VERBOSE 201 | char *msg; 202 | FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, nullptr, WSAGetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msg, 0, nullptr); 203 | fprintf(stderr, "Unhandled error on read %s\n", msg); 204 | LocalFree(msg); 205 | #endif 206 | // some error!! 207 | return false; 208 | } 209 | } else abort(); // invalid state 210 | } 211 | } 212 | // don't do any output processing while waiting for old data to be sent. 213 | while (!c.ol_output_pending) { 214 | //int precount= 215 | c.output.compact(); 216 | // first fill the output buffer as much as possible. 217 | while (c.conn->producers.size()) { 218 | int preuse = c.output.total_avail(); 219 | if (!c.conn->producers.front()(c.output)) { 220 | // producer finished, remove it. 221 | c.conn->producers.erase(c.conn->producers.begin()); 222 | } else if (preuse == c.output.total_avail()) { 223 | // no data was generated. 224 | break; 225 | } 226 | } 227 | if (!c.output.usage()) 228 | break; 229 | // then prepare a send 230 | memset(&c.overlapped_output, 0, sizeof(c.overlapped_output)); 231 | c.overlapped_output.hEvent = &c; 232 | c.wsa_output.len = c.output.usage(); 233 | c.wsa_output.buf = c.output.to_consume(); 234 | DWORD outbytecount; 235 | NET11_TCP_LOG("** WSASend invoked %p\n",&c.overlapped_output); 236 | int wrv = WSASend(c.sock, &c.wsa_output, 1, &outbytecount, 0, &c.overlapped_output, completion_output); 237 | if (wrv == 0) { 238 | NET11_TCP_LOG("Wrote directly on socket %x %d\n", c.sock,outbytecount); 239 | c.output.consumed(outbytecount); 240 | continue; 241 | } else if (wrv==SOCKET_ERROR) { 242 | if (WSAGetLastError() == WSA_IO_PENDING) { 243 | NET11_TCP_LOG("Write pending on socket %x\n", c.sock); 244 | c.ol_output_pending = true; 245 | break; 246 | } else { 247 | #ifdef NET11_VERBOSE 248 | char *msg; 249 | FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, nullptr, WSAGetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msg, 0, nullptr); 250 | fprintf(stderr, "Unhandled error on write %s\n", msg); 251 | LocalFree(msg); 252 | #endif 253 | // some error!! 254 | return false; 255 | } 256 | } else abort(); 257 | } 258 | return c.want_input || c.output.usage() || c.conn->producers.size() || c.ol_output_pending || c.ol_input_pending; 259 | } 260 | 261 | #else 262 | bool work_conn(connection &c) { 263 | int fill_count = 0; 264 | while (c.want_input && fill_count<10) { 265 | // process events as long as we have data and don't have multiple 266 | // producers on queue to avoid denial of service scenarios where 267 | // the producers are too slow to drain before the sink has read. 268 | // Note: the sink should be called first since was_block below will break the loop on block. 269 | if (c.input.usage() && c.producers.size() <= 1) { 270 | c.want_input = c.current_sink->drain(c.input); 271 | c.want_input &= bool(c.current_sink); 272 | continue; 273 | } 274 | // try to fill up the buffer as much as possible. 275 | if (c.input.total_avail()) { 276 | int avail = c.input.compact(); 277 | int rc = recv(c.sock, c.input.to_produce(), avail, 0); 278 | if (rc<0) { 279 | if (was_block()) { 280 | //printf("WOULD BLOCK\n"); 281 | break; 282 | } else { 283 | // not a blocking error! 284 | return false; 285 | } 286 | } else if (rc>0) { 287 | c.input.produced(rc); 288 | fill_count++; 289 | continue; 290 | } else { 291 | // 0 on recv, closed sock; 292 | return false; 293 | } 294 | } 295 | break; 296 | } 297 | bool eop = false; 298 | while (c.output.usage() || c.producers.size()) { 299 | if (c.output.usage()) { 300 | #ifdef _MSC_VER 301 | const int send_flags = 0; 302 | #else 303 | const int send_flags = MSG_NOSIGNAL; 304 | #endif 305 | int rc = send(c.sock, c.output.to_consume(), c.output.usage(), send_flags); 306 | if (rc<0) { 307 | if (!was_block()) { 308 | // error other than wouldblock 309 | return false; 310 | } 311 | } else if (rc>0) { 312 | //bool brk=rc!=c.output.usage(); 313 | c.output.consumed(rc); 314 | //c.output.erase(c.output.begin(),c.output.begin()+rc); 315 | if (c.output.usage()) 316 | break; // could not take all data, do more later 317 | } 318 | } 319 | if (eop) 320 | break; 321 | while (c.output.total_avail() && c.producers.size()) { 322 | int preuse = c.output.total_avail(); 323 | if (!c.producers.front()(c.output)) { 324 | // producer finished, remove it. 325 | c.producers.erase(c.producers.begin()); 326 | } else if (preuse == c.output.total_avail()) { 327 | // no data was generated. 328 | eop = true; 329 | break; 330 | } 331 | } 332 | } 333 | return c.want_input || c.output.usage() || c.producers.size(); 334 | } 335 | #endif 336 | 337 | 338 | public: 339 | tcp(int in_input_buffer_size=4096,int in_out_buffer_size=4096):input_buffer_size(in_input_buffer_size),output_buffer_size(in_out_buffer_size) { 340 | #ifdef _MSC_VER 341 | WSADATA wsa_data; 342 | if (WSAStartup(MAKEWORD(1,0),&wsa_data)) { 343 | throw new std::exception("WSAStartup problem"); 344 | } 345 | #endif 346 | } 347 | ~tcp() { 348 | #ifdef _MSC_VER 349 | //if (WSACleanup()) { 350 | //std::cerr<<"WSACleanup shutdown error"<( 373 | conns.emplace_back( 374 | new connection(input_buffer_size,output_buffer_size) 375 | //,[](auto p) { delete p; } 376 | ); 377 | conns.back()->sock=newsock; 378 | conns.back()->want_input=true; 379 | 380 | //conns.back()->conn=l.second()->shared_from_this(); 381 | //conns.back()->conn.reset(l.second()); 382 | l.second(conns.back().get()); 383 | 384 | continue; 385 | } else { 386 | break; 387 | } 388 | } 389 | } 390 | // now see if we have new data 391 | // TODO: rewrite this to use kevent,etc 392 | conns.erase( 393 | std::remove_if( 394 | conns.begin(), 395 | conns.end(), 396 | [this](auto & c) { 397 | //printf("running conn:%p\n",&c); 398 | if (!work_conn(*c)) { 399 | NET11_TCP_LOG("Wanting to remove conn %x!\n",c->sock); 400 | closesocket(c->sock); 401 | return true; 402 | } else { 403 | return false; 404 | } 405 | } 406 | ), 407 | conns.end() 408 | ); 409 | return true; // change this somehow? 410 | } 411 | bool listen(int port,std::function spawn) { 412 | int sock=-1; 413 | struct sockaddr_in sockaddr; 414 | if (-1==(sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))) { 415 | return true; 416 | } 417 | memset(&sockaddr,0,sizeof(sockaddr)); 418 | sockaddr.sin_family=AF_INET; 419 | sockaddr.sin_addr.s_addr=0; // default addr 420 | sockaddr.sin_port=htons(port); 421 | if (bind(sock,(struct sockaddr*)&sockaddr,sizeof(sockaddr))) { 422 | closesocket(sock); 423 | return true; 424 | } 425 | if (::listen(sock,SOMAXCONN)) { 426 | closesocket(sock); 427 | return true; 428 | } 429 | set_non_blocking_socket(sock); 430 | listeners.push_back(std::make_pair(sock,spawn)); 431 | return false; 432 | } 433 | bool connect(const std::string & host,int port,const std::function spawn) { 434 | struct sockaddr_in sockaddr; 435 | memset(&sockaddr,0,sizeof(sockaddr)); 436 | sockaddr.sin_family=AF_INET; 437 | sockaddr.sin_port=htons(port); 438 | sockaddr.sin_addr.s_addr=inet_addr(host.c_str()); 439 | if (sockaddr.sin_addr.s_addr==INADDR_NONE) { 440 | struct hostent *he=gethostbyname(host.c_str()); 441 | if (!he) { 442 | printf("Failed getting hostname"); 443 | return true; 444 | } 445 | memcpy(&sockaddr.sin_addr,he->h_addr,sizeof(sockaddr.sin_addr)); 446 | } 447 | 448 | int sock=-1; 449 | if (-1==(sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))) { 450 | return true; 451 | } 452 | if (::connect(sock,(struct sockaddr*)&sockaddr,sizeof(sockaddr))) { 453 | printf("Connect failed?\n"); 454 | closesocket(sock); 455 | return true; 456 | } 457 | set_non_blocking_socket(sock); 458 | //std::shared_ptr out( 459 | auto* out=new connection(input_buffer_size,output_buffer_size); 460 | // [](auto p) { delete p; } 461 | //); 462 | out->sock=sock; 463 | out->want_input=true; 464 | spawn(out); 465 | //out->conn.reset(spawn()); 466 | conns.emplace_back(out); 467 | return false; 468 | } 469 | 470 | // class connection : public std::enable_s { 471 | // connection() { 472 | // //printf("conn ctor\n"); 473 | // } 474 | // virtual ~connection() { 475 | // //printf("conn dtor\n"); 476 | // } 477 | // public: 478 | // }; 479 | }; 480 | } 481 | 482 | -------------------------------------------------------------------------------- /net11/util.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __INCLUDED_NET11_UTIL_HPP__ 2 | #define __INCLUDED_NET11_UTIL_HPP__ 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #ifdef _MSC_VER 17 | #include 18 | #include 19 | #else 20 | #include 21 | #include 22 | #endif 23 | 24 | namespace net11 { 25 | // a sink is a data receiver 26 | class sink; 27 | // buffer is a utility class built to pass along data as a memory fifo 28 | class buffer; 29 | 30 | // a utility sink that reads a full line 31 | class line_parser_sink; 32 | // a utility sink that parses RFC 822 headers 33 | class header_parser_sink; 34 | 35 | // utility functions to create producers from a string or vector 36 | template 37 | std::function make_data_producer(T * in_data); 38 | template 39 | std::function make_data_producer(const T &in_data); 40 | 41 | // a utility function to give up a slice of cpu time 42 | void yield(); 43 | 44 | class buffer { 45 | bool m_isview; 46 | int m_cap; // the total number of bytes in this buffer 47 | int m_bottom; // the bottom index, ie the first used data element 48 | int m_top; // the top index, the first unused data element 49 | char *m_data; // the actual data 50 | buffer(const buffer &)=delete; 51 | buffer& operator=(const buffer&)=delete; 52 | public: 53 | // Construct a view of a piece of data (nothing available to fill but plenty of used to consume) 54 | buffer(char *data,int amount) : m_isview(true),m_cap(amount),m_bottom(0),m_top(amount),m_data(data) { 55 | } 56 | buffer(int capacity) : m_isview(false),m_cap(capacity),m_bottom(0),m_top(0),m_data(new char[capacity]) { 57 | //m_data=new char[capacity]; 58 | } 59 | ~buffer() { 60 | if (!m_isview) 61 | delete m_data; 62 | } 63 | // returns the number of bytes corrently in the buffer 64 | inline int usage() { 65 | return m_top-m_bottom; 66 | } 67 | // returns the number of bytes available to produce as a flat array 68 | int direct_avail() { 69 | return m_cap-m_top; 70 | } 71 | // returns the total number of bytes available to produce 72 | int total_avail() { 73 | return (m_cap-m_top)+(m_bottom); 74 | } 75 | // compacts the buffer to maximize the flatly available bytes 76 | int compact() { 77 | if (m_bottom==0) 78 | return direct_avail(); 79 | int sz=usage(); 80 | std::memmove(m_data,m_data+m_bottom,sz); 81 | m_bottom=0; 82 | m_top=sz; 83 | return direct_avail(); 84 | } 85 | inline int peek() { 86 | if (m_bottom>=m_top) 87 | return -1; 88 | return m_data[m_bottom]&0xff; 89 | } 90 | // consumes one byte from the currently available bytes 91 | inline char consume() { 92 | if (m_bottom>=m_top) 93 | throw std::out_of_range("no bytes to consume in buffer"); 94 | return m_data[m_bottom++]; 95 | } 96 | // returns the pointer to a number of bytes to consume directly. 97 | char* to_consume() { 98 | return m_data+m_bottom; 99 | } 100 | // tells the buffer how many bytes was consumed 101 | void consumed(int amount) { 102 | if (usage()total_avail()) 121 | to_copy=total_avail(); 122 | produce(source,to_copy); 123 | } 124 | // copy the number of bytes from the source 125 | void produce(buffer &source,int to_copy) { 126 | // if we can fit it straight away copy it directly 127 | if (direct_avail() 160 | std::function make_data_producer(T * in_data) { 161 | std::shared_ptr off(new int); 162 | std::shared_ptr data(in_data); 163 | *off=0; 164 | // return the actual producer function that writes out the contents on request 165 | return [data,off](buffer &ob){ 166 | int dataleft=data->size()-*off; // dl is how much we have left to send 167 | int outleft=ob.compact(); 168 | int to_copy=dataleftdata()+*off,to_copy); 170 | ob.produced(to_copy); 171 | *off+=to_copy; 172 | return *off!=data->size(); 173 | }; 174 | } 175 | 176 | template 177 | std::function make_data_producer(const T &in_data) { 178 | return make_data_producer(new T(in_data)); 179 | } 180 | 181 | class sink { 182 | public: 183 | // implement this function to make a working sink 184 | virtual bool drain(buffer &buf)=0; 185 | }; 186 | 187 | 188 | 189 | class line_parser_sink : public sink { 190 | std::string out; // the output string 191 | const char *term; // the line terminator 192 | int tl; // length of the terminator string 193 | int szmax; // the maximum number of bytes in a line 194 | std::function on_line; 195 | public: 196 | line_parser_sink( 197 | const char *in_term, 198 | int in_max, 199 | std::function in_on_line 200 | ): 201 | term(in_term), 202 | szmax(in_max), 203 | on_line(in_on_line) 204 | { 205 | tl=strlen(term); 206 | } 207 | virtual bool drain(buffer &buf) { 208 | size_t sz=out.size(); 209 | while(buf.usage()) { 210 | if (sz>=szmax) { 211 | return false; 212 | } 213 | //std::cout<<"Pre:["<tl) { 218 | if (!memcmp(out.data()+sz-tl,term,tl)) { 219 | out.resize(sz-tl); 220 | //std::cout<<"Line:"< on_header; 249 | std::function on_fin; 250 | public: 251 | header_parser_sink( 252 | int in_maxsz, 253 | int (*in_filter)(int c), 254 | std::function in_on_header, 255 | std::function in_on_fin 256 | ): 257 | state(firstlinestart), 258 | count(0), 259 | maxsz(in_maxsz), 260 | filter(in_filter), 261 | on_header(in_on_header), 262 | on_fin(in_on_fin) 263 | {} 264 | virtual bool drain(buffer &buf) { 265 | // pre-existing error condition, just return. 266 | if (count==-1) 267 | return false; 268 | while(buf.usage()) { 269 | if (count>=maxsz) { 270 | on_fin("Error, headers too large"); 271 | count=-1; 272 | return false; 273 | } 274 | char c=buf.consume(); 275 | count++; 276 | switch(state) { 277 | case firstlinestart : 278 | case linestart : 279 | if (c==13) { 280 | if (state!=firstlinestart) { 281 | on_header(k,v); 282 | k.clear(); 283 | v.clear(); 284 | } 285 | // empty line in progress 286 | state=testemptyline; 287 | continue; 288 | } else if (c==10) { 289 | on_fin("spurios LF"); 290 | count=-1; 291 | return false; 292 | } 293 | if (state!=firstlinestart) { 294 | if (isspace(c)) { 295 | state=invalue; 296 | v.push_back(c); 297 | continue; 298 | } 299 | on_header(k,v); 300 | k.clear(); 301 | v.clear(); 302 | } 303 | if (isspace(c)) 304 | continue; 305 | state=inkey; 306 | if (filter) 307 | c = filter(c); 308 | k.push_back(c); 309 | continue; 310 | case testemptyline : 311 | if (c==10) { 312 | // empty line encountered, we're finished with the data 313 | bool rv=on_fin(nullptr); 314 | k.clear(); 315 | v.clear(); 316 | state=firstlinestart; 317 | count=0; 318 | return rv; 319 | } else { 320 | on_fin("cr but no lf in empty headerline"); 321 | count=-1; 322 | return false; 323 | } 324 | case inkey : 325 | if (c==':') { 326 | state=postkeyskip; 327 | continue; 328 | } else { 329 | if (filter) 330 | c=filter(c); 331 | k.push_back(c); 332 | continue; 333 | } 334 | case postkeyskip : 335 | if (isspace(c)) { 336 | continue; 337 | } else { 338 | state=invalue; 339 | v.push_back(c); 340 | continue; 341 | } 342 | case invalue : 343 | if (c==13) { 344 | state=postvalue; 345 | continue; 346 | } else { 347 | v.push_back(c); 348 | continue; 349 | } 350 | case postvalue : 351 | if (c==10) { 352 | state=linestart; 353 | continue; 354 | } else { 355 | on_fin("cr but no lf in headerline"); 356 | count=-1; 357 | return false; 358 | } 359 | default: 360 | printf("headerparser unhandled state:%d\n",state); 361 | exit(-1); 362 | } 363 | } 364 | return true; 365 | } 366 | }; 367 | 368 | void yield() { 369 | // TODO: IOCP/KEVENT... 370 | #ifdef _MSC_VER 371 | SleepEx(1,TRUE); 372 | #else 373 | usleep(10000); 374 | #endif 375 | } 376 | 377 | uint64_t current_time_millis() { 378 | #ifdef _MSC_VER 379 | SYSTEMTIME st; 380 | GetSystemTime(&st); 381 | return st.wMilliseconds+(1000*(uint64_t)time(0)); 382 | #else 383 | struct timeval tv; 384 | gettimeofday(&tv,NULL); 385 | return (tv.tv_usec/1000)+(1000*(uint64_t)time(0)); 386 | #endif 387 | } 388 | 389 | class scheduler { 390 | struct event { 391 | //uint64_t next; 392 | uint64_t period; // for recurring events 393 | std::function once; 394 | std::function recurring; 395 | event(std::function f):period(0),once(f) {} 396 | event(uint64_t in_period,std::function f):period(in_period),recurring(f) {} 397 | }; 398 | std::multimap events; 399 | public: 400 | void timeout(uint64_t timeout,std::function f) { 401 | uint64_t event_time=current_time_millis()+timeout; 402 | events.emplace(event_time,event(f)); 403 | } 404 | void interval(uint64_t timeout,uint64_t period,std::function f) { 405 | uint64_t event_time=current_time_millis()+timeout; 406 | events.emplace(event_time,event(period,f)); 407 | } 408 | void poll() { 409 | uint64_t now=current_time_millis(); 410 | while(!events.empty()) { 411 | auto bi=events.begin(); 412 | if (bi->first>now) 413 | break; 414 | if (bi->second.period) { 415 | if (bi->second.recurring()) { 416 | uint64_t next=bi->first+bi->second.period; 417 | events.emplace(next,event(bi->second.period,bi->second.recurring)); 418 | bi=events.begin(); 419 | } 420 | } else { 421 | bi->second.once(); 422 | } 423 | events.erase(bi); 424 | } 425 | } 426 | }; 427 | 428 | int stricmp(const std::string &l,const std::string &r) { 429 | size_t count=l.size() split(const std::string &s,char delim) { 443 | std::pair out; 444 | size_t dp=s.find(delim); 445 | // just take the find up until the match (or to the end if no match) 446 | out.first.append(s,0,dp); 447 | // create an adjusted start position for the second string based on find status and position 448 | size_t sp=(dp==std::string::npos)?s.size():dp+1; 449 | out.second.append(s,sp,s.size()-sp); 450 | return out; 451 | } 452 | void ltrim(std::string &v) { 453 | size_t ec=0; 454 | while(ec 471 | I filterOr(R v,FT f,I elval) { 472 | if (v) 473 | return f(v); 474 | return elval; 475 | } 476 | } 477 | 478 | #endif // __INCLUDED_NET11_UTIL_HPP__ 479 | --------------------------------------------------------------------------------