Ticket #1657: binary_streaming.1.5.0-svn-r2146.mod_flv_streaming.c

File binary_streaming.1.5.0-svn-r2146.mod_flv_streaming.c, 8.5 kB (added by snailfly, 2 months ago)
Line 
1 #include <ctype.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 #include "base.h"
6 #include "log.h"
7 #include "buffer.h"
8 #include "response.h"
9 #include "stat_cache.h"
10
11 #include "plugin.h"
12
13 #ifdef HAVE_CONFIG_H
14 #include "config.h"
15 #endif
16
17 /* plugin config for all request/connections */
18
19 typedef struct {
20         array *extensions;
21         unsigned short binary_streaming;
22 } plugin_config;
23
24 typedef struct {
25         PLUGIN_DATA;
26
27         buffer *query_str;
28         array *get_params;
29
30         plugin_config **config_storage;
31
32         plugin_config conf;
33 } plugin_data;
34
35 /* init the plugin data */
36 INIT_FUNC(mod_flv_streaming_init) {
37         plugin_data *p;
38
39         UNUSED(srv);
40
41         p = calloc(1, sizeof(*p));
42
43         p->query_str = buffer_init();
44         p->get_params = array_init();
45
46         return p;
47 }
48
49 /* detroy the plugin data */
50 FREE_FUNC(mod_flv_streaming_free) {
51         plugin_data *p = p_d;
52
53         UNUSED(srv);
54
55         if (!p) return HANDLER_GO_ON;
56
57         if (p->config_storage) {
58                 size_t i;
59
60                 for (i = 0; i < srv->config_context->used; i++) {
61                         plugin_config *s = p->config_storage[i];
62
63                         if (!s) continue;
64
65                         array_free(s->extensions);
66
67                         free(s);
68                 }
69                 free(p->config_storage);
70         }
71
72         buffer_free(p->query_str);
73         array_free(p->get_params);
74
75         free(p);
76
77         return HANDLER_GO_ON;
78 }
79
80 /* handle plugin config and check values */
81
82 SETDEFAULTS_FUNC(mod_flv_streaming_set_defaults) {
83         plugin_data *p = p_d;
84         size_t i = 0;
85
86         config_values_t cv[] = {
87                 { "flv-streaming.extensions",   NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },       /* 0 */
88                 { "flv-streaming.binary-streaming", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION },       /* 1 */
89                 { NULL,                         NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
90         };
91
92         if (!p) return HANDLER_ERROR;
93
94         p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
95
96         for (i = 0; i < srv->config_context->used; i++) {
97                 plugin_config *s;
98
99                 s = calloc(1, sizeof(plugin_config));
100                 s->extensions     = array_init();
101                 s->binary_streaming     = 0;
102
103                 cv[0].destination = s->extensions;
104                 cv[1].destination = &(s->binary_streaming);
105
106                 p->config_storage[i] = s;
107
108                 if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
109                         return HANDLER_ERROR;
110                 }
111         }
112
113         return HANDLER_GO_ON;
114 }
115
116 static int mod_flv_streaming_patch_connection(server *srv, connection *con, plugin_data *p) {
117         size_t i, j;
118         plugin_config *s = p->config_storage[0];
119
120         PATCH_OPTION(extensions);
121         PATCH_OPTION(binary_streaming);
122
123         /* skip the first, the global context */
124         for (i = 1; i < srv->config_context->used; i++) {
125                 data_config *dc = (data_config *)srv->config_context->data[i];
126                 s = p->config_storage[i];
127
128                 /* condition didn't match */
129                 if (!config_check_cond(srv, con, dc)) continue;
130
131                 /* merge config */
132                 for (j = 0; j < dc->value->used; j++) {
133                         data_unset *du = dc->value->data[j];
134
135                         if (buffer_is_equal_string(du->key, CONST_STR_LEN("flv-streaming.extensions"))) {
136                                 PATCH_OPTION(extensions);
137                         }else if (buffer_is_equal_string(du->key, CONST_STR_LEN("flv-streaming.binary-streaming"))) {
138                                 PATCH_OPTION(binary_streaming);
139                         }
140                 }
141         }
142
143         return 0;
144 }
145
146 static int split_get_params(array *get_params, buffer *qrystr) {
147         size_t is_key = 1;
148         size_t i;
149         char *key = NULL, *val = NULL;
150
151         key = qrystr->ptr;
152
153         /* we need the \0 */
154         for (i = 0; i < qrystr->used; i++) {
155                 switch(qrystr->ptr[i]) {
156                 case '=':
157                         if (is_key) {
158                                 val = qrystr->ptr + i + 1;
159
160                                 qrystr->ptr[i] = '\0';
161
162                                 is_key = 0;
163                         }
164
165                         break;
166                 case '&':
167                 case '\0': /* fin symbol */
168                         if (!is_key) {
169                                 data_string *ds;
170                                 /* we need at least a = since the last & */
171
172                                 /* terminate the value */
173                                 qrystr->ptr[i] = '\0';
174
175                                 if (NULL == (ds = (data_string *)array_get_unused_element(get_params, TYPE_STRING))) {
176                                         ds = data_string_init();
177                                 }
178                                 buffer_copy_string_len(ds->key, key, strlen(key));
179                                 buffer_copy_string_len(ds->value, val, strlen(val));
180
181                                 array_insert_unique(get_params, (data_unset *)ds);
182                         }
183
184                         key = qrystr->ptr + i + 1;
185                         val = NULL;
186                         is_key = 1;
187                         break;
188                 }
189         }
190
191         return 0;
192 }
193
194 URIHANDLER_FUNC(mod_flv_streaming_path_handler) {
195         plugin_data *p = p_d;
196         int s_len;
197         size_t k;
198
199         UNUSED(srv);
200
201         if (buffer_is_empty(con->physical.path)) return HANDLER_GO_ON;
202
203         if (con->conf.log_request_handling) {
204                 TRACE("-- handling %s in mod_flv_streaming", BUF_STR(con->physical.path));
205         }
206
207         mod_flv_streaming_patch_connection(srv, con, p);
208
209         s_len = con->physical.path->used - 1;
210
211         for (k = 0; k < p->conf.extensions->used; k++) {
212                 data_string *ds = (data_string *)p->conf.extensions->data[k];
213                 int ct_len = ds->value->used - 1;
214
215                 if (ct_len > s_len) continue;
216                 if (ds->value->used == 0) continue;
217
218                 if (0 == strncmp(con->physical.path->ptr + s_len - ct_len, ds->value->ptr, ct_len)) {
219                         data_string *get_param;
220                         stat_cache_entry *sce = NULL;
221                         buffer *b;
222                         long start;
223                         char *err = NULL;
224                         /* if there is a start=[0-9]+ in the header use it as start,
225                          * otherwise send the full file */
226
227                         array_reset(p->get_params);
228                         buffer_copy_string_buffer(p->query_str, con->uri.query);
229                         split_get_params(p->get_params, p->query_str);
230
231                         if (NULL == (get_param = (data_string *)array_get_element(p->get_params, CONST_STR_LEN("start")))) {
232                                 if (con->conf.log_request_handling) {
233                                         TRACE("start=... not found, skipping %s", BUF_STR(con->physical.path));
234                                 }
235
236                                 return HANDLER_GO_ON;
237                         }
238
239                         /* too short */
240                         if (get_param->value->used < 2) {
241                                 if (con->conf.log_request_handling) {
242                                         TRACE("start=... found, but empty, skipping %s", BUF_STR(con->physical.path));
243                                 }
244
245                                 return HANDLER_GO_ON;
246                         }
247
248                         /* check if it is a number */
249                         start = strtol(get_param->value->ptr, &err, 10);
250                         if (*err != '\0') {
251                                 if (con->conf.log_request_handling) {
252                                         TRACE("parsing start '%s' as number failed, skipping %s",
253                                                         BUF_STR(get_param->value), BUF_STR(con->physical.path));
254                                 }
255
256                                 return HANDLER_GO_ON;
257                         }
258
259                         if (start <= 0) {
260                                 if (con->conf.log_request_handling) {
261                                         TRACE("start is <= 0, skipping %s", BUF_STR(con->physical.path));
262                                 }
263
264                                 return HANDLER_GO_ON;
265                         }
266
267                         /* check if start is > filesize */
268                         if (HANDLER_GO_ON != stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
269                                 if (con->conf.log_request_handling) {
270                                         TRACE("stat() for %s failed", BUF_STR(con->physical.path));
271                                 }
272
273                                 return HANDLER_GO_ON;
274                         }
275
276                         if (start > sce->st.st_size) {
277                                 if (con->conf.log_request_handling) {
278                                         TRACE("start > file-size, skipping %s", BUF_STR(con->physical.path));
279                                 }
280
281                                 return HANDLER_GO_ON;
282                         }
283
284                         if(p->conf.binary_streaming){
285                                
286                                 /* we are safe now, let's build a Range request header */
287                                
288                                 //request: Range: bytes=217404-
289                                 //response: Content-Range: bytes 217404-3043664/3043665
290                                
291                                 data_string *hdr;
292                                
293                                 if (NULL == (hdr = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) {
294                                         hdr = data_string_init();
295                                 }
296                        
297                                 buffer_copy_string(hdr->key, "Range");
298                                 buffer_copy_string(hdr->value, "bytes=");
299                                 buffer_append_long(hdr->value, start);
300                                 buffer_append_string(hdr->value, "-");
301                        
302                                 array_insert_unique(con->request.headers, (data_unset *)hdr);
303                        
304                                 sendlen = sce->st.st_size - start;
305                                 if (con->conf.log_request_handling) {
306                                         TRACE("sending %s from position %d, size %d ( %d to %d ).", con->physical.path->ptr, start, sendlen, start, sce->st.st_size);
307                                 }
308                        
309                                 return HANDLER_GO_ON;
310                         }else{
311                                 /* we are safe now, let's build a flv header */
312                                 b = chunkqueue_get_append_buffer(con->send);
313                                 BUFFER_COPY_STRING_CONST(b, "FLV\x1\x1\0\0\0\x9\0\0\0\x9");
314                                
315                                 chunkqueue_append_file(con->send, con->physical.path, start, sce->st.st_size - start);
316                                
317                                 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("video/x-flv"));
318                                
319                                 con->send->is_closed = 1;
320                                
321                                 if (con->conf.log_request_handling) {
322                                         TRACE("sending %s from position %ld", BUF_STR(con->physical.path), start);
323                                 }
324                                
325                                 return HANDLER_FINISHED;
326                         }
327                 }
328         }
329
330         if (con->conf.log_request_handling) {
331                 TRACE("none of the extensions matched %s, leaving", BUF_STR(con->physical.path));
332         }
333
334         /* not found */
335         return HANDLER_GO_ON;
336 }
337
338 /* this function is called at dlopen() time and inits the callbacks */
339
340 LI_EXPORT int mod_flv_streaming_plugin_init(plugin *p) {
341         p->version     = LIGHTTPD_VERSION_ID;
342         p->name        = buffer_init_string("flv_streaming");
343
344         p->init        = mod_flv_streaming_init;
345         p->handle_physical = mod_flv_streaming_path_handler;
346         p->set_defaults  = mod_flv_streaming_set_defaults;
347         p->cleanup     = mod_flv_streaming_free;
348
349         p->data        = NULL;
350
351         return 0;
352 }