]> code.delx.au - pulseaudio/blob - src/pulsecore/protocol-http.c
iochannel: Avoid unnecessary wakeup after successful write
[pulseaudio] / src / pulsecore / protocol-http.c
1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2005-2009 Lennart Poettering
5
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published
8 by the Free Software Foundation; either version 2.1 of the License,
9 or (at your option) any later version.
10
11 PulseAudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with PulseAudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19 USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <errno.h>
30
31 #include <pulse/util.h>
32 #include <pulse/xmalloc.h>
33 #include <pulse/timeval.h>
34
35 #include <pulsecore/core-util.h>
36 #include <pulsecore/ioline.h>
37 #include <pulsecore/thread-mq.h>
38 #include <pulsecore/macro.h>
39 #include <pulsecore/log.h>
40 #include <pulsecore/namereg.h>
41 #include <pulsecore/cli-text.h>
42 #include <pulsecore/shared.h>
43 #include <pulsecore/core-error.h>
44 #include <pulsecore/mime-type.h>
45
46 #include "protocol-http.h"
47
48 /* Don't allow more than this many concurrent connections */
49 #define MAX_CONNECTIONS 10
50
51 #define URL_ROOT "/"
52 #define URL_CSS "/style"
53 #define URL_STATUS "/status"
54 #define URL_LISTEN "/listen"
55 #define URL_LISTEN_SOURCE "/listen/source/"
56
57 #define MIME_HTML "text/html; charset=utf-8"
58 #define MIME_TEXT "text/plain; charset=utf-8"
59 #define MIME_CSS "text/css"
60
61 #define HTML_HEADER(t) \
62 "<?xml version=\"1.0\"?>\n" \
63 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" \
64 "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" \
65 " <head>\n" \
66 " <title>"t"</title>\n" \
67 " <link rel=\"stylesheet\" type=\"text/css\" href=\"style\"/>\n" \
68 " </head>\n" \
69 " <body>\n"
70
71 #define HTML_FOOTER \
72 " </body>\n" \
73 "</html>\n"
74
75 #define RECORD_BUFFER_SECONDS (5)
76 #define DEFAULT_SOURCE_LATENCY (300*PA_USEC_PER_MSEC)
77
78 enum state {
79 STATE_REQUEST_LINE,
80 STATE_MIME_HEADER,
81 STATE_DATA
82 };
83
84 enum method {
85 METHOD_GET,
86 METHOD_HEAD
87 };
88
89 struct connection {
90 pa_http_protocol *protocol;
91 pa_iochannel *io;
92 pa_ioline *line;
93 pa_memblockq *output_memblockq;
94 pa_source_output *source_output;
95 pa_client *client;
96 enum state state;
97 char *url;
98 enum method method;
99 pa_module *module;
100 };
101
102 struct pa_http_protocol {
103 PA_REFCNT_DECLARE;
104
105 pa_core *core;
106 pa_idxset *connections;
107
108 pa_strlist *servers;
109 };
110
111 enum {
112 SOURCE_OUTPUT_MESSAGE_POST_DATA = PA_SOURCE_OUTPUT_MESSAGE_MAX
113 };
114
115 /* Called from main context */
116 static void connection_unlink(struct connection *c) {
117 pa_assert(c);
118
119 if (c->source_output) {
120 pa_source_output_unlink(c->source_output);
121 c->source_output->userdata = NULL;
122 pa_source_output_unref(c->source_output);
123 }
124
125 if (c->client)
126 pa_client_free(c->client);
127
128 pa_xfree(c->url);
129
130 if (c->line)
131 pa_ioline_unref(c->line);
132
133 if (c->io)
134 pa_iochannel_free(c->io);
135
136 if (c->output_memblockq)
137 pa_memblockq_free(c->output_memblockq);
138
139 pa_idxset_remove_by_data(c->protocol->connections, c, NULL);
140
141 pa_xfree(c);
142 }
143
144 /* Called from main context */
145 static int do_write(struct connection *c) {
146 pa_memchunk chunk;
147 ssize_t r;
148 void *p;
149
150 pa_assert(c);
151
152 if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0)
153 return 0;
154
155 pa_assert(chunk.memblock);
156 pa_assert(chunk.length > 0);
157
158 p = pa_memblock_acquire(chunk.memblock);
159 r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length);
160 pa_memblock_release(chunk.memblock);
161
162 pa_memblock_unref(chunk.memblock);
163
164 if (r < 0) {
165 pa_log("write(): %s", pa_cstrerror(errno));
166 return -1;
167 }
168
169 pa_memblockq_drop(c->output_memblockq, (size_t) r);
170
171 return 0;
172 }
173
174 /* Called from main context */
175 static void do_work(struct connection *c) {
176 pa_assert(c);
177
178 if (pa_iochannel_is_hungup(c->io))
179 goto fail;
180
181 if (pa_iochannel_is_writable(c->io))
182 if (do_write(c) < 0)
183 goto fail;
184
185 return;
186
187 fail:
188 connection_unlink(c);
189 }
190
191 /* Called from thread context, except when it is not */
192 static int source_output_process_msg(pa_msgobject *m, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
193 pa_source_output *o = PA_SOURCE_OUTPUT(m);
194 struct connection *c;
195
196 pa_source_output_assert_ref(o);
197
198 if (!(c = o->userdata))
199 return -1;
200
201 switch (code) {
202
203 case SOURCE_OUTPUT_MESSAGE_POST_DATA:
204 /* While this function is usually called from IO thread
205 * context, this specific command is not! */
206 pa_memblockq_push_align(c->output_memblockq, chunk);
207 do_work(c);
208 break;
209
210 default:
211 return pa_source_output_process_msg(m, code, userdata, offset, chunk);
212 }
213
214 return 0;
215 }
216
217 /* Called from thread context */
218 static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
219 struct connection *c;
220
221 pa_source_output_assert_ref(o);
222 pa_assert_se(c = o->userdata);
223 pa_assert(chunk);
224
225 pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(o), SOURCE_OUTPUT_MESSAGE_POST_DATA, NULL, 0, chunk, NULL);
226 }
227
228 /* Called from main context */
229 static void source_output_kill_cb(pa_source_output *o) {
230 struct connection*c;
231
232 pa_source_output_assert_ref(o);
233 pa_assert_se(c = o->userdata);
234
235 connection_unlink(c);
236 }
237
238 /* Called from main context */
239 static pa_usec_t source_output_get_latency_cb(pa_source_output *o) {
240 struct connection*c;
241
242 pa_source_output_assert_ref(o);
243 pa_assert_se(c = o->userdata);
244
245 return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec);
246 }
247
248 /*** client callbacks ***/
249 static void client_kill_cb(pa_client *client) {
250 struct connection*c;
251
252 pa_assert(client);
253 pa_assert_se(c = client->userdata);
254
255 connection_unlink(c);
256 }
257
258 /*** pa_iochannel callbacks ***/
259 static void io_callback(pa_iochannel*io, void *userdata) {
260 struct connection *c = userdata;
261
262 pa_assert(c);
263 pa_assert(io);
264
265 do_work(c);
266 }
267
268 static char *escape_html(const char *t) {
269 pa_strbuf *sb;
270 const char *p, *e;
271
272 sb = pa_strbuf_new();
273
274 for (e = p = t; *p; p++) {
275
276 if (*p == '>' || *p == '<' || *p == '&') {
277
278 if (p > e) {
279 pa_strbuf_putsn(sb, e, p-e);
280 e = p + 1;
281 }
282
283 if (*p == '>')
284 pa_strbuf_puts(sb, "&gt;");
285 else if (*p == '<')
286 pa_strbuf_puts(sb, "&lt;");
287 else
288 pa_strbuf_puts(sb, "&amp;");
289 }
290 }
291
292 if (p > e)
293 pa_strbuf_putsn(sb, e, p-e);
294
295 return pa_strbuf_tostring_free(sb);
296 }
297
298 static void http_response(
299 struct connection *c,
300 int code,
301 const char *msg,
302 const char *mime) {
303
304 char *s;
305
306 pa_assert(c);
307 pa_assert(msg);
308 pa_assert(mime);
309
310 s = pa_sprintf_malloc(
311 "HTTP/1.0 %i %s\n"
312 "Connection: close\n"
313 "Content-Type: %s\n"
314 "Cache-Control: no-cache\n"
315 "Expires: 0\n"
316 "Server: "PACKAGE_NAME"/"PACKAGE_VERSION"\n"
317 "\n", code, msg, mime);
318 pa_ioline_puts(c->line, s);
319 pa_xfree(s);
320 }
321
322 static void html_response(
323 struct connection *c,
324 int code,
325 const char *msg,
326 const char *text) {
327
328 char *s;
329 pa_assert(c);
330
331 http_response(c, code, msg, MIME_HTML);
332
333 if (c->method == METHOD_HEAD) {
334 pa_ioline_defer_close(c->line);
335 return;
336 }
337
338 if (!text)
339 text = msg;
340
341 s = pa_sprintf_malloc(
342 HTML_HEADER("%s")
343 "%s"
344 HTML_FOOTER,
345 text, text);
346
347 pa_ioline_puts(c->line, s);
348 pa_xfree(s);
349
350 pa_ioline_defer_close(c->line);
351 }
352
353 static void html_print_field(pa_ioline *line, const char *left, const char *right) {
354 char *eleft, *eright;
355
356 eleft = escape_html(left);
357 eright = escape_html(right);
358
359 pa_ioline_printf(line,
360 "<tr><td><b>%s</b></td>"
361 "<td>%s</td></tr>\n", eleft, eright);
362
363 pa_xfree(eleft);
364 pa_xfree(eright);
365 }
366
367 static void handle_root(struct connection *c) {
368 char *t;
369
370 pa_assert(c);
371
372 http_response(c, 200, "OK", MIME_HTML);
373
374 if (c->method == METHOD_HEAD) {
375 pa_ioline_defer_close(c->line);
376 return;
377 }
378
379 pa_ioline_puts(c->line,
380 HTML_HEADER(PACKAGE_NAME" "PACKAGE_VERSION)
381 "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n"
382 "<table>\n");
383
384 t = pa_get_user_name_malloc();
385 html_print_field(c->line, "User Name:", t);
386 pa_xfree(t);
387
388 t = pa_get_host_name_malloc();
389 html_print_field(c->line, "Host name:", t);
390 pa_xfree(t);
391
392 t = pa_machine_id();
393 html_print_field(c->line, "Machine ID:", t);
394 pa_xfree(t);
395
396 t = pa_uname_string();
397 html_print_field(c->line, "System:", t);
398 pa_xfree(t);
399
400 t = pa_sprintf_malloc("%lu", (unsigned long) getpid());
401 html_print_field(c->line, "Process ID:", t);
402 pa_xfree(t);
403
404 pa_ioline_puts(c->line,
405 "</table>\n"
406 "<p><a href=\"" URL_STATUS "\">Show an extensive server status report</a></p>\n"
407 "<p><a href=\"" URL_LISTEN "\">Monitor sinks and sources</a></p>\n"
408 HTML_FOOTER);
409
410 pa_ioline_defer_close(c->line);
411 }
412
413 static void handle_css(struct connection *c) {
414 pa_assert(c);
415
416 http_response(c, 200, "OK", MIME_CSS);
417
418 if (c->method == METHOD_HEAD) {
419 pa_ioline_defer_close(c->line);
420 return;
421 }
422
423 pa_ioline_puts(c->line,
424 "body { color: black; background-color: white; }\n"
425 "a:link, a:visited { color: #900000; }\n"
426 "div.news-date { font-size: 80%; font-style: italic; }\n"
427 "pre { background-color: #f0f0f0; padding: 0.4cm; }\n"
428 ".grey { color: #8f8f8f; font-size: 80%; }"
429 "table { margin-left: 1cm; border:1px solid lightgrey; padding: 0.2cm; }\n"
430 "td { padding-left:10px; padding-right:10px; }\n");
431
432 pa_ioline_defer_close(c->line);
433 }
434
435 static void handle_status(struct connection *c) {
436 char *r;
437
438 pa_assert(c);
439
440 http_response(c, 200, "OK", MIME_TEXT);
441
442 if (c->method == METHOD_HEAD) {
443 pa_ioline_defer_close(c->line);
444 return;
445 }
446
447 r = pa_full_status_string(c->protocol->core);
448 pa_ioline_puts(c->line, r);
449 pa_xfree(r);
450
451 pa_ioline_defer_close(c->line);
452 }
453
454 static void handle_listen(struct connection *c) {
455 pa_source *source;
456 pa_sink *sink;
457 uint32_t idx;
458
459 http_response(c, 200, "OK", MIME_HTML);
460
461 pa_ioline_puts(c->line,
462 HTML_HEADER("Listen")
463 "<h2>Sinks</h2>\n"
464 "<p>\n");
465
466 if (c->method == METHOD_HEAD) {
467 pa_ioline_defer_close(c->line);
468 return;
469 }
470
471 PA_IDXSET_FOREACH(sink, c->protocol->core->sinks, idx) {
472 char *t, *m;
473
474 t = escape_html(pa_strna(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)));
475 m = pa_sample_spec_to_mime_type_mimefy(&sink->sample_spec, &sink->channel_map);
476
477 pa_ioline_printf(c->line,
478 "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n",
479 sink->monitor_source->name, m, t);
480
481 pa_xfree(t);
482 pa_xfree(m);
483 }
484
485 pa_ioline_puts(c->line,
486 "</p>\n"
487 "<h2>Sources</h2>\n"
488 "<p>\n");
489
490 PA_IDXSET_FOREACH(source, c->protocol->core->sources, idx) {
491 char *t, *m;
492
493 if (source->monitor_of)
494 continue;
495
496 t = escape_html(pa_strna(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)));
497 m = pa_sample_spec_to_mime_type_mimefy(&source->sample_spec, &source->channel_map);
498
499 pa_ioline_printf(c->line,
500 "<a href=\"" URL_LISTEN_SOURCE "%s\" title=\"%s\">%s</a><br/>\n",
501 source->name, m, t);
502
503 pa_xfree(m);
504 pa_xfree(t);
505
506 }
507
508 pa_ioline_puts(c->line,
509 "</p>\n"
510 HTML_FOOTER);
511
512 pa_ioline_defer_close(c->line);
513 }
514
515 static void line_drain_callback(pa_ioline *l, void *userdata) {
516 struct connection *c;
517
518 pa_assert(l);
519 pa_assert_se(c = userdata);
520
521 /* We don't need the line reader anymore, instead we need a real
522 * binary io channel */
523 pa_assert_se(c->io = pa_ioline_detach_iochannel(c->line));
524 pa_iochannel_set_callback(c->io, io_callback, c);
525
526 pa_iochannel_socket_set_sndbuf(c->io, pa_memblockq_get_length(c->output_memblockq));
527
528 pa_ioline_unref(c->line);
529 c->line = NULL;
530 }
531
532 static void handle_listen_prefix(struct connection *c, const char *source_name) {
533 pa_source *source;
534 pa_source_output_new_data data;
535 pa_sample_spec ss;
536 pa_channel_map cm;
537 char *t;
538 size_t l;
539
540 pa_assert(c);
541 pa_assert(source_name);
542
543 pa_assert(c->line);
544 pa_assert(!c->io);
545
546 if (!(source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE))) {
547 html_response(c, 404, "Source not found", NULL);
548 return;
549 }
550
551 ss = source->sample_spec;
552 cm = source->channel_map;
553
554 pa_sample_spec_mimefy(&ss, &cm);
555
556 pa_source_output_new_data_init(&data);
557 data.driver = __FILE__;
558 data.module = c->module;
559 data.client = c->client;
560 pa_source_output_new_data_set_source(&data, source, FALSE);
561 pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist);
562 pa_source_output_new_data_set_sample_spec(&data, &ss);
563 pa_source_output_new_data_set_channel_map(&data, &cm);
564
565 pa_source_output_new(&c->source_output, c->protocol->core, &data);
566 pa_source_output_new_data_done(&data);
567
568 if (!c->source_output) {
569 html_response(c, 403, "Cannot create source output", NULL);
570 return;
571 }
572
573 c->source_output->parent.process_msg = source_output_process_msg;
574 c->source_output->push = source_output_push_cb;
575 c->source_output->kill = source_output_kill_cb;
576 c->source_output->get_latency = source_output_get_latency_cb;
577 c->source_output->userdata = c;
578
579 pa_source_output_set_requested_latency(c->source_output, DEFAULT_SOURCE_LATENCY);
580
581 l = (size_t) (pa_bytes_per_second(&ss)*RECORD_BUFFER_SECONDS);
582 c->output_memblockq = pa_memblockq_new(
583 "http protocol connection output_memblockq",
584 0,
585 l,
586 0,
587 &ss,
588 1,
589 0,
590 0,
591 NULL);
592
593 pa_source_output_put(c->source_output);
594
595 t = pa_sample_spec_to_mime_type(&ss, &cm);
596 http_response(c, 200, "OK", t);
597 pa_xfree(t);
598
599 if(c->method == METHOD_HEAD) {
600 connection_unlink(c);
601 return;
602 }
603 pa_ioline_set_callback(c->line, NULL, NULL);
604
605 if (pa_ioline_is_drained(c->line))
606 line_drain_callback(c->line, c);
607 else
608 pa_ioline_set_drain_callback(c->line, line_drain_callback, c);
609 }
610
611 static void handle_url(struct connection *c) {
612 pa_assert(c);
613
614 pa_log_debug("Request for %s", c->url);
615
616 if (pa_streq(c->url, URL_ROOT))
617 handle_root(c);
618 else if (pa_streq(c->url, URL_CSS))
619 handle_css(c);
620 else if (pa_streq(c->url, URL_STATUS))
621 handle_status(c);
622 else if (pa_streq(c->url, URL_LISTEN))
623 handle_listen(c);
624 else if (pa_startswith(c->url, URL_LISTEN_SOURCE))
625 handle_listen_prefix(c, c->url + sizeof(URL_LISTEN_SOURCE)-1);
626 else
627 html_response(c, 404, "Not Found", NULL);
628 }
629
630 static void line_callback(pa_ioline *line, const char *s, void *userdata) {
631 struct connection *c = userdata;
632 pa_assert(line);
633 pa_assert(c);
634
635 if (!s) {
636 /* EOF */
637 connection_unlink(c);
638 return;
639 }
640
641 switch (c->state) {
642 case STATE_REQUEST_LINE: {
643 if (pa_startswith(s, "GET ")) {
644 c->method = METHOD_GET;
645 s +=4;
646 } else if (pa_startswith(s, "HEAD ")) {
647 c->method = METHOD_HEAD;
648 s +=5;
649 } else {
650 goto fail;
651 }
652
653 c->url = pa_xstrndup(s, strcspn(s, " \r\n\t?"));
654 c->state = STATE_MIME_HEADER;
655 break;
656 }
657
658 case STATE_MIME_HEADER: {
659
660 /* Ignore MIME headers */
661 if (strcspn(s, " \r\n") != 0)
662 break;
663
664 /* We're done */
665 c->state = STATE_DATA;
666
667 handle_url(c);
668 break;
669 }
670
671 default:
672 ;
673 }
674
675 return;
676
677 fail:
678 html_response(c, 500, "Internal Server Error", NULL);
679 }
680
681 void pa_http_protocol_connect(pa_http_protocol *p, pa_iochannel *io, pa_module *m) {
682 struct connection *c;
683 pa_client_new_data client_data;
684 char pname[128];
685
686 pa_assert(p);
687 pa_assert(io);
688 pa_assert(m);
689
690 if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) {
691 pa_log("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS);
692 pa_iochannel_free(io);
693 return;
694 }
695
696 c = pa_xnew0(struct connection, 1);
697 c->protocol = p;
698 c->state = STATE_REQUEST_LINE;
699 c->module = m;
700
701 c->line = pa_ioline_new(io);
702 pa_ioline_set_callback(c->line, line_callback, c);
703
704 pa_client_new_data_init(&client_data);
705 client_data.module = c->module;
706 client_data.driver = __FILE__;
707 pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname));
708 pa_proplist_setf(client_data.proplist, PA_PROP_APPLICATION_NAME, "HTTP client (%s)", pname);
709 pa_proplist_sets(client_data.proplist, "http-protocol.peer", pname);
710 c->client = pa_client_new(p->core, &client_data);
711 pa_client_new_data_done(&client_data);
712
713 if (!c->client)
714 goto fail;
715
716 c->client->kill = client_kill_cb;
717 c->client->userdata = c;
718
719 pa_idxset_put(p->connections, c, NULL);
720
721 return;
722
723 fail:
724 if (c)
725 connection_unlink(c);
726 }
727
728 void pa_http_protocol_disconnect(pa_http_protocol *p, pa_module *m) {
729 struct connection *c;
730 uint32_t idx;
731
732 pa_assert(p);
733 pa_assert(m);
734
735 PA_IDXSET_FOREACH(c, p->connections, idx)
736 if (c->module == m)
737 connection_unlink(c);
738 }
739
740 static pa_http_protocol* http_protocol_new(pa_core *c) {
741 pa_http_protocol *p;
742
743 pa_assert(c);
744
745 p = pa_xnew0(pa_http_protocol, 1);
746 PA_REFCNT_INIT(p);
747 p->core = c;
748 p->connections = pa_idxset_new(NULL, NULL);
749
750 pa_assert_se(pa_shared_set(c, "http-protocol", p) >= 0);
751
752 return p;
753 }
754
755 pa_http_protocol* pa_http_protocol_get(pa_core *c) {
756 pa_http_protocol *p;
757
758 if ((p = pa_shared_get(c, "http-protocol")))
759 return pa_http_protocol_ref(p);
760
761 return http_protocol_new(c);
762 }
763
764 pa_http_protocol* pa_http_protocol_ref(pa_http_protocol *p) {
765 pa_assert(p);
766 pa_assert(PA_REFCNT_VALUE(p) >= 1);
767
768 PA_REFCNT_INC(p);
769
770 return p;
771 }
772
773 void pa_http_protocol_unref(pa_http_protocol *p) {
774 struct connection *c;
775
776 pa_assert(p);
777 pa_assert(PA_REFCNT_VALUE(p) >= 1);
778
779 if (PA_REFCNT_DEC(p) > 0)
780 return;
781
782 while ((c = pa_idxset_first(p->connections, NULL)))
783 connection_unlink(c);
784
785 pa_idxset_free(p->connections, NULL);
786
787 pa_strlist_free(p->servers);
788
789 pa_assert_se(pa_shared_remove(p->core, "http-protocol") >= 0);
790
791 pa_xfree(p);
792 }
793
794 void pa_http_protocol_add_server_string(pa_http_protocol *p, const char *name) {
795 pa_assert(p);
796 pa_assert(PA_REFCNT_VALUE(p) >= 1);
797 pa_assert(name);
798
799 p->servers = pa_strlist_prepend(p->servers, name);
800 }
801
802 void pa_http_protocol_remove_server_string(pa_http_protocol *p, const char *name) {
803 pa_assert(p);
804 pa_assert(PA_REFCNT_VALUE(p) >= 1);
805 pa_assert(name);
806
807 p->servers = pa_strlist_remove(p->servers, name);
808 }
809
810 pa_strlist *pa_http_protocol_servers(pa_http_protocol *p) {
811 pa_assert(p);
812 pa_assert(PA_REFCNT_VALUE(p) >= 1);
813
814 return p->servers;
815 }