-
Notifications
You must be signed in to change notification settings - Fork 931
Expand file tree
/
Copy pathcache.c
More file actions
3194 lines (2717 loc) · 93.1 KB
/
cache.c
File metadata and controls
3194 lines (2717 loc) · 93.1 KB
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
/*
* Cache management
*
* Copyright 2017 HAProxy Technologies
* William Lallemand <[email protected]>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <import/eb32tree.h>
#include <import/sha1.h>
#include <haproxy/action-t.h>
#include <haproxy/api.h>
#include <haproxy/applet.h>
#include <haproxy/cfgparse.h>
#include <haproxy/channel.h>
#include <haproxy/cli.h>
#include <haproxy/errors.h>
#include <haproxy/filters.h>
#include <haproxy/hash.h>
#include <haproxy/http.h>
#include <haproxy/http_ana.h>
#include <haproxy/http_htx.h>
#include <haproxy/http_rules.h>
#include <haproxy/htx.h>
#include <haproxy/net_helper.h>
#include <haproxy/proxy.h>
#include <haproxy/sample.h>
#include <haproxy/sc_strm.h>
#include <haproxy/shctx.h>
#include <haproxy/stconn.h>
#include <haproxy/stream.h>
#include <haproxy/tools.h>
#include <haproxy/xxhash.h>
#define CACHE_FLT_F_IMPLICIT_DECL 0x00000001 /* The cache filtre was implicitly declared (ie without
* the filter keyword) */
#define CACHE_FLT_INIT 0x00000002 /* Whether the cache name was freed. */
static uint64_t cache_hash_seed = 0;
const char *cache_store_flt_id = "cache store filter";
extern struct applet http_cache_applet;
struct flt_ops cache_ops;
struct cache_tree {
struct eb_root entries; /* head of cache entries based on keys */
__decl_thread(HA_RWLOCK_T lock);
struct list cleanup_list;
__decl_thread(HA_SPINLOCK_T cleanup_lock);
} ALIGNED(64);
struct cache {
struct cache_tree trees[CACHE_TREE_NUM];
struct list list; /* cache linked list */
unsigned int maxage; /* max-age */
unsigned int maxblocks;
unsigned int maxobjsz; /* max-object-size (in bytes) */
unsigned int max_secondary_entries; /* maximum number of secondary entries with the same primary hash */
uint8_t vary_processing_enabled; /* boolean : manage Vary header (disabled by default) */
char id[33]; /* cache name */
};
/* the appctx context of a cache applet, stored in appctx->svcctx */
struct cache_appctx {
struct cache *cache;
struct cache_tree *cache_tree;
struct cache_entry *entry; /* Entry to be sent from cache. */
unsigned int sent; /* The number of bytes already sent for this cache entry. */
unsigned int offset; /* start offset of remaining data relative to beginning of the next block */
unsigned int rem_data; /* Remaining bytes for the last data block (HTX only, 0 means process next block) */
unsigned int send_notmodified:1; /* In case of conditional request, we might want to send a "304 Not Modified" response instead of the stored data. */
unsigned int unused:31;
/* 4 bytes hole here */
struct shared_block *next; /* The next block of data to be sent for this cache entry. */
};
/* cache config for filters */
struct cache_flt_conf {
union {
struct cache *cache; /* cache used by the filter */
char *name; /* cache name used during conf parsing */
} c;
unsigned int flags; /* CACHE_FLT_F_* */
};
/* CLI context used during "show cache" */
struct show_cache_ctx {
struct cache *cache;
struct cache_tree *cache_tree;
uint next_key;
};
/*
* Vary-related structures and functions
*/
enum vary_header_bit {
VARY_ACCEPT_ENCODING = (1 << 0),
VARY_REFERER = (1 << 1),
VARY_ORIGIN = (1 << 2),
VARY_LAST /* should always be last */
};
/*
* Encoding list extracted from
* https://www.iana.org/assignments/http-parameters/http-parameters.xhtml
* and RFC7231#5.3.4.
*/
enum vary_encoding {
VARY_ENCODING_GZIP = (1 << 0),
VARY_ENCODING_DEFLATE = (1 << 1),
VARY_ENCODING_BR = (1 << 2),
VARY_ENCODING_COMPRESS = (1 << 3),
VARY_ENCODING_AES128GCM = (1 << 4),
VARY_ENCODING_EXI = (1 << 5),
VARY_ENCODING_PACK200_GZIP = (1 << 6),
VARY_ENCODING_ZSTD = (1 << 7),
VARY_ENCODING_IDENTITY = (1 << 8),
VARY_ENCODING_STAR = (1 << 9),
VARY_ENCODING_OTHER = (1 << 10)
};
struct vary_hashing_information {
struct ist hdr_name; /* Header name */
enum vary_header_bit value; /* Bit representing the header in a vary signature */
unsigned int hash_length; /* Size of the sub hash for this header's value */
int(*norm_fn)(struct htx*,struct ist hdr_name,char* buf,unsigned int* buf_len); /* Normalization function */
int(*cmp_fn)(const void *ref, const void *new, unsigned int len); /* Comparison function, should return 0 if the hashes are alike */
};
static int http_request_prebuild_full_secondary_key(struct stream *s);
static int http_request_build_secondary_key(struct stream *s, int vary_signature);
static int http_request_reduce_secondary_key(unsigned int vary_signature,
char prebuilt_key[HTTP_CACHE_SEC_KEY_LEN]);
static int parse_encoding_value(struct ist value, unsigned int *encoding_value,
unsigned int *has_null_weight);
static int accept_encoding_normalizer(struct htx *htx, struct ist hdr_name,
char *buf, unsigned int *buf_len);
static int default_normalizer(struct htx *htx, struct ist hdr_name,
char *buf, unsigned int *buf_len);
static int accept_encoding_bitmap_cmp(const void *ref, const void *new, unsigned int len);
/* Warning : do not forget to update HTTP_CACHE_SEC_KEY_LEN when new items are
* added to this array. */
const struct vary_hashing_information vary_information[] = {
{ IST("accept-encoding"), VARY_ACCEPT_ENCODING, sizeof(uint32_t), &accept_encoding_normalizer, &accept_encoding_bitmap_cmp },
{ IST("referer"), VARY_REFERER, sizeof(uint64_t), &default_normalizer, NULL },
{ IST("origin"), VARY_ORIGIN, sizeof(uint64_t), &default_normalizer, NULL },
};
static inline void cache_rdlock(struct cache_tree *cache)
{
HA_RWLOCK_RDLOCK(CACHE_LOCK, &cache->lock);
}
static inline void cache_rdunlock(struct cache_tree *cache)
{
HA_RWLOCK_RDUNLOCK(CACHE_LOCK, &cache->lock);
}
static inline void cache_wrlock(struct cache_tree *cache)
{
HA_RWLOCK_WRLOCK(CACHE_LOCK, &cache->lock);
}
static inline void cache_wrunlock(struct cache_tree *cache)
{
HA_RWLOCK_WRUNLOCK(CACHE_LOCK, &cache->lock);
}
/*
* cache ctx for filters
*/
struct cache_st {
struct shared_block *first_block;
struct list detached_head;
};
#define DEFAULT_MAX_SECONDARY_ENTRY 10
struct cache_entry {
unsigned int complete; /* An entry won't be valid until complete is not null. */
unsigned int latest_validation; /* latest validation date */
unsigned int expire; /* expiration date (wall clock time) */
unsigned int age; /* Origin server "Age" header value */
unsigned int body_size; /* Size of the body */
int refcount;
struct eb32_node eb; /* ebtree node used to hold the cache object */
char hash[20];
struct list cleanup_list;/* List used between the cache_free_blocks and cache_reserve_finish calls */
char secondary_key[HTTP_CACHE_SEC_KEY_LEN]; /* Optional secondary key. */
unsigned int secondary_key_signature; /* Bitfield of the HTTP headers that should be used
* to build secondary keys for this cache entry. */
unsigned int secondary_entries_count; /* Should only be filled in the last entry of a list of dup entries */
unsigned int last_clear_ts; /* Timestamp of the last call to clear_expired_duplicates. */
unsigned int etag_length; /* Length of the ETag value (if one was found in the response). */
unsigned int etag_offset; /* Offset of the ETag value in the data buffer. */
time_t last_modified; /* Origin server "Last-Modified" header value converted in
* seconds since epoch. If no "Last-Modified"
* header is found, use "Date" header value,
* otherwise use reception time. This field will
* be used in case of an "If-Modified-Since"-based
* conditional request. */
unsigned char data[0];
};
#define CACHE_BLOCKSIZE 1024
#define CACHE_ENTRY_MAX_AGE 2147483648U
static struct list caches = LIST_HEAD_INIT(caches);
static struct list caches_config = LIST_HEAD_INIT(caches_config); /* cache config to init */
static struct cache *tmp_cache_config = NULL;
DECLARE_STATIC_TYPED_POOL(pool_head_cache_st, "cache_st", struct cache_st);
static struct eb32_node *insert_entry(struct cache *cache, struct cache_tree *tree, struct cache_entry *new_entry);
static void delete_entry(struct cache_entry *del_entry);
static inline void release_entry_locked(struct cache_tree *cache, struct cache_entry *entry);
static inline void release_entry_unlocked(struct cache_tree *cache, struct cache_entry *entry);
/*
* Find a cache_entry in the <cache>'s tree that has the hash <hash>.
* If <delete_expired> is 0 then the entry is left untouched if it is found but
* is already expired, and NULL is returned. Otherwise, the expired entry is
* removed from the tree and NULL is returned.
* Returns a valid (not expired) cache_tree pointer.
* The returned entry is not retained, it should be explicitly retained only
* when necessary.
*
* This function must be called under a cache lock, either read if
* delete_expired==0, write otherwise.
*/
struct cache_entry *get_entry(struct cache_tree *cache_tree, char *hash, int delete_expired)
{
struct eb32_node *node;
struct cache_entry *entry;
node = eb32_lookup(&cache_tree->entries, read_u32(hash));
if (!node)
return NULL;
entry = eb32_entry(node, struct cache_entry, eb);
/* if that's not the right node */
if (memcmp(entry->hash, hash, sizeof(entry->hash)))
return NULL;
if (entry->expire > date.tv_sec) {
return entry;
} else if (delete_expired) {
release_entry_locked(cache_tree, entry);
}
return NULL;
}
/*
* Increment a cache_entry's reference counter.
*/
static void retain_entry(struct cache_entry *entry)
{
if (entry)
HA_ATOMIC_INC(&entry->refcount);
}
/*
* Decrement a cache_entry's reference counter and remove it from the <cache>'s
* tree if the reference counter becomes 0.
* If <needs_locking> is 0 then the cache lock was already taken by the caller,
* otherwise it must be taken in write mode before actually deleting the entry.
*/
static void release_entry(struct cache_tree *cache, struct cache_entry *entry, int needs_locking)
{
if (!entry)
return;
if (HA_ATOMIC_SUB_FETCH(&entry->refcount, 1) <= 0) {
if (needs_locking) {
cache_wrlock(cache);
/* The value might have changed between the last time we
* checked it and now, we need to recheck it just in
* case.
*/
if (HA_ATOMIC_LOAD(&entry->refcount) > 0) {
cache_wrunlock(cache);
return;
}
}
delete_entry(entry);
if (needs_locking) {
cache_wrunlock(cache);
}
}
}
/*
* Decrement a cache_entry's reference counter and remove it from the <cache>'s
* tree if the reference counter becomes 0.
* This function must be called under the cache lock in write mode.
*/
static inline void release_entry_locked(struct cache_tree *cache, struct cache_entry *entry)
{
release_entry(cache, entry, 0);
}
/*
* Decrement a cache_entry's reference counter and remove it from the <cache>'s
* tree if the reference counter becomes 0.
* This function must not be called under the cache lock or the shctx lock. The
* cache lock might be taken in write mode (if the entry gets deleted).
*/
static inline void release_entry_unlocked(struct cache_tree *cache, struct cache_entry *entry)
{
release_entry(cache, entry, 1);
}
/*
* Compare a newly built secondary key to the one found in a cache_entry.
* Every sub-part of the key is compared to the reference through the dedicated
* comparison function of the sub-part (that might do more than a simple
* memcmp).
* Returns 0 if the keys are alike.
*/
static int secondary_key_cmp(const char *ref_key, const char *new_key)
{
int retval = 0;
size_t idx = 0;
unsigned int offset = 0;
const struct vary_hashing_information *info;
for (idx = 0; idx < sizeof(vary_information)/sizeof(*vary_information) && !retval; ++idx) {
info = &vary_information[idx];
if (info->cmp_fn)
retval = info->cmp_fn(&ref_key[offset], &new_key[offset], info->hash_length);
else
retval = memcmp(&ref_key[offset], &new_key[offset], info->hash_length);
offset += info->hash_length;
}
return retval;
}
/*
* There can be multiple entries with the same primary key in the ebtree so in
* order to get the proper one out of the list, we use a secondary_key.
* This function simply iterates over all the entries with the same primary_key
* until it finds the right one.
* If <delete_expired> is 0 then the entry is left untouched if it is found but
* is already expired, and NULL is returned. Otherwise, the expired entry is
* removed from the tree and NULL is returned.
* Returns the cache_entry in case of success, NULL otherwise.
*
* This function must be called under a cache lock, either read if
* delete_expired==0, write otherwise.
*/
struct cache_entry *get_secondary_entry(struct cache_tree *cache, struct cache_entry *entry,
const char *primary_hash, const char *secondary_key, int delete_expired)
{
struct eb32_node *node = &entry->eb;
if (!entry->secondary_key_signature)
return NULL;
while (entry && secondary_key_cmp(entry->secondary_key, secondary_key) != 0) {
node = eb32_next_dup(node);
/* Make the best use of this iteration and clear expired entries
* when we find them. Calling delete_entry would be too costly
* so we simply call eb32_delete. The secondary_entry count will
* be updated when we try to insert a new entry to this list. */
if (entry->expire <= date.tv_sec && delete_expired) {
release_entry_locked(cache, entry);
}
entry = node ? eb32_entry(node, struct cache_entry, eb) : NULL;
}
/* Now verify the full primary hash matches: eb32 only compares 32 bits so
* we could have ended up on a different, unrelated entry.
*/
if (entry && primary_hash && memcmp(entry->hash, primary_hash, sizeof(entry->hash)))
entry = NULL;
/* Expired entry */
if (entry && entry->expire <= date.tv_sec) {
if (delete_expired) {
release_entry_locked(cache, entry);
}
entry = NULL;
}
return entry;
}
static inline struct cache_tree *get_cache_tree_from_hash(struct cache *cache, unsigned int hash)
{
if (!cache)
return NULL;
return &cache->trees[hash % CACHE_TREE_NUM];
}
/*
* Remove all expired entries from a list of duplicates.
* Return the number of alive entries in the list and sets dup_tail to the
* current last item of the list.
*
* This function must be called under a cache write lock.
*/
static unsigned int clear_expired_duplicates(struct cache_tree *cache, struct eb32_node **dup_tail)
{
unsigned int entry_count = 0;
struct cache_entry *entry = NULL;
struct eb32_node *prev = *dup_tail;
struct eb32_node *tail = NULL;
while (prev) {
entry = container_of(prev, struct cache_entry, eb);
prev = eb32_prev_dup(prev);
if (entry->expire <= date.tv_sec) {
release_entry_locked(cache, entry);
}
else {
if (!tail)
tail = &entry->eb;
++entry_count;
}
}
*dup_tail = tail;
return entry_count;
}
/*
* This function inserts a cache_entry in the cache's ebtree. In case of
* duplicate entries (vary), it then checks that the number of entries did not
* reach the max number of secondary entries. If this entry should not have been
* created, remove it.
* In the regular case (unique entries), this function does not do more than a
* simple insert. In case of secondary entries, it will at most cost an
* insertion+max_sec_entries time checks and entry deletion.
* Returns the newly inserted node in case of success, NULL otherwise.
*
* This function must be called under a cache write lock.
*/
static struct eb32_node *insert_entry(struct cache *cache, struct cache_tree *tree, struct cache_entry *new_entry)
{
struct eb32_node *prev = NULL;
struct cache_entry *entry = NULL;
unsigned int entry_count = 0;
unsigned int last_clear_ts = date.tv_sec;
struct eb32_node *node = eb32_insert(&tree->entries, &new_entry->eb);
new_entry->refcount = 1;
/* We should not have multiple entries with the same primary key unless
* the entry has a non null vary signature. */
if (!new_entry->secondary_key_signature)
return node;
prev = eb32_prev_dup(node);
if (prev != NULL) {
/* The last entry of a duplicate list should contain the current
* number of entries in the list. */
entry = container_of(prev, struct cache_entry, eb);
entry_count = entry->secondary_entries_count;
last_clear_ts = entry->last_clear_ts;
if (entry_count >= cache->max_secondary_entries) {
/* Some entries of the duplicate list might be expired so
* we will iterate over all the items in order to free some
* space. In order to avoid going over the same list too
* often, we first check the timestamp of the last check
* performed. */
if (last_clear_ts == date.tv_sec) {
/* Too many entries for this primary key, clear the
* one that was inserted. */
release_entry_locked(tree, new_entry);
return NULL;
}
entry_count = clear_expired_duplicates(tree, &prev);
if (entry_count >= cache->max_secondary_entries) {
/* Still too many entries for this primary key, delete
* the newly inserted one. */
entry = container_of(prev, struct cache_entry, eb);
entry->last_clear_ts = date.tv_sec;
release_entry_locked(tree, new_entry);
return NULL;
}
}
}
new_entry->secondary_entries_count = entry_count + 1;
new_entry->last_clear_ts = last_clear_ts;
return node;
}
/*
* This function removes an entry from the ebtree. If the entry was a duplicate
* (in case of Vary), it updates the secondary entry counter in another
* duplicate entry (the last entry of the dup list).
*
* This function must be called under a cache write lock.
*/
static void delete_entry(struct cache_entry *del_entry)
{
struct eb32_node *prev = NULL, *next = NULL;
struct cache_entry *entry = NULL;
struct eb32_node *last = NULL;
/* The entry might have been removed from the cache before. In such a
* case calling eb32_next_dup would crash. */
if (del_entry->secondary_key_signature && del_entry->eb.key != 0) {
next = &del_entry->eb;
/* Look for last entry of the duplicates list. */
while ((next = eb32_next_dup(next))) {
last = next;
}
if (last) {
entry = container_of(last, struct cache_entry, eb);
--entry->secondary_entries_count;
}
else {
/* The current entry is the last one, look for the
* previous one to update its counter. */
prev = eb32_prev_dup(&del_entry->eb);
if (prev) {
entry = container_of(prev, struct cache_entry, eb);
entry->secondary_entries_count = del_entry->secondary_entries_count - 1;
}
}
}
eb32_delete(&del_entry->eb);
del_entry->eb.key = 0;
}
static inline struct shared_context *shctx_ptr(struct cache *cache)
{
return (struct shared_context *)((unsigned char *)cache - offsetof(struct shared_context, data));
}
static inline struct shared_block *block_ptr(struct cache_entry *entry)
{
return (struct shared_block *)((unsigned char *)entry - offsetof(struct shared_block, data));
}
static int
cache_store_init(struct proxy *px, struct flt_conf *fconf)
{
fconf->flags |= FLT_CFG_FL_HTX;
return 0;
}
static void
cache_store_deinit(struct proxy *px, struct flt_conf *fconf)
{
struct cache_flt_conf *cconf = fconf->conf;
if (!(cconf->flags & CACHE_FLT_INIT))
free(cconf->c.name);
free(cconf);
}
static int
cache_store_check(struct proxy *px, struct flt_conf *fconf)
{
struct cache_flt_conf *cconf = fconf->conf;
struct flt_conf *f;
struct cache *cache;
int comp = 0;
/* Find the cache corresponding to the name in the filter config. The
* cache will not be referenced now in the filter config because it is
* not fully allocated. This step will be performed during the cache
* post_check.
*/
list_for_each_entry(cache, &caches_config, list) {
if (strcmp(cache->id, cconf->c.name) == 0)
goto found;
}
ha_alert("config: %s '%s': unable to find the cache '%s' referenced by the filter 'cache'.\n",
proxy_type_str(px), px->id, (char *)cconf->c.name);
return 1;
found:
/* Here <cache> points on the cache the filter must use and <cconf>
* points on the cache filter configuration. */
/* Check all filters for proxy <px> to know if the compression is
* enabled and if it is after the cache. When the compression is before
* the cache, an error is returned. Also check if the cache filter must
* be explicitly declaired or not. */
list_for_each_entry(f, &px->filter_configs, list) {
if (f == fconf) {
/* The compression filter must be evaluated after the cache. */
if (comp) {
ha_alert("config: %s '%s': unable to enable the compression filter before "
"the cache '%s'.\n", proxy_type_str(px), px->id, cache->id);
return 1;
}
}
else if (f->id == http_comp_req_flt_id || f->id == http_comp_res_flt_id)
comp = 1;
else if (f->id == fcgi_flt_id)
continue;
else if ((f->id != fconf->id) && (cconf->flags & CACHE_FLT_F_IMPLICIT_DECL)) {
/* Implicit declaration is only allowed with the
* compression and fcgi. For other filters, an implicit
* declaration is required. */
ha_alert("config: %s '%s': require an explicit filter declaration "
"to use the cache '%s'.\n", proxy_type_str(px), px->id, cache->id);
return 1;
}
}
return 0;
}
static int
cache_store_strm_init(struct stream *s, struct filter *filter)
{
struct cache_st *st;
st = pool_alloc(pool_head_cache_st);
if (st == NULL)
return -1;
st->first_block = NULL;
filter->ctx = st;
/* Register post-analyzer on AN_RES_WAIT_HTTP */
filter->post_analyzers |= AN_RES_WAIT_HTTP;
return 1;
}
static void
cache_store_strm_deinit(struct stream *s, struct filter *filter)
{
struct cache_st *st = filter->ctx;
struct cache_flt_conf *cconf = FLT_CONF(filter);
struct cache *cache = cconf->c.cache;
struct shared_context *shctx = shctx_ptr(cache);
/* Everything should be released in the http_end filter, but we need to do it
* there too, in case of errors */
if (st && st->first_block) {
struct cache_entry *object = (struct cache_entry *)st->first_block->data;
if (!object->complete) {
/* The stream was closed but the 'complete' flag was not
* set which means that cache_store_http_end was not
* called. The stream must have been closed before we
* could store the full answer in the cache.
*/
release_entry_unlocked(&cache->trees[object->eb.key % CACHE_TREE_NUM], object);
}
shctx_wrlock(shctx);
shctx_row_reattach(shctx, st->first_block);
shctx_wrunlock(shctx);
}
if (st) {
pool_free(pool_head_cache_st, st);
filter->ctx = NULL;
}
}
static int
cache_store_post_analyze(struct stream *s, struct filter *filter, struct channel *chn,
unsigned an_bit)
{
struct http_txn *txn = s->txn.http;
struct http_msg *msg = &txn->rsp;
struct cache_st *st = filter->ctx;
if (an_bit != AN_RES_WAIT_HTTP)
goto end;
/* Here we need to check if any compression filter precedes the cache
* filter. This is only possible when the compression is configured in
* the frontend while the cache filter is configured on the
* backend. This case cannot be detected during HAProxy startup. So in
* such cases, the cache is disabled.
*/
if (st && (msg->flags & HTTP_MSGF_COMPRESSING)) {
pool_free(pool_head_cache_st, st);
filter->ctx = NULL;
}
end:
return 1;
}
static int
cache_store_http_headers(struct stream *s, struct filter *filter, struct http_msg *msg)
{
struct cache_st *st = filter->ctx;
if (!(msg->chn->flags & CF_ISRESP) || !st)
return 1;
if (st->first_block)
register_data_filter(s, msg->chn, filter);
return 1;
}
static inline void disable_cache_entry(struct cache_st *st,
struct filter *filter, struct shared_context *shctx)
{
struct cache_entry *object;
struct cache *cache = (struct cache*)shctx->data;
object = (struct cache_entry *)st->first_block->data;
filter->ctx = NULL; /* disable cache */
release_entry_unlocked(&cache->trees[object->eb.key % CACHE_TREE_NUM], object);
shctx_wrlock(shctx);
shctx_row_reattach(shctx, st->first_block);
shctx_wrunlock(shctx);
pool_free(pool_head_cache_st, st);
}
static int
cache_store_http_payload(struct stream *s, struct filter *filter, struct http_msg *msg,
unsigned int offset, unsigned int len)
{
struct cache_flt_conf *cconf = FLT_CONF(filter);
struct shared_context *shctx = shctx_ptr(cconf->c.cache);
struct cache_st *st = filter->ctx;
struct htx *htx = htxbuf(&msg->chn->buf);
struct htx_blk *blk;
struct shared_block *fb;
struct htx_ret htxret;
unsigned int orig_len, to_forward;
int ret;
if (!len)
return len;
if (!st->first_block) {
unregister_data_filter(s, msg->chn, filter);
return len;
}
orig_len = len;
to_forward = 0;
htxret = htx_find_offset(htx, offset);
blk = htxret.blk;
offset = htxret.ret;
for (; blk && len; blk = htx_get_next_blk(htx, blk)) {
enum htx_blk_type type = htx_get_blk_type(blk);
uint32_t info, sz = htx_get_blksz(blk);
struct ist v;
switch (type) {
case HTX_BLK_UNUSED:
break;
case HTX_BLK_DATA:
v = htx_get_blk_value(htx, blk);
v = istadv(v, offset);
v = isttrim(v, len);
info = (type << 28) + v.len;
fb = shctx_row_reserve_hot(shctx, st->first_block, sizeof(info)+v.len);
if (!fb)
goto no_cache;
ret = shctx_row_data_append(shctx, st->first_block, (unsigned char *)&info, sizeof(info));
if (ret < 0)
goto no_cache;
ret = shctx_row_data_append(shctx, st->first_block, (unsigned char *)istptr(v), istlen(v));
if (ret < 0)
goto no_cache;
ASSUME_NONNULL((struct cache_entry *)st->first_block->data)->body_size += v.len;
to_forward += v.len;
len -= v.len;
break;
default:
/* Here offset must always be 0 because only
* DATA blocks can be partially transferred. */
if (offset)
goto no_cache;
if (sz > len)
goto end;
fb = shctx_row_reserve_hot(shctx, st->first_block, sizeof(blk->info)+sz);
if (!fb)
goto no_cache;
ret = shctx_row_data_append(shctx, st->first_block, (unsigned char *)&(blk->info), sizeof(blk->info));
if (ret < 0)
goto no_cache;
ret = shctx_row_data_append(shctx, st->first_block, (unsigned char *)htx_get_blk_ptr(htx, blk), sz);
if (ret < 0)
goto no_cache;
to_forward += sz;
len -= sz;
break;
}
offset = 0;
}
end:
return to_forward;
no_cache:
disable_cache_entry(st, filter, shctx);
unregister_data_filter(s, msg->chn, filter);
return orig_len;
}
static int
cache_store_http_end(struct stream *s, struct filter *filter,
struct http_msg *msg)
{
struct cache_st *st = filter->ctx;
struct cache_flt_conf *cconf = FLT_CONF(filter);
struct cache *cache = cconf->c.cache;
struct shared_context *shctx = shctx_ptr(cache);
struct cache_entry *object;
if (!(msg->chn->flags & CF_ISRESP))
return 1;
if (st && st->first_block) {
object = (struct cache_entry *)st->first_block->data;
shctx_wrlock(shctx);
/* The whole payload was cached, the entry can now be used. */
object->complete = 1;
/* remove from the hotlist */
shctx_row_reattach(shctx, st->first_block);
shctx_wrunlock(shctx);
}
if (st) {
pool_free(pool_head_cache_st, st);
filter->ctx = NULL;
}
return 1;
}
/*
* This intends to be used when checking HTTP headers for some
* word=value directive. Return a pointer to the first character of value, if
* the word was not found or if there wasn't any value assigned to it return NULL
*/
char *directive_value(const char *sample, int slen, const char *word, int wlen)
{
int st = 0;
if (slen < wlen)
return 0;
while (wlen) {
char c = *sample ^ *word;
if (c && c != ('A' ^ 'a'))
return NULL;
sample++;
word++;
slen--;
wlen--;
}
while (slen) {
if (st == 0) {
if (*sample != '=')
return NULL;
sample++;
slen--;
st = 1;
continue;
} else {
return (char *)sample;
}
}
return NULL;
}
/*
* Return the maxage in seconds of an HTTP response.
* The returned value will always take the cache's configuration into account
* (cache->maxage) but the actual max age of the response will be set in the
* true_maxage parameter. It will be used to determine if a response is already
* stale or not.
* Compute the maxage using either:
* - the assigned max-age of the cache
* - the s-maxage directive
* - the max-age directive
* - (Expires - Data) headers
* - the default-max-age of the cache
*
*/
int http_calc_maxage(struct stream *s, struct cache *cache, int *true_maxage)
{
struct htx *htx = htxbuf(&s->res.buf);
struct http_hdr_ctx ctx = { .blk = NULL };
long smaxage = -1;
long maxage = -1;
int expires = -1;
struct tm tm = {};
time_t expires_val = 0;
char *endptr = NULL;
int offset = 0;
/* The Cache-Control max-age and s-maxage directives should be followed by
* a positive numerical value (see RFC 7234#5.2.1.1). According to the
* specs, a sender "should not" generate a quoted-string value but we will
* still accept this format since it isn't strictly forbidden. */
while (http_find_header(htx, ist("cache-control"), &ctx, 0)) {
char *value;
value = directive_value(ctx.value.ptr, ctx.value.len, "s-maxage", 8);
if (value) {
struct buffer *chk = get_trash_chunk();
chunk_memcat(chk, value, ctx.value.len - (8 + 1));
*(b_tail(chk)) = '\0';
offset = (*chk->area == '"') ? 1 : 0;
smaxage = strtol(chk->area + offset, &endptr, 10);
if (unlikely(smaxage < 0 || endptr == chk->area + offset))
return -1;
}
value = directive_value(ctx.value.ptr, ctx.value.len, "max-age", 7);
if (value) {
struct buffer *chk = get_trash_chunk();
chunk_memcat(chk, value, ctx.value.len - (7 + 1));
*(b_tail(chk)) = '\0';
offset = (*chk->area == '"') ? 1 : 0;
maxage = strtol(chk->area + offset, &endptr, 10);
if (unlikely(maxage < 0 || endptr == chk->area + offset))
return -1;
}
}
/* Look for Expires header if no s-maxage or max-age Cache-Control data
* was found. */
if (maxage == -1 && smaxage == -1) {
ctx.blk = NULL;
if (http_find_header(htx, ist("expires"), &ctx, 1)) {
if (parse_http_date(istptr(ctx.value), istlen(ctx.value), &tm)) {
expires_val = my_timegm(&tm);
/* A request having an expiring date earlier
* than the current date should be considered as
* stale. */
expires = (expires_val >= date.tv_sec) ?
(expires_val - date.tv_sec) : 0;
}
else {
/* Following RFC 7234#5.3, an invalid date
* format must be treated as a date in the past
* so the cache entry must be seen as already
* expired. */
expires = 0;
}
}
}
if (smaxage > 0) {
if (true_maxage)
*true_maxage = smaxage;
return MIN(smaxage, cache->maxage);