-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathasynk.module
160 lines (136 loc) · 4.44 KB
/
asynk.module
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
<?php
/**
* @file
* Module file for Asynk asynchronous tasks.
*/
/**
* Executes registered asynchronous tasks.
*
* In order to execute any function asynchronously call them using
* this function with arguments for the task.
* Example usage:
* asynk('sample_heavy_task', $var1, $var2);
* In the above example, we are executing function simple_heavy_task()
* asynchronously with arguments $var1 and $var2.
* Example usage 2:
* asynk('drupal_mail', $module, $key, $to, $lan, $params);
* In the above example, we are sending email using drupal_mail().
*
* @param string $action
* The function name which needs to be executed asynchronously.
*/
function asynk($action) {
// Get all arguments from the function.
$args = func_get_args();
// We need atleast one argument, which is the function name
// that we need to run asynchronously.
if (empty($action)) {
throw new Exception('An existing function name is required as first argument');
}
// First argument of the shutdown function should be
// our custom asynchronous action.
$params = array('asynk_request');
// Pass the additional arguments.
$params = array_merge($params, $args);
// Register our custom shutdown function for asynchronous
// tasks, with additional arguments as arguments.
call_user_func_array('register_shutdown_function', $params);
}
/**
* Asynk custom HTTP request for asynchronous tasks.
*
* Here is the trick. We are sending a POST HTTP request
* to our custom callback function with the arguments as
* post data. We have low timout limit so the maximum execution
* time for the asynchronous actions will be very less.
* Adjust the timeout limit for large arguments.
*/
function asynk_request() {
// Get all arguments from the function.
$data['args'] = func_get_args();
// We need our heavy function name as first argument.
if (empty($data)) {
return;
}
// Create a security token.
$data['asynk_token'] = drupal_get_token('asynk_token');
// Build query from data.
$data = drupal_http_build_query($data);
// Timeout for HTTP request.
$timeout = 2;
// Other modules can alter this timeout limit.
// We are not going to wait for HTTP response.
// So no need to make it high.
drupal_alter('asynk_http_timeout', $timeout);
// Options for Drupal HTTP request.
$options = array(
'method' => 'POST',
'data' => $data,
'timeout' => $timeout,
'headers' => array('Content-Type' => 'application/x-www-form-urlencoded'),
);
// Send post data to our custom page with arguments data.
drupal_http_request(url('asynk', array('absolute' => TRUE)), $options);
}
/**
* Implements hook_theme().
*
* Create a custom page to handle our HTTP request
* for asynchronous actions. This is a public link
* which does not require any authentication.
*
* @todo Add security token to avoid CSRF exploit.
*/
function asynk_menu() {
// Our custom page.
$items['asynk'] = array(
'title' => 'Asynk',
'page callback' => 'asynk_request_handler',
'access callback' => 'asynk_access_callback',
);
return $items;
}
/**
* Access check for Asynk HTTP request.
*
* Validate the current request to our custom
* asynk request handler page. We need to verify
* access to this page is only for our module.
* Otherwise attackers can send post request to
* execute any actions easily.
* TODO: Implement more reliable, secure token.
*
* @return bool
* Valid request or not.
*/
function asynk_access_callback() {
// If token not found in post.
if (empty($_POST['asynk_token'])) {
return FALSE;
}
// Validate the token value.
return drupal_valid_token($_POST['asynk_token'], 'asynk_token', TRUE);
}
/**
* Request handler for asynchronous tasks.
*
* Finally! Execute our heavy function now.
* Get the function name from $_POST and execute it right away.
* If there are additional arguments, it should be available
* in post data. We need to pass them to the heavy function.
* $_POST data will be numerice keyed array. The first item
* will be our heavy function name. If it is not set, bail out.
*/
function asynk_request_handler() {
// Get post data.
$data = empty($_POST['args']) ? array() : $_POST['args'];
// Continue only if we have function name in post.
if (!empty($data[0])) {
// We don't need the function name as argument.
unset($data[0]);
// Execute the heavy function now.
call_user_func_array($_POST['args'][0], $data);
}
// Exit the execution after the request.
exit();
}