Line data Source code
1 : /*
2 : START OF LICENSE STUB
3 : DeDOS: Declarative Dispersion-Oriented Software
4 : Copyright (C) 2017 University of Pennsylvania, Georgetown University
5 :
6 : This program is free software: you can redistribute it and/or modify
7 : it under the terms of the GNU General Public License as published by
8 : the Free Software Foundation, either version 3 of the License, or
9 : (at your option) any later version.
10 :
11 : This program is distributed in the hope that it will be useful,
12 : but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : GNU General Public License for more details.
15 :
16 : You should have received a copy of the GNU General Public License
17 : along with this program. If not, see <http://www.gnu.org/licenses/>.
18 : END OF LICENSE STUB
19 : */
20 : #include "connection-handler.h"
21 : #include "dfg.h"
22 : #include "local_msu.h"
23 : #include "logging.h"
24 : #include "msu_calls.h"
25 : #include "msu_message.h"
26 : #include "msu_type.h"
27 : #include "routing_strategies.h"
28 : #include "rt_stats.h"
29 : #include "local_files.h"
30 :
31 : #include "webserver/uthash.h"
32 : #include "webserver/write_msu.h"
33 : #include "webserver/cache_msu.h"
34 : #include "webserver/fileio_msu.h"
35 : #include "webserver/connection-handler.h"
36 : #include "webserver/httpops.h"
37 :
38 : #include <string.h>
39 :
40 : #define DEFAULT_WWW_DIR "www/"
41 : #define DEFAULT_OCCUPANCY_RATE 0.2
42 : #define DEFAULT_MAX_KB_SIZE UINT_MAX
43 : #define DEFAULT_MAX_FILES UINT_MAX
44 : #define CACHE_INIT_SYNTAX "<www_dir>, <max_cache_size_in_kb>, <max_cached_files>, " \
45 : "<max_cache_occupancy_rate>"
46 :
47 : #define MONITOR_CACHE_STATS
48 : #define CACHE_HIT_STAT MSU_STAT1
49 : #define CACHE_MISS_STAT MSU_STAT2
50 : #define CACHE_EVICT_STAT MSU_STAT3
51 :
52 :
53 : static struct local_msu *cache_instance;
54 :
55 : struct cached_file {
56 : long byte_size;
57 : char *path;
58 : char *contents;
59 : struct cached_file *lru_prev;
60 : struct cached_file *lru_next;
61 : UT_hash_handle hh;
62 : };
63 :
64 : struct ws_cache_state {
65 : unsigned int max_files;
66 : unsigned int max_kb_size;
67 : float max_occupancy_rate;
68 : unsigned long byte_size;
69 : unsigned int file_count;
70 : char *www_dir;
71 : struct cached_file *cache;
72 : struct cached_file *lru_head; // Contains the least recently used item
73 : struct cached_file *lru_tail; // Contains the last item used
74 : };
75 :
76 138 : struct cached_file *check_cache(struct ws_cache_state *fc, char *path) {
77 138 : struct cached_file *cached = NULL;
78 138 : HASH_FIND_STR(fc->cache, path, cached);
79 138 : if (cached != NULL) {
80 109 : log_info("File %s retrieved from cache", path);
81 :
82 : // Update LRU if not already the tail
83 109 : if (fc->lru_tail != cached) {
84 6 : if (fc->lru_head == cached) {
85 6 : fc->lru_head = cached->lru_next;
86 : } else {
87 0 : cached->lru_prev->lru_next = cached->lru_next;
88 : }
89 6 : cached->lru_next->lru_prev = cached->lru_prev;
90 6 : cached->lru_prev = fc->lru_tail;
91 6 : fc->lru_tail->lru_next = cached;
92 6 : cached->lru_next = NULL;
93 6 : fc->lru_tail = cached;
94 : }
95 :
96 109 : return cached;
97 : }
98 :
99 29 : return NULL;
100 : }
101 :
102 27 : static int parse_init_cache_payload(char *to_parse, struct ws_cache_state *cache_state) {
103 :
104 27 : cache_state->www_dir = DEFAULT_WWW_DIR;
105 27 : cache_state->max_kb_size = DEFAULT_MAX_KB_SIZE;
106 27 : cache_state->max_files = DEFAULT_MAX_FILES;
107 27 : cache_state->max_occupancy_rate = DEFAULT_OCCUPANCY_RATE;
108 :
109 27 : if (to_parse == NULL) {
110 0 : log_warn("Initializing cache MSU with default parameters. "
111 : "(FYI: init syntax is [" CACHE_INIT_SYNTAX "])");
112 : } else {
113 : char *saveptr, *tok;
114 27 : if ( (tok = strtok_r(to_parse, " ,", &saveptr)) == NULL) {
115 0 : log_warn("Couldn't get wwW_dir from initialization string");
116 0 : return 0;
117 : }
118 27 : cache_state->www_dir = malloc(32 + strlen(tok));
119 27 : get_local_file(cache_state->www_dir, tok);
120 :
121 27 : if ( (tok = strtok_r(NULL, " ,", &saveptr)) == NULL) {
122 0 : log_warn("Couldn't get max cache size in kb from initialization string");
123 0 : return 0;
124 : }
125 27 : int max_kb_size = atoi(tok);
126 27 : if (max_kb_size >= 0)
127 27 : cache_state->max_kb_size = (unsigned int) max_kb_size;
128 :
129 27 : if ( (tok = strtok_r(NULL, " ,", &saveptr)) == NULL) {
130 0 : log_warn("Couldn't get max cached files from initialization string");
131 0 : return 0;
132 : }
133 27 : int max_files = atoi(tok);
134 27 : if (max_files >= 0)
135 27 : cache_state->max_files = (unsigned int) max_files;
136 :
137 27 : if ( (tok = strtok_r(NULL, " ,", &saveptr)) == NULL) {
138 0 : log_warn("Couldn't get max occupancy rate in kb from initialization string");
139 0 : return 0;
140 : }
141 27 : float max_occupancy_rate = atof(tok);
142 27 : if (max_occupancy_rate >= 0.0)
143 27 : cache_state->max_occupancy_rate = max_occupancy_rate;
144 :
145 27 : if ( (tok = strtok_r(NULL, " ,", &saveptr)) != NULL) {
146 0 : log_warn("Discarding extra tokens from cache initialization: %s", tok);
147 : }
148 : }
149 27 : return 0;
150 : }
151 :
152 29 : static int cache_file(struct ws_cache_state *fc, char *path, char *contents, long length) {
153 : // Only cache the file if it isn't too large
154 29 : float kbytes = (float) length / 1024;
155 29 : if (kbytes > fc->max_kb_size || kbytes / fc->max_kb_size > fc->max_occupancy_rate) {
156 4 : log_info("File at %s is too large for caching (%ld bytes)", path, length);
157 4 : return -1;
158 : }
159 :
160 : // Evict files if necessar11y
161 87 : while (((float) fc->byte_size + length) / 1024 > fc->max_kb_size ||
162 29 : fc->max_files == fc->file_count) {
163 8 : struct cached_file *cached = fc->lru_head;
164 8 : if (cached == NULL) {
165 0 : log_error("Trying to evict lru head that is NULL!");
166 0 : return -2;
167 : }
168 :
169 8 : log_info("Evicting %s from cache", cached->path);
170 : #ifdef MONITOR_CACHE_STATS
171 8 : increment_stat(CACHE_EVICT_STAT, cache_instance->id, 1);
172 : #endif
173 :
174 8 : HASH_DEL(fc->cache, cached);
175 8 : fc->lru_head = cached->lru_next;
176 8 : if (fc->lru_head != NULL) {
177 4 : fc->lru_head->lru_prev = NULL;
178 : }
179 8 : if (fc->lru_tail == cached) {
180 4 : fc->lru_tail = NULL;
181 : }
182 8 : fc->file_count--;
183 8 : fc->byte_size -= cached->byte_size;
184 8 : free(cached->path);
185 8 : free(cached->contents);
186 8 : free(cached);
187 : }
188 :
189 : // Now add the file to the cache
190 25 : struct cached_file *cached = (struct cached_file *) malloc(
191 : sizeof(struct cached_file));
192 25 : if (cached == NULL) {
193 0 : log_error("Failed to allocate space for cached_file struct for file %s", path);
194 0 : return -2;
195 : }
196 :
197 25 : log_info("Adding file %s to cache", path);
198 :
199 : // Add to cache
200 25 : cached->byte_size = length;
201 : // Copy path
202 25 : int path_len = strlen(path);
203 25 : cached->path = (char *) malloc(path_len + 1);
204 25 : strncpy(cached->path, path, path_len);
205 25 : cached->path[path_len] = '\0';
206 : // Copy contents
207 25 : if (length > 0) {
208 25 : cached->contents = (char *) malloc(length);
209 25 : memcpy(cached->contents, contents, length);
210 : } else {
211 0 : cached->contents = NULL;
212 : }
213 25 : fc->file_count++;
214 25 : HASH_ADD_STR(fc->cache, path, cached);
215 :
216 : // Update LRU linked list
217 25 : cached->lru_prev = fc->lru_tail;
218 25 : if (fc->lru_tail != NULL) {
219 8 : fc->lru_tail->lru_next = cached;
220 : }
221 25 : cached->lru_next = NULL;
222 25 : if (fc->lru_head == NULL) {
223 17 : fc->lru_head = cached;
224 : }
225 25 : fc->lru_tail = cached;
226 25 : fc->byte_size += length;
227 :
228 25 : return length;
229 : }
230 :
231 167 : static int ws_cache_lookup(struct local_msu *self,
232 : struct msu_msg *msg) {
233 167 : struct response_state *resp = msg->data;
234 167 : struct ws_cache_state *fc = self->msu_state;
235 :
236 167 : url_to_path(resp->url, fc->www_dir, resp->path, MAX_FILEPATH_LEN);
237 :
238 : // TODO: Check message type properly to determine if this is a response to cache, or request
239 305 : if (resp->body[0] == '\0' && resp->body_len == 0) {
240 138 : struct cached_file *file = check_cache(fc, resp->path);
241 138 : if (file == NULL) {
242 : // File not cached, send to file IO msu
243 : #ifdef MONITOR_CACHE_STATS
244 29 : increment_stat(CACHE_MISS_STAT, cache_instance->id, 1);
245 : #endif
246 29 : call_msu_type(self, &WEBSERVER_FILEIO_MSU_TYPE, &msg->hdr, sizeof(*resp), resp);
247 : } else {
248 : // File cached, generate response including http headers and send to write msu
249 109 : int code = 404;
250 109 : char *mime_type = NULL;
251 109 : if (file->contents != NULL && file->byte_size > 0) {
252 109 : code = 200;
253 109 : resp->body_len = file->byte_size;
254 109 : mime_type = path_to_mimetype(resp->path);
255 109 : memcpy(resp->body, file->contents, resp->body_len);
256 : }
257 109 : resp->header_len = generate_header(resp->header, code, MAX_HEADER_LEN, resp->body_len,
258 : mime_type);
259 109 : if (resp->header_len > MAX_HEADER_LEN) {
260 0 : resp->header_len = MAX_HEADER_LEN;
261 : }
262 : #ifdef MONITOR_CACHE_STATS
263 109 : increment_stat(CACHE_HIT_STAT, cache_instance->id, 1);
264 : #endif
265 109 : call_msu_type(self, &WEBSERVER_WRITE_MSU_TYPE, &msg->hdr, sizeof(*resp), resp);
266 : }
267 : } else {
268 : // Received a response to save to the cache
269 29 : cache_file((struct ws_cache_state *)self->msu_state, resp->path, resp->body,
270 29 : resp->body_len);
271 : }
272 :
273 167 : return 0;
274 : }
275 :
276 27 : static int ws_cache_init(struct local_msu *self, struct msu_init_data *init_data) {
277 27 : struct ws_cache_state *cache_state = malloc(sizeof(*cache_state));
278 :
279 27 : parse_init_cache_payload(init_data->init_data, cache_state);
280 :
281 27 : cache_state->byte_size = 0;
282 27 : cache_state->file_count = 0;
283 27 : cache_state->cache = NULL;
284 27 : cache_state->lru_head = NULL;
285 27 : cache_state->lru_tail = NULL;
286 :
287 27 : self->msu_state = (void*)cache_state;
288 :
289 27 : cache_instance = self;
290 :
291 : #ifdef MONITOR_CACHE_STATS
292 27 : init_stat_item(CACHE_HIT_STAT, self->id);
293 27 : init_stat_item(CACHE_MISS_STAT, self->id);
294 27 : init_stat_item(CACHE_EVICT_STAT, self->id);
295 : #endif
296 :
297 27 : return 0;
298 : }
299 :
300 : struct msu_type WEBSERVER_CACHE_MSU_TYPE = {
301 : .name = "Webserver_cache_msu",
302 : .id = WEBSERVER_CACHE_MSU_TYPE_ID,
303 : .receive = ws_cache_lookup,
304 : .init = ws_cache_init,
305 : .route = shortest_queue_route
306 : };
|