forked from sysml/minicache
-
Notifications
You must be signed in to change notification settings - Fork 0
/
http.c
1562 lines (1397 loc) · 47 KB
/
http.c
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
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Fast HTTP Server Implementation for SHFS volumes
*
* Authors: Simon Kuenzer <[email protected]>
*
*
* Copyright (c) 2013-2017, NEC Europe Ltd., NEC Corporation All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY.
*/
#if defined HAVE_SHELL && defined HTTP_INFO
#include "shell.h"
#endif
#include "http_defs.h"
#include "http_data.h"
#include "http_fio.h"
#include "http_link.h"
#include "http.h"
struct http_srv *hs = NULL;
static err_t httpsess_accept (void *argp, struct tcp_pcb *new_tpcb, err_t err);
static err_t httpsess_close (struct http_sess *hsess, enum http_sess_close type);
static err_t httpsess_sent (void *argp, struct tcp_pcb *tpcb, uint16_t len);
static err_t httpsess_recv (void *argp, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);
static void httpsess_error (void *argp, err_t err);
static err_t httpsess_poll (void *argp, struct tcp_pcb *tpcb);
static err_t httpsess_acknowledge(struct http_sess *hsess, size_t len);
static int httprecv_req_complete(struct http_parser *parser);
static int httprecv_hdr_url(struct http_parser *parser, const char *buf, size_t len);
static http_parser_settings _http_parser_settings = {
.on_message_begin = NULL,
.on_url = httprecv_hdr_url,
.on_status = NULL,
.on_header_field = httpparser_recvhdr_field,
.on_header_value = httpparser_recvhdr_value,
.on_headers_complete = NULL,
.on_body = NULL,
.on_message_complete = httprecv_req_complete
};
int init_http(uint16_t nb_sess, uint32_t nb_reqs)
{
err_t err;
int ret = 0;
hs = target_malloc(CACHELINE_SIZE, sizeof(*hs));
if (!hs) {
ret = -ENOMEM;
goto err_out;
}
hs->max_nb_sess = nb_sess;
hs->nb_sess = 0;
hs->max_nb_reqs = nb_reqs;
hs->nb_reqs = 0;
/* allocate session pool */
hs->sess_pool = alloc_simple_mempool(hs->max_nb_sess, sizeof(struct http_sess));
if (!hs->sess_pool) {
ret = -ENOMEM;
goto err_free_hs;
}
/* allocate request pool */
hs->req_pool = alloc_simple_mempool(hs->max_nb_reqs, sizeof(struct http_req));
if (!hs->req_pool) {
ret = -ENOMEM;
goto err_free_sesspool;
}
/* initialize http link system */
ret = httplink_init(hs);
if (ret < 0)
goto err_free_reqpool;
/* register TCP listener */
hs->tpcb = tcp_new();
if (!hs->tpcb) {
ret = -ENOMEM;
goto err_exit_link;
}
err = tcp_bind(hs->tpcb, IP_ADDR_ANY, HTTP_LISTEN_PORT);
if (err != ERR_OK) {
ret = -err;
goto err_free_tcp;
}
hs->tpcb = tcp_listen(hs->tpcb);
tcp_arg(hs->tpcb, hs);
tcp_accept(hs->tpcb, httpsess_accept); /* register session accept */
/* init session list */
hs->hsess_head = NULL;
hs->hsess_tail = NULL;
/* wait for I/O retry list */
dlist_init_head(hs->ioretry_chain);
printd("HTTP server %p initialized\n", hs);
#if defined HAVE_SHELL && defined HTTP_INFO
shell_register_cmd("http-info", shcmd_http_info);
#endif
return 0;
err_free_tcp:
tcp_abort(hs->tpcb);
err_exit_link:
httplink_exit(hs);
err_free_reqpool:
free_mempool(hs->req_pool);
err_free_sesspool:
free_mempool(hs->sess_pool);
err_free_hs:
target_free(hs);
err_out:
return ret;
}
void exit_http(void)
{
/* terminate connections that are still open */
while(hs->hsess_head) {
printd("Closing session %p...\n", hs->hsess_head);
httpsess_close(hs->hsess_head, HSC_CLOSE);
}
BUG_ON(hs->nb_reqs != 0);
BUG_ON(hs->nb_sess != 0);
tcp_close(hs->tpcb);
httplink_exit(hs);
free_mempool(hs->req_pool);
free_mempool(hs->sess_pool);
target_free(hs);
hs = NULL;
}
/*******************************************************************************
* Session + Request handling
******************************************************************************/
#define httpsess_reset_keepalive(hsess) \
do { \
(hsess)->keepalive_timer = HTTP_KEEPALIVE_TIMEOUT; \
} while(0)
#define httpsess_halt_keepalive(hsess) \
do { \
(hsess)->keepalive_timer = -1; \
} while(0)
/* gets called whenever it is worth
* to retry an failed file I/O operation (with EAGAIN) */
void http_poll_ioretry(void) {
struct http_sess *hsess;
struct http_sess *hsess_next;
if (unlikely(!hs))
return; /* no active http server */
hsess = dlist_first_el(hs->ioretry_chain, struct http_sess);
/* clear head so that a new list is created
* This avoids the the case that within a callback the elements gets
* appanded to the list over an over again */
dlist_init_head(hs->ioretry_chain);
while (hsess) {
hsess_next = dlist_next_el(hsess, ioretry_chain);
/* "unlink" this element from the list because
* the head is released already -> we need to do a list cleanup */
hsess->ioretry_chain.next = NULL;
hsess->ioretry_chain.prev = NULL;
printd("Retrying I/O on session %p\n", hsess);
httpsess_respond(hsess); /* can register itself to the new list */
hsess = hsess_next; /* next element */
}
}
static inline struct http_req *httpreq_open(struct http_sess *hsess)
{
struct mempool_obj *hrobj;
struct http_req *hreq;
hrobj = mempool_pick(hsess->hsrv->req_pool);
if (!hrobj)
return NULL;
hreq = hrobj->data;
hreq->pobj = hrobj;
hreq->hsess = hsess;
hreq->next = NULL;
hreq->state = HRS_PARSING_HDR;
hreq->type = HRT_UNDEF;
http_recvhdr_reset(&hreq->request.hdr);
hreq->request.url_len = 0;
hreq->request.url_overflow = 0;
hreq->request.url_argp = NULL;
http_sendhdr_reset(&hreq->response.hdr);
hreq->response.hdr_total_len = 0;
hreq->response.hdr_acked_len = 0;
hreq->response.ftr_acked_len = 0;
hreq->smsg = NULL;
hreq->fd = NULL;
hreq->rlen = 0;
hreq->alen = 0;
hreq->is_stream = 0;
#if defined SHFS_STATS && defined SHFS_STATS_HTTP && defined SHFS_STATS_HTTP_DPC
hreq->stats.dpc_i = 0;
#endif
++hsess->hsrv->nb_reqs;
return hreq;
}
static inline void httpreq_close(struct http_req *hreq)
{
struct http_sess *hsess = hreq->hsess;
printd("Closing request %p...\n", hreq);
/* unlink session from ioretry chain if it was linked before */
httpsess_unregister_ioretry(hreq->hsess);
/* close open file */
if (hreq->fd) {
switch (hreq->type) {
case HRT_FIOMSG:
printd("Release request %p from file I/O\n", hreq);
httpreq_fio_close(hreq);
break;
case HRT_LINKMSG:
printd("Release request %p from link\n", hreq);
httpreq_link_close(hreq);
break;
default:
break;
}
shfs_fio_close(hreq->fd);
}
mempool_put(hreq->pobj);
--hsess->hsrv->nb_reqs;
printd("Request %p destroyed\n", hreq);
}
static err_t httpsess_accept(void *argp, struct tcp_pcb *new_tpcb, err_t err)
{
struct mempool_obj *hsobj;
struct http_sess *hsess;
if (err != ERR_OK)
goto err_out;
hsobj = mempool_pick(hs->sess_pool);
if (!hsobj) {
err = ERR_MEM;
goto err_out;
}
hsess = hsobj->data;
hsess->pobj = hsobj;
hsess->hsrv = hs;
hsess->sent_infly = 0;
/* setup request queue */
hsess->cpreq = httpreq_open(hsess);
if (!hsess->cpreq) {
err = ERR_MEM;
goto err_free_hsess;
}
hsess->rqueue_head = NULL;
hsess->rqueue_tail = NULL;
hsess->rqueue_len = 0;
hsess->aqueue_head = NULL;
hsess->aqueue_tail = NULL;
hsess->retry_replychain = 0;
hsess->_in_respond = 0;
/* register tpcb */
hsess->tpcb = new_tpcb;
tcp_arg (hsess->tpcb, hsess); /* argp for callbacks */
tcp_recv(hsess->tpcb, httpsess_recv); /* recv callback */
tcp_sent(hsess->tpcb, httpsess_sent); /* sent ack callback */
tcp_err (hsess->tpcb, httpsess_error); /* err callback */
tcp_poll(hsess->tpcb, httpsess_poll, HTTP_POLL_INTERVAL); /* poll callback */
tcp_setprio(hsess->tpcb, HTTP_TCP_PRIO);
/* Turn on TCP Keepalive */
hsess->tpcb->so_options |= SOF_KEEPALIVE;
hsess->tpcb->keep_intvl = (HTTP_TCPKEEPALIVE_TIMEOUT * 1000);
hsess->tpcb->keep_idle = (HTTP_TCPKEEPALIVE_IDLE * 1000);
hsess->tpcb->keep_cnt = 1;
/* init parser */
hsess->parser.data = (void *) &hsess->cpreq->request.hdr;
http_parser_init(&(hsess)->parser, HTTP_REQUEST);
/* reset HTTP keep alive */
httpsess_reset_keepalive((hsess));
/* register session to session list */
if (!hs->hsess_head) {
hs->hsess_head = hsess;
hsess->prev = NULL;
} else {
hs->hsess_tail->next = hsess;
hsess->prev = hs->hsess_tail;
}
hsess->next = NULL;
hs->hsess_tail = hsess;
dlist_init_el(hsess, ioretry_chain);
hsess->state = HSS_ESTABLISHED;
++hs->nb_sess;
printd("New HTTP session accepted on server %p "
"(currently, there are %"PRIu16"/%"PRIu16" open sessions)\n",
hs, hs->nb_sess, hs->max_nb_sess);
return 0;
err_free_hsess:
mempool_put(hsobj);
err_out:
printd("Session establishment declined on server %p "
"(currently, there are %"PRIu16"/%"PRIu16" open sessions)\n",
hs, hs->nb_sess, hs->max_nb_sess);
return err;
}
static err_t httpsess_close(struct http_sess *hsess, enum http_sess_close type)
{
struct http_req *hreq;
err_t err;
ASSERT(hsess != NULL);
printd("%s session %p (caller: 0x%x)\n",
(type == HSC_ABORT ? "Aborting" :
(type == HSC_CLOSE ? "Closing" : "Killing")),
hsess,
get_caller());
hsess->state = -99999;
/* disable tcp connection */
tcp_arg(hsess->tpcb, NULL);
tcp_sent(hsess->tpcb, NULL);
tcp_recv(hsess->tpcb, NULL);
tcp_sent(hsess->tpcb, NULL);
tcp_err(hsess->tpcb, NULL);
tcp_poll(hsess->tpcb, NULL, 0);
/* close unserved requests */
if (dlist_is_linked(hsess, hs->ioretry_chain, ioretry_chain))
printd(" Session is linked to IORetry list, removing it\n");
httpsess_unregister_ioretry(hsess);
for (hreq = hsess->aqueue_head; hreq != NULL; hreq = hreq->next)
httpreq_close(hreq);
for (hreq = hsess->rqueue_head; hreq != NULL; hreq = hreq->next)
httpreq_close(hreq);
if (hsess->cpreq)
httpreq_close(hsess->cpreq);
/* terminate connection */
switch (type) {
case HSC_CLOSE:
err = tcp_close(hsess->tpcb);
if (likely(err == ERR_OK))
break;
case HSC_ABORT:
tcp_abort(hsess->tpcb);
err = ERR_ABRT; /* lwip callback functions need to be notified */
break;
default: /* HSC_KILL */
err = ERR_OK;
break;
}
/* unlink session from session list */
if (hsess->prev)
hsess->prev->next = hsess->next;
else
hs->hsess_head = hsess->next;
if (hsess->next)
hsess->next->prev = hsess->prev;
else
hs->hsess_tail = hsess->prev;
/* release memory */
mempool_put(hsess->pobj);
--hs->nb_sess;
return err;
}
static err_t httpsess_recv(void *argp, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
/* lwIP pbuf handling depending on return value:
* On ERR_ABRT indicates an aborted session to lwIP
* On !ERR_OK, the pbuf is hold back and repassed later
* On ERR_OK, we have to free the buffer here and inform the
* sender about the received data */
struct http_sess *hsess = argp;
struct http_req *cpreq;
struct pbuf *q;
unsigned int prev_rqueue_len;
size_t plen;
err_t ret = ERR_OK;
if (unlikely(!p || err != ERR_OK)) {
/* receive error: kill connection */
printd("Unexpected session error (p=%p, err=%d)\n", p, err);
if (p) {
tcp_recved(tpcb, p->tot_len);
pbuf_free(p);
}
return httpsess_close(hsess, HSC_ABORT);
}
if (unlikely(hsess->retry_replychain)) {
/* We end up here when we were not able to start the reply chain
* The pbuf is reinjected by lwIP (since we returned ERR_MEM previously).
* Hence, we need to ignore it because it has been already
* processed by the parser */
printd("Try to start reply chain again...\n");
ret = httpsess_respond(hsess);
if (ret == ERR_MEM) {
printd("Replying failed: Out of memory\n");
goto out; /* still did not work, retry it again later */
}
if (ret == ERR_ABRT)
goto out; /* connection got aborted */
hsess->retry_replychain = 0;
goto out;
}
cpreq = hsess->cpreq;
if (unlikely(!cpreq || hsess->state != HSS_ESTABLISHED)) {
/* We don't have an object allocated for parsing the requests or
* we are about to close the connection, thus ignoring all further
* incoming data
* This can only happen after the first request was processed
* and there are two reasons for this:
* 1) We couldn't allocate such an object previously
* 2) User requested connection close
* However, we will ignore all further incoming data
*
* TODO: Is ignoring a clean way to handle these cases because
* we will send ack on the wire? */
printd("Ignoring unrelated data (p=%p, len=%d)\n", p, p->tot_len);
goto out;
}
switch (cpreq->state) {
case HRS_PARSING_HDR:
case HRS_PARSING_MSG:
/* feed parser */
prev_rqueue_len = hsess->rqueue_len;
httpsess_halt_keepalive(hsess);
for (q = p; q != NULL; q = q->next) {
plen = http_parser_execute(&hsess->parser, &_http_parser_settings,
q->payload, q->len);
if (unlikely(hsess->parser.upgrade)) {
/* protocol upgrade requested */
printd("Unsupported HTTP protocol upgrade requested: Dropping connection...\n");
ret = httpsess_close(hsess, HSC_CLOSE);
goto out;
}
if (unlikely(plen != q->len)) {
/* less data was parsed: this happens only when
* there was a parsing error */
printd("HTTP protocol parsing error: Dropping connection...\n");
ret = httpsess_close(hsess, HSC_CLOSE);
goto out;
}
}
printd("prev_rqueue_len == %u, hsess->rqueue_len = %u\n",
prev_rqueue_len, hsess->rqueue_len);
if (prev_rqueue_len == 0 && hsess->rqueue_len) {
/* new request came in: start reply chain */
printd("Starting reply chain...\n");
ret = httpsess_respond(hsess);
if (ret == ERR_MEM) {
/* out of memory for replying.
* We will retry it later by holding the current
* pbuf back in the stack */
printd("Replying failed: Out of memory\n");
hsess->retry_replychain = 1;
goto out;
}
goto out;
}
break;
default:
/* this case never happens */
printd("FATAL: Invalid receive state\n");
break;
}
out:
if (likely(ret != ERR_MEM)) {
tcp_recved(tpcb, p->tot_len);
pbuf_free(p);
}
return ret;
}
static void httpsess_error(void *argp, err_t err)
{
struct http_sess *hsess = argp;
printd("Killing HTTP session %p due to error: %d\n", hsess, err);
httpsess_close(hsess, HSC_KILL); /* drop connection */
}
/* Is called every 5 sec */
static err_t httpsess_poll(void *argp, struct tcp_pcb *tpcb)
{
struct http_sess *hsess = argp;
printd("poll session %p\n", hsess);
if (unlikely(hsess->keepalive_timer == 0)) {
/* keepalive timeout: close connection */
if (hsess->sent_infly == 0) {
return httpsess_close(hsess, HSC_CLOSE);
} else {
/* we need to wait for the client until it ack'ed */
hsess->state = HSS_CLOSING;
}
}
if (hsess->keepalive_timer > 0)
--hsess->keepalive_timer;
return ERR_OK;
}
/**
* Call tcp_write() in a loop trying smaller and smaller length
*
* @param pcb tcp_pcb to send
* @param ptr Data to send
* @param length Length of data to send (in/out: on return, contains the
* amount of data sent)
* Note: length can be at most tcp_sndbuf()
* @param apiflags directly passed to tcp_write
* @return the return value of tcp_write
*/
err_t httpsess_write(struct http_sess *hsess, const void* buf, size_t *len, uint8_t apiflags)
{
struct tcp_pcb *pcb = hsess->tpcb;
register size_t l, s;
uint16_t slen;
err_t err;
s = 0;
l = *len;
err = ERR_OK;
try_next:
slen = (uint16_t) min3(l, tcp_sndbuf(pcb), UINT16_MAX);
if (!slen)
goto out;
try_again:
printd("tcp_write(buf=@%p, slen=%"PRIu16", left=%"PRIu64", sndbuf=%"PRIu32", sndqueuelen=%"PRIu16")\n",
buf, slen, l, (uint32_t) tcp_sndbuf(pcb), (uint16_t) tcp_sndqueuelen(pcb));
err = tcp_write(pcb, buf, slen, apiflags);
if (unlikely(err == ERR_MEM)) {
if (slen <= 1 || !tcp_sndbuf(pcb) ||
(tcp_sndqueuelen(pcb) >= TCP_SND_QUEUELEN)) {
printd("tcp_write returned memory error\n");
goto out; /* no need to try smaller sizes, send buffers are full */
} else {
printd("tcp_write returned memory error, retry with half send length\n", err);
slen >>= 1; /* l /= 2 */
goto try_again;
}
}
if (likely(err == ERR_OK)) {
s += slen;
l -= slen;
if (l) {
buf = (const void *) ((uintptr_t) buf + slen);
goto try_next;
}
}
out:
#ifdef HTTPREQ_LOW_SNDBUF
/* workaround that enforces an ACK because our send buffer is actually smaller than the TCP window */
if (tcp_sndbuf(pcb) == 0)
httpsess_flush(hsess);
#endif
printd("leaving: sent %"PRIu64"/%"PRIu64" bytes\n", (uint64_t) s, (uint64_t) (*len));
hsess->sent_infly += s;
*len = s;
return err;
}
static err_t httpsess_sent(void *argp, struct tcp_pcb *tpcb, uint16_t len)
{
struct http_sess *hsess = argp;
printd("ACK for session %p\n", hsess);
hsess->sent_infly -= len;
switch (hsess->state) {
case HSS_ESTABLISHED:
if (len)
return httpsess_acknowledge(hsess, len); /* will continue replying */
break;
case HSS_CLOSING:
/* connection is about to be closed:
* check if all bytes were transmitted
* and close it if so */
if (hsess->sent_infly == 0)
return httpsess_close(hsess, HSC_CLOSE);
break;
default:
printd("ERROR: session %p in unknown state: %d\n",
hsess, hsess->state);
break;
}
return ERR_OK;
}
/*******************************************************************************
* HTTP request parsing
******************************************************************************/
static int httprecv_hdr_url(struct http_parser *parser, const char *buf, size_t len)
{
struct http_sess *hsess = container_of(parser, struct http_sess, parser);
struct http_req *hreq = hsess->cpreq;
register size_t i, curpos, maxlen;
curpos = hreq->request.url_len;
maxlen = sizeof(hreq->request.url) - 1 - curpos;
if (unlikely(len > maxlen)) {
hreq->request.url_overflow = 1; /* Out of memory */
len = maxlen;
}
if (!hreq->request.url_argp) {
for (i = 0; i < len; ++i) {
hreq->request.url[curpos + i] = buf[i];
if (buf[i] == HTTPURL_ARGS_INDICATOR) {
hreq->request.url_argp = &hreq->request.url[curpos + i];
hreq->request.url_len += i;
len -= i;
buf += i;
curpos += i;
goto memcpy;
}
}
} else {
memcpy:
MEMCPY(&hreq->request.url[curpos], buf, len);
}
hreq->request.url_len += len;
return 0;
}
static int httprecv_req_complete(struct http_parser *parser)
{
struct http_sess *hsess = container_of(parser, struct http_sess, parser);
struct http_req *hreq;
printd("Parsing finalized: Enqueueing request...\n");
/* because we finished parsing at this point, we remove the http request object
* from the current parsing and enqueue it to the reply queue
* We might try to add a object new one later, if this request will reply
* keepalive enabled
* Note: The parser does not have to be resetted since it continues parsing
* the input */
hreq = hsess->cpreq;
hsess->cpreq = NULL;
if (hsess->rqueue_tail)
hsess->rqueue_tail->next = hreq;
else
hsess->rqueue_head = hreq;
hsess->rqueue_tail = hreq;
++hsess->rqueue_len;
/* Because keepalive is only 0 when parsing is completed or client
* requested it, we try here to allocate a new request object if this
* was not the last message */
hsess->keepalive = http_should_keep_alive(&hsess->parser);
if (hsess->keepalive) {
hsess->cpreq = httpreq_open(hsess);
if (!hsess->cpreq) {
/* Could not allocate next object: close connection */
printd("Could not allocate a new request object: "
"Connection will close after serving is finished\n");
hsess->keepalive = 0;
}
}
/* copy data */
hreq->request.keepalive = hsess->keepalive;
hreq->request.http_major = parser->http_major;
hreq->request.http_minor = parser->http_minor;
hreq->request.http_errno = parser->http_errno;
hreq->request.method = parser->method;
/* finalize request lines by adding terminating '\0' */
http_recvhdr_terminate(&hreq->request.hdr);
hreq->request.url[hreq->request.url_len++] = '\0';
hreq->state = HRS_PREPARING_HDR;
return 0;
}
static inline void httpreq_prepare_hdr(struct http_req *hreq)
{
size_t url_offset = 0;
size_t nb_slines = 0;
size_t nb_dlines = 0;
#ifdef HTTP_TESTFILES
hash512_t h;
#endif
#if defined SHFS_STATS && defined SHFS_STATS_HTTP && defined SHFS_STATS_HTTP_DPC
register unsigned int i;
#endif
#ifdef HTTP_DEBUG
unsigned l;
#endif
char strsbuf[64];
char strlbuf[128];
/* check request method (GET, POST, ...) */
if (hreq->request.method != HTTP_GET) {
printd("Invalid/unsupported request method: %u HTTP/%hu.%hu\n",
hreq->request.method,
hreq->request.http_major,
hreq->request.http_minor);
goto err501_hdr; /* 501 Invalid request */
}
#ifdef HTTP_DEBUG
printd("GET %s HTTP/%hu.%hu\n",
hreq->request.url,
hreq->request.http_major,
hreq->request.http_minor);
for (l = 0; l < hreq->request.hdr.nb_lines; ++l) {
printd(" %s: %s\n",
hreq->request.hdr.line[l].field.b,
hreq->request.hdr.line[l].value.b);
}
#endif
/* try to open requested file and construct header */
/* eliminate leading '/'s */
while (hreq->request.url[url_offset] == '/')
++url_offset;
#ifdef HTTP_URL_CUTARGS
/* remove args from URL when there was a filename passed (-> "open by filename") */
if (hreq->request.url_argp &&
&(hreq->request.url[url_offset]) != hreq->request.url_argp)
*(hreq->request.url_argp) = '\0';
#endif
#ifdef HTTP_TESTFILES
if ((hreq->request.url[url_offset] == HTTPURL_ARGS_INDICATOR) &&
(hash_parse(&hreq->request.url[url_offset + 1], h, shfs_vol.hlen) == 0)) {
if (hash_is_zero(h, shfs_vol.hlen))
goto testfile_hdr0; /* empty testfile */
if (hash_is_max(h, shfs_vol.hlen))
goto testfile_hdr1; /* infinite testfile */
}
#endif
hreq->fd = shfs_fio_open(&hreq->request.url[url_offset]);
if (!hreq->fd) {
printd("Could not open requested file '%s': %s\n", &hreq->request.url[url_offset], strerror(errno));
if (errno == ENOENT || errno == ENODEV)
goto err404_hdr; /* 404 File not found */
goto err500_hdr; /* 500 Internal server error */
}
#if defined SHFS_STATS && defined SHFS_STATS_HTTP
hreq->stats.el_stats = shfs_stats_from_fd(hreq->fd);
#endif
if (shfs_fio_islink(hreq->fd)) {
if (shfs_fio_link_type(hreq->fd) == SHFS_LTYPE_REDIRECT)
goto red307_hdr; /* 307 temporary moved */
/**
* REMOTE LINK HANDLING
* Note: header will be built in next phase (HRS_BUILDING_HDR)
* Here, upstream connection is established (if non existent)
*/
hreq->type = HRT_LINKMSG;
if (httpreq_link_prepare_hdr(hreq) < 0) {
shfs_fio_close(hreq->fd);
hreq->fd = NULL;
goto err500_hdr;
}
return;
}
/**
* LOCAL FILE HEADER
*/
/* call build HDR directly on local file I/O -> skip HRS_BUILDING_HDR phase switch */
hreq->type = HRT_FIOMSG;
httpreq_fio_build_hdr(hreq);
#if defined SHFS_STATS && defined SHFS_STATS_HTTP && defined SHFS_STATS_HTTP_DPC
for (i = 0; i < SHFS_STATS_HTTP_DPCR; ++i)
hreq->stats.dpc_threshold[i] = SHFS_STATS_HTTP_DPC_THRESHOLD(hreq->f.fsize, i);
#endif
hreq->state = HRS_FINALIZING_HDR;
return;
/**
* REDIRECT HEADER
*/
red307_hdr:
hreq->response.code = 307;
http_sendhdr_add_shdr(&hreq->response.hdr, &nb_slines,
HTTP_SHDR_307(hreq->request.http_major, hreq->request.http_minor));
strshfshost(strsbuf, sizeof(strsbuf),
shfs_fio_link_rhost(hreq->fd));
shfs_fio_link_rpath(hreq->fd, strlbuf, sizeof(strlbuf));
http_sendhdr_add_dline(&hreq->response.hdr, &nb_dlines,
"%s: http://%s:%"PRIu16"/%s\r\n", _http_dhdr[HTTP_DHDR_LOCATION],
strsbuf, shfs_fio_link_rport(hreq->fd), strlbuf);
hreq->type = HRT_NOMSG;
goto err_out;
/**
* TESTFILE HEADER
*/
#ifdef HTTP_TESTFILES
testfile_hdr0: /* testfile with zero length */
hreq->response.code = 200;
http_sendhdr_add_shdr(&hreq->response.hdr, &nb_slines,
HTTP_SHDR_200(hreq->request.http_major, hreq->request.http_minor));
http_sendhdr_add_shdr(&hreq->response.hdr, &nb_slines, HTTP_SHDR_BINARY);
/* Content length */
http_sendhdr_add_dline(&hreq->response.hdr, &nb_dlines,
"%s: %"PRIu64"\r\n", _http_dhdr[HTTP_DHDR_SIZE], 0);
hreq->type = HRT_NOMSG;
goto err_out;
testfile_hdr1: /* infinite testfile */
hreq->response.code = 200;
http_sendhdr_add_shdr(&hreq->response.hdr, &nb_slines,
HTTP_SHDR_200(hreq->request.http_major, hreq->request.http_minor));
http_sendhdr_add_shdr(&hreq->response.hdr, &nb_slines, HTTP_SHDR_BINARY);
hreq->rlen = sizeof(_http_testfile) - 1;
hreq->type = HRT_SMSG_INF;
hreq->smsg = _http_testfile;
hreq->is_stream = 1;
goto err_out;
#endif
/**
* ERROR HEADERS
*/
err404_hdr:
/* 404 File not found */
hreq->response.code = 404;
http_sendhdr_add_shdr(&hreq->response.hdr, &nb_slines,
HTTP_SHDR_404(hreq->request.http_major, hreq->request.http_minor));
http_sendhdr_add_shdr(&hreq->response.hdr, &nb_slines,HTTP_SHDR_HTML);
http_sendhdr_add_shdr(&hreq->response.hdr, &nb_slines,HTTP_SHDR_NOCACHE);
/* Content length */
http_sendhdr_add_dline(&hreq->response.hdr, &nb_dlines,
"%s: %"PRIu64"\r\n", _http_dhdr[HTTP_DHDR_SIZE],
_http_err404p_len);
hreq->type = HRT_SMSG;
hreq->smsg = _http_err404p;
hreq->rlen = _http_err404p_len;
goto err_out;
err500_hdr:
/* 500 Internal server error */
hreq->response.code = 500;
http_sendhdr_add_shdr(&hreq->response.hdr, &nb_slines,
HTTP_SHDR_500(hreq->request.http_major, hreq->request.http_minor));
http_sendhdr_add_shdr(&hreq->response.hdr, &nb_slines, HTTP_SHDR_HTML);
/* Content length */
http_sendhdr_add_dline(&hreq->response.hdr, &nb_dlines,
"%s: %"PRIu64"\r\n", _http_dhdr[HTTP_DHDR_SIZE],
_http_err500p_len);
hreq->type = HRT_SMSG;
hreq->smsg = _http_err500p;
hreq->rlen = _http_err500p_len;
goto err_out;
err501_hdr:
/* 501 Invalid request */
hreq->response.code = 501;
http_sendhdr_add_shdr(&hreq->response.hdr, &nb_slines,
HTTP_SHDR_501(hreq->request.http_major, hreq->request.http_minor));
http_sendhdr_add_shdr(&hreq->response.hdr, &nb_slines, HTTP_SHDR_HTML);
/* Content length */
http_sendhdr_add_dline(&hreq->response.hdr, &nb_dlines,
"%s: %"PRIu64"\r\n", _http_dhdr[HTTP_DHDR_SIZE],
_http_err501p_len);
hreq->type = HRT_SMSG;
hreq->smsg = _http_err501p;
hreq->rlen = _http_err501p_len;
goto err_out;
err_out:
http_sendhdr_set_nbslines(&hreq->response.hdr, nb_slines);
http_sendhdr_set_nbdlines(&hreq->response.hdr, nb_dlines);
hreq->state = HRS_FINALIZING_HDR;
return;
}
static inline void httpreq_build_hdr(struct http_req *hreq)
{
size_t nb_slines = 0;
size_t nb_dlines = 0;
int ret;
/* For now, just remote links utilize this phase for connecting to
* upstream server. All other reponses are already build
* Because of this it might be possible that this function needs
* to be called multiple times until the connection was established */
ASSERT(shfs_fio_islink(hreq->fd));
ASSERT(shfs_fio_link_type(hreq->fd) != SHFS_LTYPE_REDIRECT);
ret = httpreq_link_build_hdr(hreq);
if (ret == -EAGAIN)
return; /* stay in current phase because we are not done yet */
if (ret < 0) {
httpreq_link_close(hreq);
shfs_fio_close(hreq->fd);
goto err503_hdr; /* an unknown error happend -> send out a 503 error page instead */
}
/* we are done -> switch to next phase */
hreq->state = HRS_FINALIZING_HDR;
return;
err503_hdr:
/* 503 Service unavailable */
hreq->response.code = 503;
http_sendhdr_add_shdr(&hreq->response.hdr, &nb_slines,
HTTP_SHDR_503(hreq->request.http_major, hreq->request.http_minor));
http_sendhdr_add_shdr(&hreq->response.hdr, &nb_slines, HTTP_SHDR_HTML);
/* Content length */
http_sendhdr_add_dline(&hreq->response.hdr, &nb_dlines,
"%s: %"PRIu64"\r\n", _http_dhdr[HTTP_DHDR_SIZE],
_http_err503p_len);
/* Retry-after (TODO: replace the hard-coded 2 second) */
http_sendhdr_add_dline(&hreq->response.hdr, &nb_dlines,
"%s: %u\r\n", _http_dhdr[HTTP_DHDR_RETRY],
2);
hreq->type = HRT_SMSG;
hreq->smsg = _http_err503p;
hreq->rlen = _http_err503p_len;
http_sendhdr_set_nbslines(&hreq->response.hdr, nb_slines);
http_sendhdr_set_nbdlines(&hreq->response.hdr, nb_dlines);
hreq->state = HRS_FINALIZING_HDR;
return;
}
static inline void httpreq_finalize_hdr(struct http_req *hreq)
{
size_t nb_slines = http_sendhdr_get_nbslines(&hreq->response.hdr);
size_t nb_dlines = http_sendhdr_get_nbdlines(&hreq->response.hdr);
#ifdef HTTP_DEBUG
register unsigned l;
#endif
/* Default header lines */
http_sendhdr_add_shdr(&hreq->response.hdr, &nb_slines, HTTP_SHDR_SERVER);
/* keepalive */