Objectively
Ultra-lightweight object oriented framework for GNU C.
Loading...
Searching...
No Matches
URLSession.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
26#include <curl/curl.h>
27
28#include "URLSession.h"
29
30#define _Class _URLSession
31
32#pragma mark - Object
33
37static void dealloc(Object *self) {
38
39 URLSession *this = (URLSession *) self;
40
41 $(this, invalidateAndCancel);
42
43 $(this->locals.thread, join, NULL);
44
45 release(this->locals.condition);
46 release(this->locals.thread);
47 release(this->locals.tasks);
48 release(this->configuration);
49
50 super(Object, self, dealloc);
51}
52
53#pragma mark - URLSession
54
59
60 task = $((URLSessionTask *) task, initWithRequestInSession, request, self, completion);
61 if (task) {
62
63 synchronized(self->locals.condition, {
64 $(self->locals.tasks, addObject, task);
65 $(self->locals.condition, signal);
66 });
67 }
68
69 return task;
70}
71
77
79 initWithRequestInSession, request, self, completion);
80 if (task) {
81
82 URLCache *cache = self->configuration->urlCache;
83 if (cache) {
85 if (task->cachedResponse) {
86 return task;
87 }
88 }
89
90 synchronized(self->locals.condition, {
91 $(self->locals.tasks, addObject, task);
92 $(self->locals.condition, signal);
93 });
94 }
95
96 return task;
97}
98
104
106 request->httpMethod = HTTP_GET;
107
108 URLSessionDataTask *task = $(self, dataTaskWithRequest, request, completion);
109
111
112 return task;
113}
114
123
129
131
132 URLSessionDownloadTask *task = $(self, downloadTaskWithRequest, request, completion);
133
135
136 return task;
137}
138
143static URLSession *init(URLSession *self) {
144
146
147 self = $(self, initWithConfiguration, configuration);
148
149 release(configuration);
150
151 return self;
152}
153
157static ident run(Thread *thread) {
158
159 URLSession *self = thread->data;
160
161 self->locals.handle = curl_multi_init();
162 assert(self->locals.handle);
163
164 while (true) {
165 int ret;
166
167 bool idle;
168 synchronized(self->locals.condition, {
169 while (!thread->isCancelled && self->locals.tasks->count == 0) {
170 $(self->locals.condition, wait);
171 }
172 idle = thread->isCancelled && self->locals.tasks->count == 0;
173 });
174
175 if (idle) {
176 break;
177 }
178
179 Array *tasks = $(self, tasks);
180
181 for (size_t i = 0; i < tasks->count; i++) {
182
183 URLSessionTask *task = $(tasks, objectAtIndex, i);
184 if (task->state == URLSESSIONTASK_SUSPENDING) {
185
186 if (task->locals.handle) {
187 const CURLcode err = curl_easy_pause(task->locals.handle, CURLPAUSE_ALL);
188 assert(err == CURLE_OK);
189 }
190
192
193 } else if (task->state == URLSESSIONTASK_RESUMING) {
194
195 if (task->locals.handle == NULL) {
196
197 $(task, setup);
198 assert(task->locals.handle);
199
200 const CURLMcode merr = curl_multi_add_handle(self->locals.handle, task->locals.handle);
201 assert(merr == CURLM_OK);
202 } else {
203 const CURLcode err = curl_easy_pause(task->locals.handle, CURLPAUSE_CONT);
204 assert(err == CURLE_OK);
205 }
206
208
209 } else if (task->state == URLSESSIONTASK_CANCELING) {
210
211 if (task->locals.handle) {
212 const CURLMcode merr = curl_multi_remove_handle(self->locals.handle, task->locals.handle);
213 assert(merr == CURLM_OK);
214 }
215
217
218 if (task->completion) {
219 task->completion(task, false);
220 }
221
222 $(task, teardown);
223
224 synchronized(self->locals.condition, {
225 $(self->locals.tasks, removeObject, task);
226 });
227 } else if (task->state == URLSESSIONTASK_COMPLETED) {
228
229 synchronized(self->locals.condition, {
230 $(self->locals.tasks, removeObject, task);
231 });
232 }
233 }
234
235 CURLMcode merr = curl_multi_wait(self->locals.handle, NULL, 0, 0, NULL);
236 assert(merr == CURLM_OK);
237
238 merr = curl_multi_perform(self->locals.handle, &ret);
239 assert(merr == CURLM_OK);
240
241 CURLMsg *message;
242 while ((message = curl_multi_info_read(self->locals.handle, &ret))) {
243
244 URLSessionTask *task = NULL;
245 for (size_t i = 0; i < tasks->count; i++) {
246
248 if (t->locals.handle == message->easy_handle) {
249 task = t;
250 break;
251 }
252 }
253
254 assert(task);
255
256 if (message->msg == CURLMSG_DONE) {
257
258 merr = curl_multi_remove_handle(self->locals.handle, task->locals.handle);
259 assert(merr == CURLM_OK);
260
262
263 curl_easy_getinfo(task->locals.handle, CURLINFO_RESPONSE_CODE, (long *) &task->response->httpStatusCode);
264
265 if (message->data.result == CURLE_OK && $((Object *) task, isKindOfClass, _URLSessionDataTask())) {
267 }
268
269 if (task->completion) {
270 task->completion(task, message->data.result == CURLE_OK);
271 }
272
273 $(task, teardown);
274
275 synchronized(self->locals.condition, {
276 $(self->locals.tasks, removeObject, task);
277 });
278 }
279 }
280
281 release(tasks);
282 }
283
284 curl_multi_cleanup(self->locals.handle);
285
286 return NULL;
287}
288
294
295 assert(configuration);
296
297 self = (URLSession *) super(Object, self, init);
298 if (self) {
299 self->configuration = retain(configuration);
300
301 self->locals.condition = $(alloc(Condition), init);
302 self->locals.tasks = $(alloc(Array), init);
303 self->locals.thread = $(alloc(Thread), initWithFunction, run, self);
304
305 $(self->locals.thread, start);
306 }
307
308 return self;
309}
310
315static void invalidateAndCancel(URLSession *self) {
316
317 if (self->locals.thread->isCancelled) {
318 return;
319 }
320
321 Array *tasks = $(self, tasks);
322
323 for (size_t i = 0; i < tasks->count; i++) {
324
325 URLSessionTask *task = $(tasks, objectAtIndex, i);
326 $(task, cancel);
327 }
328
329 release(tasks);
330
331 $(self->locals.thread, cancel);
332 synchronized(self->locals.condition, $(self->locals.condition, signal));
333}
334
336
342
343 static Once once;
344
345 do_once(&once, {
347 });
348
349 return _sharedInstance;
350}
351
356static Array *tasks(const URLSession *self) {
357
358 Array *array;
359
360 synchronized(self->locals.condition, {
361 array = $$(Array, arrayWithArray, (Array *) self->locals.tasks);
362 });
363
364 return array;
365}
366
375
376#pragma mark - Class lifecycle
377
381static void destroy(Class *clazz) {
382
384
385 curl_global_cleanup();
386}
387
391static void initialize(Class *clazz) {
392
393 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
394
395 ((URLSessionInterface *) clazz->interface)->dataTaskWithRequest = dataTaskWithRequest;
396 ((URLSessionInterface *) clazz->interface)->dataTaskWithURL = dataTaskWithURL;
397 ((URLSessionInterface *) clazz->interface)->downloadTaskWithRequest = downloadTaskWithRequest;
398 ((URLSessionInterface *) clazz->interface)->downloadTaskWithURL = downloadTaskWithURL;
399 ((URLSessionInterface *) clazz->interface)->init = init;
400 ((URLSessionInterface *) clazz->interface)->initWithConfiguration = initWithConfiguration;
401 ((URLSessionInterface *) clazz->interface)->invalidateAndCancel = invalidateAndCancel;
402 ((URLSessionInterface *) clazz->interface)->sharedInstance = sharedInstance;
403 ((URLSessionInterface *) clazz->interface)->tasks = tasks;
404 ((URLSessionInterface *) clazz->interface)->uploadTaskWithRequest = uploadTaskWithRequest;
405
406 const CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
407 assert(code == CURLE_OK);
408}
409
415 static Class *clazz;
416 static Once once;
417
418 do_once(&once, {
419 clazz = _initialize(&(const ClassDef) {
420 .name = "URLSession",
421 .superclass = _Object(),
422 .instanceSize = sizeof(URLSession),
423 .interfaceOffset = offsetof(URLSession, interface),
424 .interfaceSize = sizeof(URLSessionInterface),
426 .destroy = destroy,
427 });
428 });
429
430 return clazz;
431}
432
433#undef _Class
static ident objectAtIndex(const Array *self, size_t index)
Definition Array.c:578
static void removeObject(Array *self, const ident obj)
Definition Array.c:642
static Array * arrayWithArray(const Array *array)
Definition Array.c:243
static void addObject(Array *self, const ident obj)
Definition Array.c:181
static Array * array(void)
Definition Array.c:234
static void teardown(void)
Called atexit to teardown Objectively.
Definition Class.c:46
static void setup(void)
Called when initializing Object to setup Objectively.
Definition Class.c:73
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 bool isKindOfClass(const Object *self, const Class *clazz)
Definition Object.c:101
Class * _Object(void)
Definition Object.c:136
static Operation * initWithFunction(Operation *self, OperationFunction function, ident data)
Definition Operation.c:119
static void start(Operation *self)
Definition Operation.c:171
static void cancel(Operation *self)
Definition Operation.c:74
static int request(RESTClient *self, HTTPMethod method, const char *url_string, const Data *body, Data **out_data)
Definition RESTClient.c:57
static void join(Thread *self, ident *status)
Definition Thread.c:130
void * ident
The identity type, similar to Objective-C id.
Definition Types.h:49
static URLCachedResponse * cachedResponseForRequest(URLCache *self, const URLRequest *request)
Definition URLCache.c:268
static URLRequest * initWithURL(URLRequest *self, URL *url)
Definition URLRequest.c:150
@ HTTP_GET
Definition URLRequest.h:46
static URLSessionDataTask * dataTaskWithRequest(URLSession *self, URLRequest *request, URLSessionTaskCompletion completion)
Definition URLSession.c:76
static void destroy(Class *clazz)
Definition URLSession.c:381
static URLSessionUploadTask * uploadTaskWithRequest(URLSession *self, URLRequest *request, URLSessionTaskCompletion completion)
Definition URLSession.c:371
static URLSession * initWithConfiguration(URLSession *self, URLSessionConfiguration *configuration)
Definition URLSession.c:293
Class * _URLSession(void)
Definition URLSession.c:414
static URLSessionDownloadTask * downloadTaskWithURL(URLSession *self, URL *url, URLSessionTaskCompletion completion)
Definition URLSession.c:128
static Array * tasks(const URLSession *self)
Definition URLSession.c:356
static URLSession * init(URLSession *self)
Definition URLSession.c:143
static ident taskWithRequest(URLSession *self, ident task, URLRequest *request, URLSessionTaskCompletion completion)
URLSessionTask factory function.
Definition URLSession.c:58
static URLSession * sharedInstance(void)
Definition URLSession.c:341
static ident run(Thread *thread)
ThreadFunction for a URLSession.
Definition URLSession.c:157
static void dealloc(Object *self)
Definition URLSession.c:37
static URLSessionDataTask * dataTaskWithURL(URLSession *self, URL *url, URLSessionTaskCompletion completion)
Definition URLSession.c:103
static URLSession * _sharedInstance
Definition URLSession.c:335
static void invalidateAndCancel(URLSession *self)
Definition URLSession.c:315
static void initialize(Class *clazz)
Definition URLSession.c:391
static URLSessionDownloadTask * downloadTaskWithRequest(URLSession *self, URLRequest *request, URLSessionTaskCompletion completion)
Definition URLSession.c:119
A management context for loading resources via URLs.
Class * _URLSessionDataTask(void)
static void cacheResponse(URLSessionDataTask *self)
static URLSessionTask * initWithRequestInSession(URLSessionTask *self, struct URLRequest *request, struct URLSession *session, URLSessionTaskCompletion completion)
void(* URLSessionTaskCompletion)(URLSessionTask *task, bool success)
A function pointer for URLSessionTask completion.
@ URLSESSIONTASK_RESUMING
@ URLSESSIONTASK_CANCELED
@ URLSESSIONTASK_SUSPENDING
@ URLSESSIONTASK_SUSPENDED
@ URLSESSIONTASK_COMPLETED
@ URLSESSIONTASK_RESUMED
@ URLSESSIONTASK_CANCELING
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
POSIX Threads conditional variables.
Definition Condition.h:44
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
POSIX Threads.
Definition Thread.h:53
ident data
The user data.
Definition Thread.h:69
bool isCancelled
true when this Thread has been cancelled, false otherwise.
Definition Thread.h:79
A cache for HTTP responses.
Definition URLCache.h:44
Uniform Resource Locators (RFC 3986).
Definition URL.h:44
A protocol-agnostic abstraction for requesting resources via URLs.
Definition URLRequest.h:58
int httpStatusCode
The HTTP response status code.
Definition URLResponse.h:63
Configuration bundle for URLSession.
URLCache * urlCache
The cache for URL responses, or NULL to disable caching.
Use data tasks to send and receive Data in-memory.
URLCachedResponse * cachedResponse
A cached response, if this task was fulfilled from URLCache.
Use download tasks to save remote resources to file.
A management context for loading resources via URLs.
Definition URLSession.h:57
Array * tasks
The URLSessionTasks.
Definition URLSession.h:87
Condition * condition
The condition, and the Lock guarding access to tasks.
Definition URLSession.h:77
URLSessionConfiguration * configuration
The session configuration.
Definition URLSession.h:98
Thread * thread
The backing Thread.
Definition URLSession.h:92
void invalidateAndCancel(URLSession *)
Invalidates this URLSession and cancels all pending tasks.
Definition URLSession.c:315
URLSession * initWithConfiguration(URLSession *, URLSessionConfiguration *)
Initializes this URLSession with the given configuration.
Definition URLSession.c:293
ident handle
The libcurl handle.
Definition URLSession.h:82
URL session tasks are handles to pending URL operations.
struct URLResponse * response
The response.
ident handle
The backing libcurl handle.
URLSessionTaskState state
The state.
URLSessionTaskCompletion completion
The completion function.
Use upload tasks to send files directly from disk.