Objectively
Ultra-lightweight object oriented framework for GNU C.
Loading...
Searching...
No Matches
URLCache.c
Go to the documentation of this file.
1/*
2 * Objectively: Ultra-lightweight object oriented framework for GNU C.
3 * Copyright (C) 2014 Jay Dolan <jay@jaydolan.com>
4 *
5 * This software is provided 'as-is', without any express or implied
6 * warranty. In no event will the authors be held liable for any damages
7 * arising from the use of this software.
8 *
9 * Permission is granted to anyone to use this software for any purpose,
10 * including commercial applications, and to alter it and redistribute it
11 * freely, subject to the following restrictions:
12 *
13 * 1. The origin of this software must not be misrepresented; you must not
14 * claim that you wrote the original software. If you use this software
15 * in a product, an acknowledgment in the product documentation would be
16 * appreciated but is not required.
17 *
18 * 2. Altered source versions must be plainly marked as such, and must not be
19 * misrepresented as being the original software.
20 *
21 * 3. This notice may not be removed or altered from any source distribution.
22 */
23
24#include <assert.h>
25#include <ctype.h>
26#include <stdlib.h>
27
28#include "String.h"
29#include "URLCache.h"
30
31#define _Class _URLCache
32
33#define URLCACHE_DEFAULT_MAX_SIZE (1024 * 1024)
34
35#pragma mark - Helpers
36
40static bool stringEqualsIgnoreCase(const char *a, const char *b) {
41
42 if (a == NULL || b == NULL) {
43 return false;
44 }
45
46 while (*a && *b) {
47 if (tolower((unsigned char) *a) != tolower((unsigned char) *b)) {
48 return false;
49 }
50
51 a++;
52 b++;
53 }
54
55 return *a == 0 && *b == 0;
56}
57
61static bool stringEqualsIgnoreCaseN(const char *a, const char *b, size_t length) {
62
63 if (a == NULL || b == NULL) {
64 return false;
65 }
66
67 for (size_t i = 0; i < length; i++) {
68 if (tolower((unsigned char) a[i]) != tolower((unsigned char) b[i])) {
69 return false;
70 }
71 }
72
73 return true;
74}
75
79static const String *headerValueForField(const Dictionary *headers, const char *field) {
80
81 if (headers && field) {
82
83 Array *keys = $(headers, allKeys);
84 for (size_t i = 0; i < keys->count; i++) {
85
86 const String *key = $(keys, objectAtIndex, i);
87 if (stringEqualsIgnoreCase(key->chars, field)) {
88
89 const String *value = $(headers, objectForKey, (ident) key);
90 release(keys);
91 return value;
92 }
93 }
94
95 release(keys);
96 }
97
98 return NULL;
99}
100
104static bool cacheControlDisablesCaching(const char *value) {
105
106 if (value == NULL) {
107 return false;
108 }
109
110 const char *cursor = value;
111 while (*cursor) {
112
113 while (*cursor == ' ' || *cursor == '\t' || *cursor == ',') {
114 cursor++;
115 }
116
117 const char *end = cursor;
118 while (*end && *end != ',') {
119 end++;
120 }
121
122 const char *start = cursor;
123 while (start < end && isspace((unsigned char) *start)) {
124 start++;
125 }
126
127 while (end > start && isspace((unsigned char) *(end - 1))) {
128 end--;
129 }
130
131 const size_t length = (size_t) (end - start);
132 if (length > 0) {
133
134 if (length == 8 && stringEqualsIgnoreCaseN(start, "no-cache", 8)) {
135 return true;
136 }
137
138 if (length == 8 && stringEqualsIgnoreCaseN(start, "no-store", 8)) {
139 return true;
140 }
141
142 if (length >= 8 && stringEqualsIgnoreCaseN(start, "max-age=", 8)) {
143 if (strtol(start + 8, NULL, 10) <= 0) {
144 return true;
145 }
146 }
147 }
148
149 cursor = end;
150 if (*cursor == ',') {
151 cursor++;
152 }
153 }
154
155 return false;
156}
157
162
163 if (request == NULL) {
164 return false;
165 }
166
167 switch (request->httpMethod) {
168 case HTTP_NONE:
169 case HTTP_GET:
170 break;
171 default:
172 return false;
173 }
174
175 const String *cacheControl = headerValueForField(request->httpHeaders, "Cache-Control");
176 if (cacheControl && cacheControlDisablesCaching(cacheControl->chars)) {
177 return false;
178 }
179
180 const String *pragma = headerValueForField(request->httpHeaders, "Pragma");
181 if (pragma && stringEqualsIgnoreCase(pragma->chars, "no-cache")) {
182 return false;
183 }
184
185 return true;
186}
187
191static bool responseIsCacheable(const URLResponse *response) {
192
193 if (response == NULL || response->httpStatusCode != 200) {
194 return false;
195 }
196
197 const String *cacheControl = headerValueForField(response->httpHeaders, "Cache-Control");
198 if (cacheControl && cacheControlDisablesCaching(cacheControl->chars)) {
199 return false;
200 }
201
202 const String *pragma = headerValueForField(response->httpHeaders, "Pragma");
203 if (pragma && stringEqualsIgnoreCase(pragma->chars, "no-cache")) {
204 return false;
205 }
206
207 return true;
208}
209
213static void evictIfNeeded(URLCache *self) {
214
215 const Dictionary *dictionary = (Dictionary *) self->responses;
216
217 while (self->currentSize > self->maxSize && self->responses->count > 0) {
218
219 Array *keys = $(dictionary, allKeys);
220
221 const URLRequest *oldestKey = NULL;
222 URLCachedResponse *oldestResponse = NULL;
223
224 for (size_t i = 0; i < keys->count; i++) {
225
226 const URLRequest *key = $(keys, objectAtIndex, i);
227 URLCachedResponse *candidate = $(dictionary, objectForKey, (ident) key);
228
229 if (candidate && (oldestResponse == NULL
230 || $(candidate->timestamp, compareTo, oldestResponse->timestamp) == OrderAscending)) {
231 oldestKey = key;
232 oldestResponse = candidate;
233 }
234 }
235
236 release(keys);
237
238 if (oldestKey == NULL || oldestResponse == NULL) {
239 break;
240 }
241
242 self->currentSize -= oldestResponse->size;
243 $(self->responses, removeObjectForKey, (ident) oldestKey);
244 }
245}
246
247#pragma mark - Object
248
252static void dealloc(Object *self) {
253
254 URLCache *this = (URLCache *) self;
255
256 release(this->lock);
257 release(this->responses);
258
259 super(Object, self, dealloc);
260}
261
262#pragma mark - URLCache
263
269
270 if (requestIsCacheable(request) == false) {
271 return NULL;
272 }
273
274 URLCachedResponse *cachedResponse = NULL;
275 const Dictionary *dictionary = (Dictionary *) self->responses;
276 synchronized(self->lock, {
277 cachedResponse = $(dictionary, objectForKey, (ident) request);
278 if (cachedResponse) {
279 retain(cachedResponse);
280 }
281 });
282
283 return cachedResponse;
284}
285
290static URLCache *init(URLCache *self) {
291
292 self = (URLCache *) super(Object, self, init);
293 if (self) {
294 self->lock = $(alloc(Lock), init);
295 assert(self->lock);
296
297 self->responses = $(alloc(Dictionary), init);
298 assert(self->responses);
299
301 }
302
303 return self;
304}
305
311
312 synchronized(self->lock, {
313 $(self->responses, removeAllObjects);
314 self->currentSize = 0;
315 });
316}
317
323
324 synchronized(self->lock, {
325 const Dictionary *dictionary = (Dictionary *) self->responses;
326 URLCachedResponse *cachedResponse = $(dictionary, objectForKey, (ident) request);
327 if (cachedResponse) {
328 self->currentSize -= cachedResponse->size;
330 }
331 });
332}
333
338static void setMaxSize(URLCache *self, size_t maxSize) {
339
340 synchronized(self->lock, {
341 self->maxSize = maxSize;
342
343 if (self->maxSize == 0) {
344 $(self->responses, removeAllObjects);
345 self->currentSize = 0;
346 } else {
347 evictIfNeeded(self);
348 }
349 });
350}
351
357 const URLResponse *response, const Data *data) {
358
359 if (requestIsCacheable(request) == false || responseIsCacheable(response) == false) {
360 return;
361 }
362
363 URLCachedResponse *cachedResponse = $(alloc(URLCachedResponse), initWithResponseData, response, data);
364 if (cachedResponse == NULL) {
365 return;
366 }
367
368 synchronized(self->lock, {
369 if (cachedResponse->size <= self->maxSize && self->maxSize > 0) {
370
371 const Dictionary *dictionary = (Dictionary *) self->responses;
373 if (existing) {
374 self->currentSize -= existing->size;
376 }
377
378 URLRequest *key = (URLRequest *) $((Object *) request, copy);
379 $(self->responses, setObjectForKey, cachedResponse, key);
380 release(key);
381
382 self->currentSize += cachedResponse->size;
383 evictIfNeeded(self);
384 }
385 });
386
387 release(cachedResponse);
388}
389
390#pragma mark - Class lifecycle
391
395static void initialize(Class *clazz) {
396
397 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
398
399 ((URLCacheInterface *) clazz->interface)->cachedResponseForRequest = cachedResponseForRequest;
400 ((URLCacheInterface *) clazz->interface)->init = init;
401 ((URLCacheInterface *) clazz->interface)->removeAllCachedResponses = removeAllCachedResponses;
402 ((URLCacheInterface *) clazz->interface)->removeCachedResponseForRequest = removeCachedResponseForRequest;
403 ((URLCacheInterface *) clazz->interface)->setMaxSize = setMaxSize;
404 ((URLCacheInterface *) clazz->interface)->storeCachedResponseForRequest = storeCachedResponseForRequest;
405}
406
412 static Class *clazz;
413 static Once once;
414
415 do_once(&once, {
416 clazz = _initialize(&(const ClassDef) {
417 .name = "URLCache",
418 .superclass = _Object(),
419 .instanceSize = sizeof(URLCache),
420 .interfaceOffset = offsetof(URLCache, interface),
421 .interfaceSize = sizeof(URLCacheInterface),
423 });
424 });
425
426 return clazz;
427}
428
429#undef _Class
static ident objectAtIndex(const Array *self, size_t index)
Definition Array.c:578
static void removeAllObjects(Array *self)
Definition Array.c:604
static Object * copy(const Object *self)
Definition Array.c:84
ident release(ident obj)
Atomically decrement the given Object's reference count. If the resulting reference count is 0,...
Definition Class.c:195
Class * _initialize(const ClassDef *def)
Initializes the given Class.
Definition Class.c:86
ident retain(ident obj)
Atomically increment the given Object's reference count.
Definition Class.c:210
#define alloc(type)
Allocate and initialize and instance of type.
Definition Class.h:176
#define super(type, obj, method,...)
static Data * data(void)
Definition Data.c:286
static Order compareTo(const Date *self, const Date *other)
Definition Date.c:74
static void setObjectForKey(Dictionary *self, const ident obj, const ident key)
Definition Dictionary.c:634
static void removeObjectForKey(Dictionary *self, const ident key)
Definition Dictionary.c:547
static Dictionary * dictionary(void)
Definition Dictionary.c:242
static Array * allKeys(const Dictionary *self)
Definition Dictionary.c:193
static ident objectForKey(const Dictionary *self, const ident key)
Definition Dictionary.c:439
static void lock(Lock *self)
Definition Lock.c:81
Class * _Object(void)
Definition Object.c:136
static void start(Operation *self)
Definition Operation.c:171
static int request(RESTClient *self, HTTPMethod method, const char *url_string, const Data *body, Data **out_data)
Definition RESTClient.c:57
UTF-8 strings.
void * ident
The identity type, similar to Objective-C id.
Definition Types.h:49
@ OrderAscending
Definition Types.h:71
static bool stringEqualsIgnoreCase(const char *a, const char *b)
Tests two C strings for case-insensitive equality.
Definition URLCache.c:40
static void evictIfNeeded(URLCache *self)
Removes the oldest cached response while this cache exceeds its maximum size.
Definition URLCache.c:213
#define URLCACHE_DEFAULT_MAX_SIZE
Definition URLCache.c:33
static bool stringEqualsIgnoreCaseN(const char *a, const char *b, size_t length)
Tests the first length characters of two C strings for case-insensitive equality.
Definition URLCache.c:61
static bool requestIsCacheable(const URLRequest *request)
Returns true if this request is cacheable.
Definition URLCache.c:161
static bool responseIsCacheable(const URLResponse *response)
Returns true if this response is cacheable.
Definition URLCache.c:191
static URLCachedResponse * cachedResponseForRequest(URLCache *self, const URLRequest *request)
Definition URLCache.c:268
static void removeCachedResponseForRequest(URLCache *self, const URLRequest *request)
Definition URLCache.c:322
static URLCache * init(URLCache *self)
Definition URLCache.c:290
static void storeCachedResponseForRequest(URLCache *self, const URLRequest *request, const URLResponse *response, const Data *data)
Definition URLCache.c:356
static void dealloc(Object *self)
Definition URLCache.c:252
static void setMaxSize(URLCache *self, size_t maxSize)
Definition URLCache.c:338
static void removeAllCachedResponses(URLCache *self)
Definition URLCache.c:310
static const String * headerValueForField(const Dictionary *headers, const char *field)
Returns the value for the specified HTTP header field, case-insensitively.
Definition URLCache.c:79
static void initialize(Class *clazz)
Definition URLCache.c:395
Class * _URLCache(void)
Definition URLCache.c:411
static bool cacheControlDisablesCaching(const char *value)
Returns true if the provided Cache-Control value disables caching.
Definition URLCache.c:104
A cache for HTTP responses.
static URLCachedResponse * initWithResponseData(URLCachedResponse *self, const URLResponse *response, const Data *data)
@ HTTP_NONE
Definition URLRequest.h:43
@ HTTP_GET
Definition URLRequest.h:46
long Once
The Once type.
Definition Once.h:37
#define do_once(once, block)
Executes the given block at most one time.
Definition Once.h:43
Arrays.
Definition Array.h:56
size_t count
The count of elements.
Definition Array.h:72
ClassDefs are passed to _initialize via an archetype to initialize a Class.
Definition Class.h:41
The runtime representation of a Class.
Definition Class.h:95
ident interface
The interface of the Class.
Definition Class.h:105
Data buffers.
Definition Data.h:50
Key-value stores.
Definition Dictionary.h:60
size_t count
The count of elements.
Definition Dictionary.h:82
POSIX Threads locks.
Definition Lock.h:42
Object is the root Class of The Objectively Class hierarchy.
Definition Object.h:46
Object * init(Object *self)
Initializes this Object.
Definition Object.c:83
UTF-8 strings.
Definition String.h:69
char * chars
The backing null-terminated UTF-8 encoded character array.
Definition String.h:85
A cache for HTTP responses.
Definition URLCache.h:44
Lock * lock
The lock protecting this cache.
Definition URLCache.h:60
size_t currentSize
The current cached body size.
Definition URLCache.h:70
void removeAllCachedResponses(URLCache *self)
Removes all cached responses.
Definition URLCache.c:310
Dictionary * responses
The cached responses, keyed by URLRequest.
Definition URLCache.h:65
size_t maxSize
The maximum cached body size.
Definition URLCache.h:75
A cached HTTP response.
Date * timestamp
The time this response was cached.
size_t size
The cached body size.
A protocol-agnostic abstraction for requesting resources via URLs.
Definition URLRequest.h:58
A protocol-agnostic abstraction for URLSessionTask responses.
Definition URLResponse.h:42
Dictionary * httpHeaders
The HTTP response headers.
Definition URLResponse.h:58
int httpStatusCode
The HTTP response status code.
Definition URLResponse.h:63