Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

ngx_http_uploadprogress_module.c 33KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  1. /*
  2. * Copyright (C) 2007 Brice Figureau
  3. * shm_zone and rbtree code Copyright (c) 2002-2007 Igor Sysoev
  4. */
  5. #include <ngx_config.h>
  6. #include <ngx_core.h>
  7. #include <ngx_http.h>
  8. #define TIMER_FREQUENCY 15 * 1000
  9. typedef struct ngx_http_uploadprogress_node_s ngx_http_uploadprogress_node_t;
  10. struct ngx_http_uploadprogress_node_s {
  11. ngx_rbtree_node_t node;
  12. ngx_http_request_t *r;
  13. ngx_uint_t err_status;
  14. time_t timeout;
  15. struct ngx_http_uploadprogress_node_s *prev;
  16. struct ngx_http_uploadprogress_node_s *next;
  17. u_char len;
  18. u_char data[1];
  19. };
  20. typedef struct {
  21. ngx_shm_zone_t *shm_zone;
  22. ngx_rbtree_node_t *node;
  23. time_t timeout;
  24. } ngx_http_uploadprogress_cleanup_t;
  25. typedef struct {
  26. ngx_rbtree_t *rbtree;
  27. ngx_http_uploadprogress_node_t list_head;
  28. ngx_http_uploadprogress_node_t list_tail;
  29. } ngx_http_uploadprogress_ctx_t;
  30. typedef struct {
  31. ngx_shm_zone_t *shm_zone;
  32. time_t timeout;
  33. ngx_event_t cleanup;
  34. u_char track;
  35. } ngx_http_uploadprogress_conf_t;
  36. static ngx_int_t ngx_http_reportuploads_handler(ngx_http_request_t *r);
  37. static void ngx_http_uploadprogress_cleanup(void *data);
  38. static char *ngx_http_report_uploads(ngx_conf_t * cf, ngx_command_t * cmd, void *conf);
  39. static ngx_int_t ngx_http_uploadprogress_init_zone(ngx_shm_zone_t * shm_zone, void *data);
  40. static ngx_int_t ngx_http_uploadprogress_init(ngx_conf_t * cf);
  41. static void *ngx_http_uploadprogress_create_loc_conf(ngx_conf_t *cf);
  42. static char *ngx_http_uploadprogress_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
  43. static char *ngx_http_track_uploads(ngx_conf_t * cf, ngx_command_t * cmd, void *conf);
  44. static char *ngx_http_report_uploads(ngx_conf_t * cf, ngx_command_t * cmd, void *conf);
  45. static char *ngx_http_upload_progress(ngx_conf_t * cf, ngx_command_t * cmd, void *conf);
  46. static void ngx_clean_old_connections(ngx_event_t * ev);
  47. static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
  48. static ngx_command_t ngx_http_uploadprogress_commands[] = {
  49. {ngx_string("upload_progress"),
  50. NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE2,
  51. ngx_http_upload_progress,
  52. 0,
  53. 0,
  54. NULL},
  55. {ngx_string("track_uploads"),
  56. NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2,
  57. ngx_http_track_uploads,
  58. NGX_HTTP_LOC_CONF_OFFSET,
  59. 0,
  60. NULL},
  61. {ngx_string("report_uploads"),
  62. NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
  63. ngx_http_report_uploads,
  64. NGX_HTTP_LOC_CONF_OFFSET,
  65. 0,
  66. NULL},
  67. ngx_null_command
  68. };
  69. static ngx_http_module_t ngx_http_uploadprogress_module_ctx = {
  70. NULL, /* preconfiguration */
  71. ngx_http_uploadprogress_init, /* postconfiguration */
  72. NULL, /* create main configuration */
  73. NULL, /* init main configuration */
  74. NULL, /* create server configuration */
  75. NULL, /* merge server configuration */
  76. ngx_http_uploadprogress_create_loc_conf, /* create location configuration */
  77. ngx_http_uploadprogress_merge_loc_conf /* merge location configuration */
  78. };
  79. ngx_module_t ngx_http_uploadprogress_module = {
  80. NGX_MODULE_V1,
  81. &ngx_http_uploadprogress_module_ctx, /* module context */
  82. ngx_http_uploadprogress_commands, /* module directives */
  83. NGX_HTTP_MODULE, /* module type */
  84. NULL, /* init master */
  85. NULL, /* init module */
  86. NULL, /* init process */
  87. NULL, /* init thread */
  88. NULL, /* exit thread */
  89. NULL, /* exit process */
  90. NULL, /* exit master */
  91. NGX_MODULE_V1_PADDING
  92. };
  93. static ngx_str_t x_progress_id = ngx_string("X-Progress-ID");
  94. static ngx_str_t*
  95. get_tracking_id(ngx_http_request_t * r)
  96. {
  97. u_char *p, *start_p;
  98. ngx_uint_t i;
  99. ngx_list_part_t *part;
  100. ngx_table_elt_t *header;
  101. ngx_str_t *ret;
  102. part = &r->headers_in.headers.part;
  103. header = part->elts;
  104. for (i = 0; /* void */ ; i++) {
  105. if (i >= part->nelts) {
  106. if (part->next == NULL) {
  107. break;
  108. }
  109. part = part->next;
  110. header = part->elts;
  111. i = 0;
  112. }
  113. if (header[i].key.len == x_progress_id.len
  114. && ngx_strncmp(header[i].key.data, x_progress_id.data,
  115. header[i].key.len) == 0) {
  116. ret = ngx_pcalloc(r->pool, sizeof(ngx_str_t));
  117. ret->data = header[i].value.data;
  118. ret->len = header[i].value.len;
  119. return ret;
  120. }
  121. }
  122. /* not found, check as a reaquest arg */
  123. if (r->args.len) {
  124. p = (u_char *) ngx_strstr(r->args.data, "X-Progress-ID=");
  125. if (p) {
  126. start_p = p += 14;
  127. while (p < r->args.data + r->args.len) {
  128. if (*p++ != '&') {
  129. continue;
  130. }
  131. }
  132. ret = ngx_pcalloc(r->pool, sizeof(ngx_str_t));
  133. ret->data = start_p;
  134. ret->len = p - start_p;
  135. return ret;
  136. }
  137. }
  138. return NULL;
  139. }
  140. static ngx_http_uploadprogress_node_t *
  141. find_node(ngx_str_t * id, ngx_http_uploadprogress_ctx_t * ctx, ngx_log_t * log)
  142. {
  143. uint32_t hash;
  144. ngx_rbtree_node_t *node, *sentinel;
  145. ngx_int_t rc;
  146. ngx_http_uploadprogress_node_t *up;
  147. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "upload-progress: find_node %V", id);
  148. hash = ngx_crc32_short(id->data, id->len);
  149. node = ctx->rbtree->root;
  150. sentinel = ctx->rbtree->sentinel;
  151. while (node != sentinel) {
  152. if (hash < node->key) {
  153. node = node->left;
  154. continue;
  155. }
  156. if (hash > node->key) {
  157. node = node->right;
  158. continue;
  159. }
  160. /* hash == node->key */
  161. do {
  162. up = (ngx_http_uploadprogress_node_t *) node;
  163. rc = ngx_memn2cmp(id->data, up->data, id->len, (size_t) up->len);
  164. if (rc == 0) {
  165. ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0,
  166. "upload-progress: found node");
  167. return up;
  168. }
  169. node = (rc < 0) ? node->left : node->right;
  170. } while (node != sentinel && hash == node->key);
  171. break;
  172. }
  173. ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "upload-progress: can't find node");
  174. return NULL;
  175. }
  176. /* This generates the response for the report */
  177. static ngx_int_t
  178. ngx_http_reportuploads_handler(ngx_http_request_t * r)
  179. {
  180. ngx_str_t *id;
  181. ngx_buf_t *b;
  182. ngx_chain_t out;
  183. ngx_http_request_t *orig;
  184. ngx_int_t rc, size;
  185. ngx_uint_t len, i;
  186. ngx_slab_pool_t *shpool;
  187. ngx_http_uploadprogress_conf_t *upcf;
  188. ngx_http_uploadprogress_ctx_t *ctx;
  189. ngx_http_uploadprogress_node_t *up;
  190. ngx_table_elt_t *expires, *cc, **ccp;
  191. if (r->method != NGX_HTTP_GET && r->method != NGX_HTTP_HEAD) {
  192. return NGX_HTTP_NOT_ALLOWED;
  193. }
  194. rc = ngx_http_discard_request_body(r);
  195. if (rc != NGX_OK) {
  196. return rc;
  197. }
  198. /* get the tracking id if any */
  199. id = get_tracking_id(r);
  200. if (id == NULL) {
  201. ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  202. "reportuploads handler cant find id");
  203. return NGX_DECLINED;
  204. }
  205. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  206. "reportuploads handler found id: %V", id);
  207. upcf = ngx_http_get_module_loc_conf(r, ngx_http_uploadprogress_module);
  208. if (upcf->shm_zone == NULL) {
  209. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  210. "reportuploads no shm_zone for id: %V", id);
  211. return NGX_DECLINED;
  212. }
  213. orig = NULL;
  214. ctx = upcf->shm_zone->data;
  215. /* get the original connection of the upload */
  216. shpool = (ngx_slab_pool_t *) upcf->shm_zone->shm.addr;
  217. ngx_shmtx_lock(&shpool->mutex);
  218. up = find_node(id, ctx, r->connection->log);
  219. if (up != NULL) {
  220. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  221. "reportuploads found node: %V", id);
  222. orig = up->r;
  223. } else {
  224. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  225. "reportuploads not found: %V", id);
  226. }
  227. ngx_shmtx_unlock(&shpool->mutex);
  228. /* send the output */
  229. r->headers_out.content_type.len = sizeof("text/javascript") - 1;
  230. r->headers_out.content_type.data = (u_char *) "text/javascript";
  231. /* force no-cache */
  232. expires = r->headers_out.expires;
  233. if (expires == NULL) {
  234. expires = ngx_list_push(&r->headers_out.headers);
  235. if (expires == NULL) {
  236. return NGX_HTTP_INTERNAL_SERVER_ERROR;
  237. }
  238. r->headers_out.expires = expires;
  239. expires->hash = 1;
  240. expires->key.len = sizeof("Expires") - 1;
  241. expires->key.data = (u_char *) "Expires";
  242. }
  243. len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT");
  244. expires->value.len = len - 1;
  245. ccp = r->headers_out.cache_control.elts;
  246. if (ccp == NULL) {
  247. if (ngx_array_init(&r->headers_out.cache_control, r->pool,
  248. 1, sizeof(ngx_table_elt_t *))
  249. != NGX_OK) {
  250. return NGX_HTTP_INTERNAL_SERVER_ERROR;
  251. }
  252. ccp = ngx_array_push(&r->headers_out.cache_control);
  253. if (ccp == NULL) {
  254. return NGX_HTTP_INTERNAL_SERVER_ERROR;
  255. }
  256. cc = ngx_list_push(&r->headers_out.headers);
  257. if (cc == NULL) {
  258. return NGX_HTTP_INTERNAL_SERVER_ERROR;
  259. }
  260. cc->hash = 1;
  261. cc->key.len = sizeof("Cache-Control") - 1;
  262. cc->key.data = (u_char *) "Cache-Control";
  263. *ccp = cc;
  264. } else {
  265. for (i = 1; i < r->headers_out.cache_control.nelts; i++) {
  266. ccp[i]->hash = 0;
  267. }
  268. cc = ccp[0];
  269. }
  270. expires->value.data = (u_char *) "Thu, 01 Jan 1970 00:00:01 GMT";
  271. cc->value.len = sizeof("no-cache") - 1;
  272. cc->value.data = (u_char *) "no-cache";
  273. if (r->method == NGX_HTTP_HEAD) {
  274. r->headers_out.status = NGX_HTTP_OK;
  275. rc = ngx_http_send_header(r);
  276. if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
  277. return rc;
  278. }
  279. }
  280. if (orig == NULL) {
  281. if (up != NULL && up->err_status != NGX_HTTP_REQUEST_ENTITY_TOO_LARGE) {
  282. size = sizeof("new Object({ 'state' : 'done' })\r\n");
  283. } else if (up != NULL && up->err_status == NGX_HTTP_REQUEST_ENTITY_TOO_LARGE) {
  284. size = sizeof("new Object({ 'state' : 'error', 'status' : 413 })\r\n");
  285. } else {
  286. size = sizeof("new Object({ 'state' : 'starting' })\r\n");
  287. }
  288. } else if (orig->err_status == NGX_HTTP_REQUEST_ENTITY_TOO_LARGE) {
  289. size = sizeof("new Object({ 'state' : 'error', 'status' : 413 })\r\n");
  290. } else {
  291. size =
  292. sizeof("new Object({ 'state' : 'uploading', 'received' : ") +
  293. NGX_INT_T_LEN + sizeof(" })\r\n");
  294. size += sizeof(", 'size' : ") + NGX_INT_T_LEN;
  295. }
  296. b = ngx_create_temp_buf(r->pool, size);
  297. if (b == NULL) {
  298. return NGX_HTTP_INTERNAL_SERVER_ERROR;
  299. }
  300. out.buf = b;
  301. out.next = NULL;
  302. if (orig == NULL) {
  303. if (up == NULL) {
  304. b->last = ngx_cpymem(b->last, "new Object({ 'state' : 'starting' })\r\n",
  305. sizeof("new Object({ 'state' : 'starting' })\r\n") -
  306. 1);
  307. ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  308. "reportuploads returning starting");
  309. } else if (up != NULL && up->err_status == NGX_HTTP_REQUEST_ENTITY_TOO_LARGE) {
  310. b->last =
  311. ngx_cpymem(b->last,
  312. "new Object({ 'state' : 'error', 'status' : 413 })\r\n",
  313. sizeof
  314. ("new Object({ 'state' : 'error', 'status' : 413 })\r\n")
  315. - 1);
  316. ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  317. "reportuploads returning error 413");
  318. } else {
  319. b->last = ngx_cpymem(b->last, "new Object({ 'state' : 'done' })\r\n",
  320. sizeof("new Object({ 'state' : 'done' })\r\n") - 1);
  321. ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  322. "reportuploads returning done");
  323. }
  324. } else if (orig->err_status == NGX_HTTP_REQUEST_ENTITY_TOO_LARGE) {
  325. b->last =
  326. ngx_cpymem(b->last,
  327. "new Object({ 'state' : 'error', 'status' : 413 })\r\n",
  328. sizeof
  329. ("new Object({ 'state' : 'error', 'status' : 413 })\r\n") -
  330. 1);
  331. ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  332. "reportuploads returning error 413");
  333. } else {
  334. b->last =
  335. ngx_cpymem(b->last, "new Object({ 'state' : 'uploading', 'received' : ",
  336. sizeof("new Object({ 'state' : 'uploading', 'received' : ") -
  337. 1);
  338. b->last =
  339. ngx_sprintf(b->last, "%uO, 'size' : %uO })\r\n",
  340. (orig->headers_in.content_length_n -
  341. orig->request_body->rest),
  342. orig->headers_in.content_length_n);
  343. ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  344. "reportuploads returning %uO / %uO",
  345. (orig->headers_in.content_length_n -
  346. orig->request_body->rest),
  347. orig->headers_in.content_length_n);
  348. }
  349. r->headers_out.status = NGX_HTTP_OK;
  350. r->headers_out.content_length_n = b->last - b->pos;
  351. b->last_buf = 1;
  352. rc = ngx_http_send_header(r);
  353. if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
  354. return rc;
  355. }
  356. return ngx_http_output_filter(r, &out);;
  357. }
  358. /*
  359. Let's register the upload connection in our connections rb-tree
  360. */
  361. static ngx_int_t
  362. ngx_http_uploadprogress_handler(ngx_http_request_t * r)
  363. {
  364. size_t n;
  365. ngx_str_t *id;
  366. uint32_t hash;
  367. ngx_slab_pool_t *shpool;
  368. ngx_rbtree_node_t *node;
  369. ngx_http_uploadprogress_conf_t *upcf;
  370. ngx_http_uploadprogress_ctx_t *ctx;
  371. ngx_http_uploadprogress_node_t *up;
  372. ngx_http_uploadprogress_cleanup_t *upcln;
  373. ngx_pool_cleanup_t *cln;
  374. /* Is it a POST connection */
  375. if (r->method != NGX_HTTP_POST) {
  376. return NGX_DECLINED;
  377. }
  378. id = get_tracking_id(r);
  379. if (id == NULL) {
  380. ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  381. "trackuploads no id found in POST upload req");
  382. return NGX_DECLINED;
  383. }
  384. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  385. "trackuploads id found: %V", id);
  386. upcf = ngx_http_get_module_loc_conf(r, ngx_http_uploadprogress_module);
  387. if (!upcf->track) {
  388. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  389. "trackuploads not tracking in this location for id: %V", id);
  390. return NGX_DECLINED;
  391. }
  392. if (upcf->shm_zone == NULL) {
  393. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  394. "trackuploads no shm_zone for id: %V", id);
  395. return NGX_DECLINED;
  396. }
  397. ctx = upcf->shm_zone->data;
  398. hash = ngx_crc32_short(id->data, id->len);
  399. ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  400. "trackuploads hash %08XD for id: %V", hash, id);
  401. shpool = (ngx_slab_pool_t *) upcf->shm_zone->shm.addr;
  402. ngx_shmtx_lock(&shpool->mutex);
  403. if (find_node(id, ctx, r->connection->log) != NULL) {
  404. ngx_shmtx_unlock(&shpool->mutex);
  405. /* already found a node with matching progress ID */
  406. ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
  407. "upload_progress: tracking already registered id: %V", id);
  408. return NGX_HTTP_INTERNAL_SERVER_ERROR;
  409. }
  410. cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_http_uploadprogress_cleanup_t));
  411. if (cln == NULL) {
  412. ngx_shmtx_unlock(&shpool->mutex);
  413. return NGX_HTTP_INTERNAL_SERVER_ERROR;
  414. }
  415. n = sizeof(ngx_http_uploadprogress_node_t)
  416. + id->len;
  417. node = ngx_slab_alloc_locked(shpool, n);
  418. if (node == NULL) {
  419. ngx_shmtx_unlock(&shpool->mutex);
  420. return NGX_HTTP_SERVICE_UNAVAILABLE;
  421. }
  422. up = (ngx_http_uploadprogress_node_t *) node;
  423. node->key = hash;
  424. up->len = (u_char) id->len;
  425. up->r = r;
  426. up->err_status = r->err_status;
  427. up->next = ctx->list_head.next;
  428. up->next->prev = up;
  429. up->prev = &ctx->list_head;
  430. ctx->list_head.next = up;
  431. ngx_memcpy(up->data, id->data, id->len);
  432. ngx_rbtree_insert(ctx->rbtree, node);
  433. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  434. "trackuploads: %08XD inserted in rbtree", node->key);
  435. if (!upcf->cleanup.timer_set) {
  436. upcf->cleanup.data = upcf->shm_zone;
  437. upcf->cleanup.handler = ngx_clean_old_connections;
  438. upcf->cleanup.log = r->connection->log;
  439. ngx_add_timer(&upcf->cleanup, TIMER_FREQUENCY);
  440. }
  441. ngx_shmtx_unlock(&shpool->mutex);
  442. cln->handler = ngx_http_uploadprogress_cleanup;
  443. upcln = cln->data;
  444. upcln->shm_zone = upcf->shm_zone;
  445. upcln->node = node;
  446. upcln->timeout = upcf->timeout;
  447. /* start the timer if needed */
  448. return NGX_DECLINED;
  449. }
  450. static void
  451. ngx_http_uploadprogress_rbtree_insert_value(ngx_rbtree_node_t * temp,
  452. ngx_rbtree_node_t * node,
  453. ngx_rbtree_node_t * sentinel)
  454. {
  455. ngx_http_uploadprogress_node_t *upn, *upnt;
  456. for (;;) {
  457. if (node->key < temp->key) {
  458. if (temp->left == sentinel) {
  459. temp->left = node;
  460. break;
  461. }
  462. temp = temp->left;
  463. } else if (node->key > temp->key) {
  464. if (temp->right == sentinel) {
  465. temp->right = node;
  466. break;
  467. }
  468. temp = temp->right;
  469. } else { /* node->key == temp->key */
  470. upn = (ngx_http_uploadprogress_node_t *) node;
  471. upnt = (ngx_http_uploadprogress_node_t *) temp;
  472. if (ngx_memn2cmp(upn->data, upnt->data, upn->len, upnt->len) < 0) {
  473. if (temp->left == sentinel) {
  474. temp->left = node;
  475. break;
  476. }
  477. temp = temp->left;
  478. } else {
  479. if (temp->right == sentinel) {
  480. temp->right = node;
  481. break;
  482. }
  483. temp = temp->right;
  484. }
  485. }
  486. }
  487. node->parent = temp;
  488. node->left = sentinel;
  489. node->right = sentinel;
  490. ngx_rbt_red(node);
  491. }
  492. static void
  493. ngx_clean_old_connections(ngx_event_t * ev)
  494. {
  495. ngx_shm_zone_t *shm_zone;
  496. ngx_http_uploadprogress_ctx_t *ctx;
  497. ngx_slab_pool_t *shpool;
  498. ngx_rbtree_node_t *node;
  499. ngx_http_uploadprogress_node_t *up;
  500. time_t now = ngx_time();
  501. /* scan the rbtree */
  502. shm_zone = ev->data;
  503. ctx = shm_zone->data;
  504. shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
  505. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, shm_zone->shm.log, 0,
  506. "uploadprogress clean old connections at %T", now);
  507. ngx_shmtx_lock(&shpool->mutex);
  508. node = (ngx_rbtree_node_t *) ctx->list_tail.prev;
  509. for (;;) {
  510. if (node == &ctx->list_head.node) {
  511. break;
  512. }
  513. up = (ngx_http_uploadprogress_node_t *) node;
  514. ngx_log_debug3(NGX_LOG_DEBUG_HTTP, shm_zone->shm.log, 0,
  515. "uploadprogress clean: scanning %08XD (req %p) timeout at %T",
  516. node->key, up->r, up->timeout);
  517. if (up->r == NULL && up->timeout < now) {
  518. up->next->prev = up->prev;
  519. up->prev->next = up->next;
  520. ngx_log_debug3(NGX_LOG_DEBUG_HTTP, shm_zone->shm.log, 0,
  521. "uploadprogress clean: removing %08XD (req %p) ",
  522. node->key, up->r, up->timeout);
  523. ngx_rbtree_delete(ctx->rbtree, node);
  524. ngx_slab_free_locked(shpool, node);
  525. }
  526. node = (ngx_rbtree_node_t *) up->prev;
  527. }
  528. ngx_log_debug0(NGX_LOG_DEBUG_HTTP, shm_zone->shm.log, 0,
  529. "uploadprogress clean old connections restarting timer");
  530. ngx_add_timer(ev, TIMER_FREQUENCY); /* trigger again in 60s */
  531. ngx_shmtx_unlock(&shpool->mutex);
  532. }
  533. /*
  534. removes the expired node from the upload rbtree
  535. */
  536. static void
  537. ngx_http_uploadprogress_cleanup(void *data)
  538. {
  539. ngx_http_uploadprogress_cleanup_t *upcln = data;
  540. ngx_slab_pool_t *shpool;
  541. ngx_rbtree_node_t *node;
  542. ngx_http_uploadprogress_ctx_t *ctx;
  543. ngx_http_uploadprogress_node_t *up;
  544. ngx_log_debug0(NGX_LOG_DEBUG_HTTP, upcln->shm_zone->shm.log, 0,
  545. "uploadprogress cleanup called");
  546. ctx = upcln->shm_zone->data;
  547. shpool = (ngx_slab_pool_t *) upcln->shm_zone->shm.addr;
  548. node = upcln->node;
  549. up = (ngx_http_uploadprogress_node_t *) node;
  550. up->r = NULL; /* mark the original request as done */
  551. up->timeout = ngx_time() + upcln->timeout; /* keep tracking for 60s */
  552. ngx_log_debug2(NGX_LOG_DEBUG_HTTP, upcln->shm_zone->shm.log, 0,
  553. "uploadprogress cleanup: connection %08XD to be deleted at %T",
  554. node->key, up->timeout);
  555. }
  556. static ngx_int_t
  557. ngx_http_uploadprogress_init_zone(ngx_shm_zone_t * shm_zone, void *data)
  558. {
  559. ngx_http_uploadprogress_ctx_t *octx = data;
  560. ngx_slab_pool_t *shpool;
  561. ngx_rbtree_node_t *sentinel;
  562. ngx_http_uploadprogress_ctx_t *ctx;
  563. ctx = shm_zone->data;
  564. if (octx) {
  565. ctx->rbtree = octx->rbtree;
  566. return NGX_OK;
  567. }
  568. shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
  569. ctx->rbtree = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_t));
  570. if (ctx->rbtree == NULL) {
  571. return NGX_ERROR;
  572. }
  573. sentinel = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_node_t));
  574. if (sentinel == NULL) {
  575. return NGX_ERROR;
  576. }
  577. ngx_rbtree_sentinel_init(sentinel);
  578. ctx->rbtree->root = sentinel;
  579. ctx->rbtree->sentinel = sentinel;
  580. ctx->rbtree->insert = ngx_http_uploadprogress_rbtree_insert_value;
  581. return NGX_OK;
  582. }
  583. static ngx_int_t
  584. ngx_http_uploadprogress_errortracker(ngx_http_request_t * r)
  585. {
  586. size_t n;
  587. ngx_str_t *id;
  588. ngx_slab_pool_t *shpool;
  589. ngx_rbtree_node_t *node;
  590. ngx_http_uploadprogress_ctx_t *ctx;
  591. ngx_http_uploadprogress_node_t *up;
  592. ngx_http_uploadprogress_conf_t *upcf;
  593. uint32_t hash;
  594. ngx_http_uploadprogress_cleanup_t *upcln;
  595. ngx_pool_cleanup_t *cln;
  596. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  597. "uploadprogress error-tracker error: %D", r->err_status);
  598. if (r->err_status == NGX_HTTP_REQUEST_ENTITY_TOO_LARGE) {
  599. upcf = ngx_http_get_module_loc_conf(r, ngx_http_uploadprogress_module);
  600. if (!upcf->track) {
  601. ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  602. "uploadprogress error-tracker not tracking in this location");
  603. goto finish;
  604. }
  605. id = get_tracking_id(r);
  606. if (id == NULL) {
  607. ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  608. "trackuploads error-tracker no id found in POST upload req");
  609. goto finish;
  610. }
  611. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  612. "trackuploads error-tracker id found: %V", id);
  613. if (upcf->shm_zone == NULL) {
  614. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  615. "trackuploads no shm_zone for id: %V", id);
  616. goto finish;
  617. }
  618. ctx = upcf->shm_zone->data;
  619. hash = ngx_crc32_short(id->data, id->len);
  620. ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  621. "trackuploads error-tracking hash %08XD for id: %V", hash,
  622. id);
  623. shpool = (ngx_slab_pool_t *) upcf->shm_zone->shm.addr;
  624. ngx_shmtx_lock(&shpool->mutex);
  625. if ((up = find_node(id, ctx, r->connection->log)) != NULL) {
  626. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  627. "trackuploads error-tracking found node for id: %V", id);
  628. up->r = NULL; /* don't really track it since it errors */
  629. up->err_status = r->err_status;
  630. ngx_shmtx_unlock(&shpool->mutex);
  631. goto finish;
  632. }
  633. /* no lz found for this tracking id */
  634. n = sizeof(ngx_http_uploadprogress_node_t) + id->len;
  635. cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_http_uploadprogress_cleanup_t));
  636. if (cln == NULL) {
  637. ngx_shmtx_unlock(&shpool->mutex);
  638. goto finish;
  639. }
  640. node = ngx_slab_alloc_locked(shpool, n);
  641. if (node == NULL) {
  642. ngx_shmtx_unlock(&shpool->mutex);
  643. goto finish;
  644. }
  645. up = (ngx_http_uploadprogress_node_t *) node;
  646. node->key = hash;
  647. up->len = (u_char) id->len;
  648. up->r = NULL; /* don't really track it since it errors */
  649. up->err_status = r->err_status;
  650. ngx_memcpy(up->data, id->data, id->len);
  651. up->next = ctx->list_head.next;
  652. up->next->prev = up;
  653. up->prev = &ctx->list_head;
  654. ctx->list_head.next = up;
  655. ngx_rbtree_insert(ctx->rbtree, node);
  656. /* start the timer if needed */
  657. if (!upcf->cleanup.timer_set) {
  658. upcf->cleanup.data = upcf->shm_zone;
  659. upcf->cleanup.handler = ngx_clean_old_connections;
  660. upcf->cleanup.log = r->connection->log;
  661. ngx_add_timer(&upcf->cleanup, TIMER_FREQUENCY);
  662. }
  663. ngx_shmtx_unlock(&shpool->mutex);
  664. cln->handler = ngx_http_uploadprogress_cleanup;
  665. upcln = cln->data;
  666. upcln->shm_zone = upcf->shm_zone;
  667. upcln->node = node;
  668. upcln->timeout = upcf->timeout;
  669. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  670. "trackuploads error-tracking adding: %08XD", node->key);
  671. }
  672. finish:
  673. /* call the filter chain as usual */
  674. return ngx_http_next_header_filter(r);
  675. }
  676. static ngx_int_t
  677. ngx_http_uploadprogress_init(ngx_conf_t * cf)
  678. {
  679. ngx_http_handler_pt *h;
  680. ngx_http_core_main_conf_t *cmcf;
  681. cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
  682. /* install the tracking handler */
  683. h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);
  684. if (h == NULL) {
  685. return NGX_ERROR;
  686. }
  687. *h = ngx_http_uploadprogress_handler;
  688. /*
  689. we also need to track HTTP 413 errors
  690. unfortunately, the above handler is not called in case of
  691. errors.
  692. we have to register a header output filter that will be
  693. called in any case to track those errors
  694. */
  695. ngx_http_next_header_filter = ngx_http_top_header_filter;
  696. ngx_http_top_header_filter = ngx_http_uploadprogress_errortracker;
  697. return NGX_OK;
  698. }
  699. static void*
  700. ngx_http_uploadprogress_create_loc_conf(ngx_conf_t * cf)
  701. {
  702. ngx_http_uploadprogress_conf_t *conf;
  703. conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_uploadprogress_conf_t));
  704. if (conf == NULL) {
  705. return NGX_CONF_ERROR;
  706. }
  707. return conf;
  708. }
  709. static char*
  710. ngx_http_uploadprogress_merge_loc_conf(ngx_conf_t * cf, void *parent, void *child)
  711. {
  712. ngx_http_uploadprogress_conf_t *prev = parent;
  713. ngx_http_uploadprogress_conf_t *conf = child;
  714. if (conf->shm_zone == NULL) {
  715. *conf = *prev;
  716. }
  717. return NGX_CONF_OK;
  718. }
  719. static char*
  720. ngx_http_upload_progress(ngx_conf_t * cf, ngx_command_t * cmd, void *conf)
  721. {
  722. ssize_t n;
  723. ngx_str_t *value;
  724. ngx_shm_zone_t *shm_zone;
  725. ngx_http_uploadprogress_ctx_t *ctx;
  726. value = cf->args->elts;
  727. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, cf->log, 0,
  728. "ngx_upload_progress name: %V", &value[1]);
  729. ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_uploadprogress_ctx_t));
  730. if (ctx == NULL) {
  731. return NGX_CONF_ERROR;
  732. }
  733. ctx->list_head.prev = NULL;
  734. ctx->list_head.next = &ctx->list_tail;
  735. ctx->list_tail.prev = &ctx->list_head;
  736. ctx->list_tail.next = NULL;
  737. n = ngx_parse_size(&value[2]);
  738. if (n == NGX_ERROR) {
  739. ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  740. "invalid size of track_uploads \"%V\"", &value[2]);
  741. return NGX_CONF_ERROR;
  742. }
  743. if (n < (ngx_int_t) (8 * ngx_pagesize)) {
  744. ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  745. "track_uploads \"%V\" is too small", &value[1]);
  746. return NGX_CONF_ERROR;
  747. }
  748. shm_zone = ngx_shared_memory_add(cf, &value[1], n,
  749. &ngx_http_uploadprogress_module);
  750. if (shm_zone == NULL) {
  751. return NGX_CONF_ERROR;
  752. }
  753. ngx_log_debug2(NGX_LOG_DEBUG_HTTP, cf->log, 0,
  754. "ngx_upload_progress name: %V, szhm_zone: %p", &value[1],
  755. shm_zone);
  756. if (shm_zone->data) {
  757. ctx = shm_zone->data;
  758. ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  759. "track_uploads \"%V\" is already created", &value[1]);
  760. return NGX_CONF_ERROR;
  761. }
  762. shm_zone->init = ngx_http_uploadprogress_init_zone;
  763. shm_zone->data = ctx;
  764. return NGX_CONF_OK;
  765. }
  766. static char*
  767. ngx_http_track_uploads(ngx_conf_t * cf, ngx_command_t * cmd, void *conf)
  768. {
  769. ngx_http_uploadprogress_conf_t *lzcf = conf;
  770. ngx_str_t *value;
  771. ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, "ngx_track_uploads in");
  772. value = cf->args->elts;
  773. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, cf->log, 0,
  774. "ngx_track_uploads name: %V", &value[1]);
  775. lzcf->shm_zone = ngx_shared_memory_add(cf, &value[1], 0,
  776. &ngx_http_uploadprogress_module);
  777. if (lzcf->shm_zone == NULL) {
  778. return NGX_CONF_ERROR;
  779. }
  780. lzcf->track = (u_char) 1;
  781. ngx_log_debug2(NGX_LOG_DEBUG_HTTP, cf->log, 0,
  782. "ngx_track_uploads name: %V,szhm_zone: %p", &value[1],
  783. lzcf->shm_zone);
  784. lzcf->timeout = ngx_parse_time(&value[2], 1);
  785. if (lzcf->timeout == NGX_ERROR) {
  786. ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  787. "track_uploads \"%V\" timeout value invalid", &value[1]);
  788. return NGX_CONF_ERROR;
  789. }
  790. if (lzcf->timeout == NGX_PARSE_LARGE_TIME) {
  791. ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
  792. "track_uploads \"%V\" timeout value must be less than 68 years", &value[1]);
  793. return NGX_CONF_ERROR;
  794. }
  795. return NGX_CONF_OK;
  796. }
  797. static char*
  798. ngx_http_report_uploads(ngx_conf_t * cf, ngx_command_t * cmd, void *conf)
  799. {
  800. ngx_http_uploadprogress_conf_t *lzcf = conf;
  801. ngx_http_core_loc_conf_t *clcf;
  802. ngx_str_t *value;
  803. ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, "ngx_report_uploads in");
  804. value = cf->args->elts;
  805. ngx_log_debug1(NGX_LOG_DEBUG_HTTP, cf->log, 0,
  806. "ngx_report_uploads name: %V", &value[1]);
  807. lzcf->shm_zone = ngx_shared_memory_add(cf, &value[1], 0,
  808. &ngx_http_uploadprogress_module);
  809. if (lzcf->shm_zone == NULL) {
  810. return NGX_CONF_ERROR;
  811. }
  812. ngx_log_debug2(NGX_LOG_DEBUG_HTTP, cf->log, 0,
  813. "ngx_report_uploads name: %V, szhm_zone: %p", &value[1],
  814. lzcf->shm_zone);
  815. lzcf->track = (u_char) 0;
  816. /* install our report handler */
  817. clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
  818. clcf->handler = ngx_http_reportuploads_handler;
  819. return NGX_CONF_OK;
  820. }