tinyproxyd.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. /*
  2. * tinyproxyd tiny http proxy server
  3. *
  4. * @build make examples
  5. *
  6. * @http_server bin/tinyhttpd 8000
  7. * @proxy_server bin/tinyproxyd 1080
  8. *
  9. * @proxy_client bin/curl -v www.httpbin.org/get --http-proxy 127.0.0.1:1080
  10. * bin/curl -v www.httpbin.org/post -d hello --http-proxy 127.0.0.1:1080
  11. * curl -v www.httpbin.org/get --proxy http://127.0.0.1:1080
  12. * curl -v www.httpbin.org/post -d hello --proxy http://127.0.0.1:1080
  13. *
  14. */
  15. #include "hv.h"
  16. #include "hloop.h"
  17. /*
  18. * workflow:
  19. * hloop_new -> hloop_create_tcp_server -> hloop_run ->
  20. * on_accept -> HV_ALLOC(http_conn_t) -> hio_readline ->
  21. * on_recv -> parse_http_request_line -> hio_readline ->
  22. * on_recv -> parse_http_head -> ... -> hio_readline ->
  23. * on_head_end -> hio_setup_upstream ->
  24. * on_upstream_connect -> hio_write_upstream(head) ->
  25. * on_body -> hio_write_upstream(body) ->
  26. * on_upstream_close -> hio_close ->
  27. * on_close -> HV_FREE(http_conn_t)
  28. *
  29. */
  30. static char proxy_host[64] = "0.0.0.0";
  31. static int proxy_port = 1080;
  32. static int proxy_ssl = 0;
  33. static int thread_num = 1;
  34. static hloop_t* accept_loop = NULL;
  35. static hloop_t** worker_loops = NULL;
  36. #define HTTP_KEEPALIVE_TIMEOUT 60000 // ms
  37. #define HTTP_MAX_URL_LENGTH 256
  38. #define HTTP_MAX_HEAD_LENGTH 1024
  39. typedef enum {
  40. s_begin,
  41. s_first_line,
  42. s_request_line = s_first_line,
  43. s_status_line = s_first_line,
  44. s_head,
  45. s_head_end,
  46. s_body,
  47. s_end
  48. } http_state_e;
  49. typedef struct {
  50. // first line
  51. int major_version;
  52. int minor_version;
  53. union {
  54. // request line
  55. struct {
  56. char method[32];
  57. char path[HTTP_MAX_URL_LENGTH];
  58. };
  59. // status line
  60. struct {
  61. int status_code;
  62. char status_message[64];
  63. };
  64. };
  65. // headers
  66. char host[64];
  67. int content_length;
  68. char content_type[64];
  69. unsigned keepalive: 1;
  70. unsigned proxy: 1;
  71. char head[HTTP_MAX_HEAD_LENGTH];
  72. int head_len;
  73. // body
  74. char* body;
  75. int body_len; // body_len = content_length
  76. } http_msg_t;
  77. typedef struct {
  78. hio_t* io;
  79. http_state_e state;
  80. http_msg_t request;
  81. // http_msg_t response;
  82. } http_conn_t;
  83. static int http_request_dump(http_conn_t* conn, char* buf, int len) {
  84. http_msg_t* msg = &conn->request;
  85. int offset = 0;
  86. // request line
  87. const char* path = msg->path;
  88. if (msg->proxy) {
  89. const char* pos = strstr(msg->path, "://");
  90. pos = pos ? pos + 3 : msg->path;
  91. path = strchr(pos, '/');
  92. }
  93. if (path == NULL) path = "/";
  94. offset += snprintf(buf + offset, len - offset, "%s %s HTTP/%d.%d\r\n", msg->method, path, msg->major_version, msg->minor_version);
  95. // headers
  96. if (msg->proxy) {
  97. if (msg->head_len) {
  98. memcpy(buf + offset, msg->head, msg->head_len);
  99. offset += msg->head_len;
  100. }
  101. char peeraddrstr[SOCKADDR_STRLEN] = {0};
  102. SOCKADDR_STR(hio_peeraddr(conn->io), peeraddrstr);
  103. offset += snprintf(buf + offset, len - offset, "X-Origin-IP: %s\r\n", peeraddrstr);
  104. } else {
  105. offset += snprintf(buf + offset, len - offset, "Connection: %s\r\n", msg->keepalive ? "keep-alive" : "close");
  106. if (msg->content_length > 0) {
  107. offset += snprintf(buf + offset, len - offset, "Content-Length: %d\r\n", msg->content_length);
  108. }
  109. if (*msg->content_type) {
  110. offset += snprintf(buf + offset, len - offset, "Content-Type: %s\r\n", msg->content_type);
  111. }
  112. }
  113. // TODO: Add your headers
  114. offset += snprintf(buf + offset, len - offset, "\r\n");
  115. // body
  116. if (msg->body && msg->content_length > 0) {
  117. memcpy(buf + offset, msg->body, msg->content_length);
  118. offset += msg->content_length;
  119. }
  120. return offset;
  121. }
  122. static bool parse_http_request_line(http_conn_t* conn, char* buf, int len) {
  123. // GET / HTTP/1.1
  124. http_msg_t* req = &conn->request;
  125. sscanf(buf, "%s %s HTTP/%d.%d", req->method, req->path, &req->major_version, &req->minor_version);
  126. if (req->major_version != 1) return false;
  127. if (req->minor_version == 1) req->keepalive = 1;
  128. // printf("%s %s HTTP/%d.%d\r\n", req->method, req->path, req->major_version, req->minor_version);
  129. return true;
  130. }
  131. static bool parse_http_head(http_conn_t* conn, char* buf, int len) {
  132. http_msg_t* req = &conn->request;
  133. // Content-Type: text/html
  134. const char* key = buf;
  135. const char* val = buf;
  136. char* delim = strchr(buf, ':');
  137. if (!delim) return false;
  138. *delim = '\0';
  139. val = delim + 1;
  140. // trim space
  141. while (*val == ' ') ++val;
  142. // printf("%s: %s\r\n", key, val);
  143. if (stricmp(key, "Host") == 0) {
  144. strncpy(req->host, val, sizeof(req->host) - 1);
  145. } else if (stricmp(key, "Content-Length") == 0) {
  146. req->content_length = atoi(val);
  147. } else if (stricmp(key, "Content-Type") == 0) {
  148. strncpy(req->content_type, val, sizeof(req->content_type) - 1);
  149. } else if (stricmp(key, "Connection") == 0 || stricmp(key, "Proxy-Connection") == 0) {
  150. if (stricmp(val, "close") == 0) {
  151. req->keepalive = 0;
  152. }
  153. }
  154. return true;
  155. }
  156. static void on_upstream_connect(hio_t* upstream_io) {
  157. // printf("on_upstream_connect\n");
  158. http_conn_t* conn = (http_conn_t*)hevent_userdata(upstream_io);
  159. http_msg_t* req = &conn->request;
  160. // send head
  161. char stackbuf[HTTP_MAX_HEAD_LENGTH + 1024] = {0};
  162. char* buf = stackbuf;
  163. int buflen = sizeof(stackbuf);
  164. int msglen = http_request_dump(conn, buf, buflen);
  165. hio_write(upstream_io, buf, msglen);
  166. if (conn->state != s_end) {
  167. // start recv body then upstream
  168. hio_read_start(conn->io);
  169. } else {
  170. if (req->keepalive) {
  171. // Connection: keep-alive\r\n
  172. // reset and receive next request
  173. memset(&conn->request, 0, sizeof(http_msg_t));
  174. // memset(&conn->response, 0, sizeof(http_msg_t));
  175. conn->state = s_first_line;
  176. hio_readline(conn->io);
  177. }
  178. }
  179. // start recv response
  180. hio_read_start(upstream_io);
  181. }
  182. static int on_head_end(http_conn_t* conn) {
  183. http_msg_t* req = &conn->request;
  184. if (req->host[0] == '\0') {
  185. fprintf(stderr, "No Host header!\n");
  186. return -1;
  187. }
  188. char backend_host[64] = {0};
  189. strcpy(backend_host, req->host);
  190. int backend_port = 80;
  191. char* pos = strchr(backend_host, ':');
  192. if (pos) {
  193. *pos = '\0';
  194. backend_port = atoi(pos + 1);
  195. }
  196. if (backend_port == proxy_port &&
  197. (strcmp(backend_host, proxy_host) == 0 ||
  198. strcmp(backend_host, "localhost") == 0 ||
  199. strcmp(backend_host, "127.0.0.1") == 0)) {
  200. req->proxy = 0;
  201. return 0;
  202. }
  203. // NOTE: blew for proxy
  204. req->proxy = 1;
  205. int backend_ssl = strncmp(req->path, "https", 5) == 0 ? 1 : 0;
  206. // printf("upstream %s:%d\n", backend_host, backend_port);
  207. hloop_t* loop = hevent_loop(conn->io);
  208. // hio_t* upstream_io = hio_setup_tcp_upstream(conn->io, backend_host, backend_port, backend_ssl);
  209. hio_t* upstream_io = hio_create_socket(loop, backend_host, backend_port, HIO_TYPE_TCP, HIO_CLIENT_SIDE);
  210. if (upstream_io == NULL) {
  211. fprintf(stderr, "Failed to upstream %s:%d!\n", backend_host, backend_port);
  212. return -3;
  213. }
  214. if (backend_ssl) {
  215. hio_enable_ssl(upstream_io);
  216. }
  217. hevent_set_userdata(upstream_io, conn);
  218. hio_setup_upstream(conn->io, upstream_io);
  219. hio_setcb_read(upstream_io, hio_write_upstream);
  220. hio_setcb_close(upstream_io, hio_close_upstream);
  221. hio_setcb_connect(upstream_io, on_upstream_connect);
  222. hio_connect(upstream_io);
  223. return 0;
  224. }
  225. static int on_body(http_conn_t* conn, void* buf, int readbytes) {
  226. http_msg_t* req = &conn->request;
  227. if (req->proxy) {
  228. hio_write_upstream(conn->io, buf, readbytes);
  229. }
  230. return 0;
  231. }
  232. static int on_request(http_conn_t* conn) {
  233. // NOTE: just reply 403, please refer to examples/tinyhttpd if you want to reply other.
  234. http_msg_t* req = &conn->request;
  235. char buf[256] = {0};
  236. int len = snprintf(buf, sizeof(buf), "HTTP/%d.%d %d %s\r\nContent-Length: 0\r\n\r\n",
  237. req->major_version, req->minor_version, 403, "Forbidden");
  238. hio_write(conn->io, buf, len);
  239. return 403;
  240. }
  241. static void on_close(hio_t* io) {
  242. // printf("on_close fd=%d error=%d\n", hio_fd(io), hio_error(io));
  243. http_conn_t* conn = (http_conn_t*)hevent_userdata(io);
  244. if (conn) {
  245. HV_FREE(conn);
  246. hevent_set_userdata(io, NULL);
  247. }
  248. hio_close_upstream(io);
  249. }
  250. static void on_recv(hio_t* io, void* buf, int readbytes) {
  251. char* str = (char*)buf;
  252. // printf("on_recv fd=%d readbytes=%d\n", hio_fd(io), readbytes);
  253. // printf("%.*s", readbytes, str);
  254. http_conn_t* conn = (http_conn_t*)hevent_userdata(io);
  255. http_msg_t* req = &conn->request;
  256. switch (conn->state) {
  257. case s_begin:
  258. // printf("s_begin");
  259. conn->state = s_first_line;
  260. case s_first_line:
  261. // printf("s_first_line\n");
  262. if (readbytes < 2) {
  263. fprintf(stderr, "Not match \r\n!");
  264. hio_close(io);
  265. return;
  266. }
  267. str[readbytes - 2] = '\0';
  268. if (parse_http_request_line(conn, str, readbytes - 2) == false) {
  269. fprintf(stderr, "Failed to parse http request line:\n%s\n", str);
  270. hio_close(io);
  271. return;
  272. }
  273. // start read head
  274. conn->state = s_head;
  275. hio_readline(io);
  276. break;
  277. case s_head:
  278. // printf("s_head\n");
  279. if (readbytes < 2) {
  280. fprintf(stderr, "Not match \r\n!");
  281. hio_close(io);
  282. return;
  283. }
  284. if (readbytes == 2 && str[0] == '\r' && str[1] == '\n') {
  285. conn->state = s_head_end;
  286. } else {
  287. // NOTE: save head
  288. if (strnicmp(str, "Proxy-", 6) != 0) {
  289. if (req->head_len + readbytes < HTTP_MAX_HEAD_LENGTH) {
  290. memcpy(req->head + req->head_len, buf, readbytes);
  291. req->head_len += readbytes;
  292. }
  293. }
  294. str[readbytes - 2] = '\0';
  295. if (parse_http_head(conn, str, readbytes - 2) == false) {
  296. fprintf(stderr, "Failed to parse http head:\n%s\n", str);
  297. hio_close(io);
  298. return;
  299. }
  300. hio_readline(io);
  301. break;
  302. }
  303. case s_head_end:
  304. // printf("s_head_end\n");
  305. if (on_head_end(conn) < 0) {
  306. hio_close(io);
  307. return;
  308. }
  309. if (req->content_length == 0) {
  310. conn->state = s_end;
  311. if (req->proxy) {
  312. // NOTE: wait upstream connect!
  313. } else {
  314. goto s_end;
  315. }
  316. } else {
  317. conn->state = s_body;
  318. if (req->proxy) {
  319. // NOTE: start read body on_upstream_connect
  320. // hio_read_start(io);
  321. } else {
  322. // WARN: too large content_length should read multiple times!
  323. hio_readbytes(io, req->content_length);
  324. }
  325. break;
  326. }
  327. case s_body:
  328. // printf("s_body\n");
  329. if (on_body(conn, buf, readbytes) < 0) {
  330. hio_close(io);
  331. return;
  332. }
  333. req->body = str;
  334. req->body_len += readbytes;
  335. if (readbytes == req->content_length) {
  336. conn->state = s_end;
  337. } else {
  338. // Not end
  339. break;
  340. }
  341. case s_end:
  342. s_end:
  343. // printf("s_end\n");
  344. // received complete request
  345. if (req->proxy) {
  346. // NOTE: reply by upstream
  347. } else {
  348. on_request(conn);
  349. }
  350. if (hio_is_closed(io)) return;
  351. if (req->keepalive) {
  352. // Connection: keep-alive\r\n
  353. // reset and receive next request
  354. memset(&conn->request, 0, sizeof(http_msg_t));
  355. // memset(&conn->response, 0, sizeof(http_msg_t));
  356. conn->state = s_first_line;
  357. hio_readline(io);
  358. } else {
  359. // Connection: close\r\n
  360. if (req->proxy) {
  361. // NOTE: wait upstream close!
  362. } else {
  363. hio_close(io);
  364. }
  365. }
  366. break;
  367. default: break;
  368. }
  369. }
  370. static void new_conn_event(hevent_t* ev) {
  371. hloop_t* loop = ev->loop;
  372. hio_t* io = (hio_t*)hevent_userdata(ev);
  373. hio_attach(loop, io);
  374. /*
  375. char localaddrstr[SOCKADDR_STRLEN] = {0};
  376. char peeraddrstr[SOCKADDR_STRLEN] = {0};
  377. printf("tid=%ld connfd=%d [%s] <= [%s]\n",
  378. (long)hv_gettid(),
  379. (int)hio_fd(io),
  380. SOCKADDR_STR(hio_localaddr(io), localaddrstr),
  381. SOCKADDR_STR(hio_peeraddr(io), peeraddrstr));
  382. */
  383. hio_setcb_close(io, on_close);
  384. hio_setcb_read(io, on_recv);
  385. hio_set_keepalive_timeout(io, HTTP_KEEPALIVE_TIMEOUT);
  386. http_conn_t* conn = NULL;
  387. HV_ALLOC_SIZEOF(conn);
  388. conn->io = io;
  389. hevent_set_userdata(io, conn);
  390. // start read first line
  391. conn->state = s_first_line;
  392. hio_readline(io);
  393. }
  394. static hloop_t* get_next_loop() {
  395. static int s_cur_index = 0;
  396. if (s_cur_index == thread_num) {
  397. s_cur_index = 0;
  398. }
  399. return worker_loops[s_cur_index++];
  400. }
  401. static void on_accept(hio_t* io) {
  402. hio_detach(io);
  403. hloop_t* worker_loop = get_next_loop();
  404. hevent_t ev;
  405. memset(&ev, 0, sizeof(ev));
  406. ev.loop = worker_loop;
  407. ev.cb = new_conn_event;
  408. ev.userdata = io;
  409. hloop_post_event(worker_loop, &ev);
  410. }
  411. static HTHREAD_ROUTINE(worker_thread) {
  412. hloop_t* loop = (hloop_t*)userdata;
  413. hloop_run(loop);
  414. return 0;
  415. }
  416. static HTHREAD_ROUTINE(accept_thread) {
  417. hloop_t* loop = (hloop_t*)userdata;
  418. hio_t* listenio = hloop_create_tcp_server(loop, proxy_host, proxy_port, on_accept);
  419. if (listenio == NULL) {
  420. exit(1);
  421. }
  422. if (proxy_ssl) {
  423. hio_enable_ssl(listenio);
  424. }
  425. printf("tinyproxyd listening on %s:%d, listenfd=%d, thread_num=%d\n",
  426. proxy_host, proxy_port, hio_fd(listenio), thread_num);
  427. hloop_run(loop);
  428. return 0;
  429. }
  430. int main(int argc, char** argv) {
  431. if (argc < 2) {
  432. printf("Usage: %s proxy_port [thread_num]\n", argv[0]);
  433. return -10;
  434. }
  435. proxy_port = atoi(argv[1]);
  436. if (argc > 2) {
  437. thread_num = atoi(argv[2]);
  438. } else {
  439. thread_num = get_ncpu();
  440. }
  441. if (thread_num == 0) thread_num = 1;
  442. worker_loops = (hloop_t**)malloc(sizeof(hloop_t*) * thread_num);
  443. for (int i = 0; i < thread_num; ++i) {
  444. worker_loops[i] = hloop_new(HLOOP_FLAG_AUTO_FREE);
  445. hthread_create(worker_thread, worker_loops[i]);
  446. }
  447. accept_loop = hloop_new(HLOOP_FLAG_AUTO_FREE);
  448. accept_thread(accept_loop);
  449. return 0;
  450. }