Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
M
mpd
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Иван Мажукин
mpd
Commits
70879356
Commit
70879356
authored
Jan 15, 2013
by
Max Kellermann
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
output/httpd: convert to C++
parent
5822daa6
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
914 additions
and
991 deletions
+914
-991
Makefile.am
Makefile.am
+3
-4
OutputList.cxx
src/OutputList.cxx
+1
-1
HttpdClient.cxx
src/output/HttpdClient.cxx
+592
-0
HttpdClient.hxx
src/output/HttpdClient.hxx
+215
-0
HttpdInternal.hxx
src/output/HttpdInternal.hxx
+9
-5
HttpdOutputPlugin.cxx
src/output/HttpdOutputPlugin.cxx
+91
-144
HttpdOutputPlugin.hxx
src/output/HttpdOutputPlugin.hxx
+3
-3
httpd_client.c
src/output/httpd_client.c
+0
-764
httpd_client.h
src/output/httpd_client.h
+0
-70
No files found.
Makefile.am
View file @
70879356
...
...
@@ -101,8 +101,6 @@ mpd_headers = \
src/AudioCompress/compress.h
\
src/path.h
\
src/open.h
\
src/output/httpd_client.h
\
src/output/httpd_internal.h
\
src/page.h
\
src/Playlist.hxx
\
src/playlist_error.h
\
...
...
@@ -899,8 +897,9 @@ endif
if
ENABLE_HTTPD_OUTPUT
liboutput_plugins_a_SOURCES
+=
\
src/icy_server.c
\
src/output/httpd_client.c
\
src/output/httpd_output_plugin.c src/output/httpd_output_plugin.h
src/output/HttpdInternal.hxx
\
src/output/HttpdClient.cxx src/output/HttpdClient.hxx
\
src/output/HttpdOutputPlugin.cxx src/output/HttpdOutputPlugin.hxx
endif
if
ENABLE_SOLARIS_OUTPUT
...
...
src/OutputList.cxx
View file @
70879356
...
...
@@ -24,7 +24,7 @@
#include "output/ao_output_plugin.h"
#include "output/ffado_output_plugin.h"
#include "output/fifo_output_plugin.h"
#include "output/
httpd_output_plugin.h
"
#include "output/
HttpdOutputPlugin.hxx
"
#include "output/jack_output_plugin.h"
#include "output/mvp_output_plugin.h"
#include "output/null_output_plugin.h"
...
...
src/output/HttpdClient.cxx
0 → 100644
View file @
70879356
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "HttpdClient.hxx"
#include "HttpdInternal.hxx"
#include "util/fifo_buffer.h"
#include "page.h"
#include "icy_server.h"
#include "glib_socket.h"
#include <assert.h>
#include <string.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "httpd_output"
HttpdClient
::~
HttpdClient
()
{
if
(
state
==
RESPONSE
)
{
if
(
write_source_id
!=
0
)
g_source_remove
(
write_source_id
);
if
(
current_page
!=
nullptr
)
page_unref
(
current_page
);
for
(
auto
page
:
pages
)
page_unref
(
page
);
}
else
fifo_buffer_free
(
input
);
if
(
metadata
)
page_unref
(
metadata
);
g_source_remove
(
read_source_id
);
g_io_channel_unref
(
channel
);
}
void
HttpdClient
::
Close
()
{
httpd_output_remove_client
(
httpd
,
this
);
}
void
HttpdClient
::
LockClose
()
{
const
ScopeLock
protect
(
httpd
->
mutex
);
Close
();
}
void
HttpdClient
::
BeginResponse
()
{
assert
(
state
!=
RESPONSE
);
state
=
RESPONSE
;
write_source_id
=
0
;
current_page
=
nullptr
;
httpd_output_send_header
(
httpd
,
this
);
}
/**
* Handle a line of the HTTP request.
*/
bool
HttpdClient
::
HandleLine
(
const
char
*
line
)
{
assert
(
state
!=
RESPONSE
);
if
(
state
==
REQUEST
)
{
if
(
strncmp
(
line
,
"GET /"
,
5
)
!=
0
)
{
/* only GET is supported */
g_warning
(
"malformed request line from client"
);
return
false
;
}
line
=
strchr
(
line
+
5
,
' '
);
if
(
line
==
nullptr
||
strncmp
(
line
+
1
,
"HTTP/"
,
5
)
!=
0
)
{
/* HTTP/0.9 without request headers */
BeginResponse
();
return
true
;
}
/* after the request line, request headers follow */
state
=
HEADERS
;
return
true
;
}
else
{
if
(
*
line
==
0
)
{
/* empty line: request is finished */
BeginResponse
();
return
true
;
}
if
(
g_ascii_strncasecmp
(
line
,
"Icy-MetaData: 1"
,
15
)
==
0
)
{
/* Send icy metadata */
metadata_requested
=
metadata_supported
;
return
true
;
}
if
(
g_ascii_strncasecmp
(
line
,
"transferMode.dlna.org: Streaming"
,
32
)
==
0
)
{
/* Send as dlna */
dlna_streaming_requested
=
true
;
/* metadata is not supported by dlna streaming, so disable it */
metadata_supported
=
false
;
metadata_requested
=
false
;
return
true
;
}
/* expect more request headers */
return
true
;
}
}
char
*
HttpdClient
::
ReadLine
()
{
assert
(
state
!=
RESPONSE
);
const
ScopeLock
protect
(
httpd
->
mutex
);
size_t
length
;
const
char
*
p
=
(
const
char
*
)
fifo_buffer_read
(
input
,
&
length
);
if
(
p
==
nullptr
)
/* empty input buffer */
return
nullptr
;
const
char
*
newline
=
(
const
char
*
)
memchr
(
p
,
'\n'
,
length
);
if
(
newline
==
nullptr
)
/* incomplete line */
return
nullptr
;
char
*
line
=
g_strndup
(
p
,
newline
-
p
);
fifo_buffer_consume
(
input
,
newline
-
p
+
1
);
/* remove trailing whitespace (e.g. '\r') */
return
g_strchomp
(
line
);
}
/**
* Sends the status line and response headers to the client.
*/
bool
HttpdClient
::
SendResponse
()
{
char
buffer
[
1024
];
GError
*
error
=
nullptr
;
GIOStatus
status
;
gsize
bytes_written
;
assert
(
state
==
RESPONSE
);
if
(
dlna_streaming_requested
)
{
g_snprintf
(
buffer
,
sizeof
(
buffer
),
"HTTP/1.1 206 OK
\r\n
"
"Content-Type: %s
\r\n
"
"Content-Length: 10000
\r\n
"
"Content-RangeX: 0-1000000/1000000
\r\n
"
"transferMode.dlna.org: Streaming
\r\n
"
"Accept-Ranges: bytes
\r\n
"
"Connection: close
\r\n
"
"realTimeInfo.dlna.org: DLNA.ORG_TLAG=*
\r\n
"
"contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0
\r\n
"
"
\r\n
"
,
httpd
->
content_type
);
}
else
if
(
metadata_requested
)
{
gchar
*
metadata_header
;
metadata_header
=
icy_server_metadata_header
(
httpd
->
name
,
httpd
->
genre
,
httpd
->
website
,
httpd
->
content_type
,
metaint
);
g_strlcpy
(
buffer
,
metadata_header
,
sizeof
(
buffer
));
g_free
(
metadata_header
);
}
else
{
/* revert to a normal HTTP request */
g_snprintf
(
buffer
,
sizeof
(
buffer
),
"HTTP/1.1 200 OK
\r\n
"
"Content-Type: %s
\r\n
"
"Connection: close
\r\n
"
"Pragma: no-cache
\r\n
"
"Cache-Control: no-cache, no-store
\r\n
"
"
\r\n
"
,
httpd
->
content_type
);
}
status
=
g_io_channel_write_chars
(
channel
,
buffer
,
strlen
(
buffer
),
&
bytes_written
,
&
error
);
switch
(
status
)
{
case
G_IO_STATUS_NORMAL
:
case
G_IO_STATUS_AGAIN
:
return
true
;
case
G_IO_STATUS_EOF
:
/* client has disconnected */
Close
();
return
false
;
case
G_IO_STATUS_ERROR
:
/* I/O error */
g_warning
(
"failed to write to client: %s"
,
error
->
message
);
g_error_free
(
error
);
Close
();
return
false
;
}
/* unreachable */
Close
();
return
false
;
}
bool
HttpdClient
::
Received
()
{
assert
(
state
!=
RESPONSE
);
char
*
line
;
bool
success
;
while
((
line
=
ReadLine
())
!=
nullptr
)
{
success
=
HandleLine
(
line
);
g_free
(
line
);
if
(
!
success
)
{
assert
(
state
!=
RESPONSE
);
return
false
;
}
if
(
state
==
RESPONSE
)
{
if
(
!
fifo_buffer_is_empty
(
input
))
{
g_warning
(
"unexpected input from client"
);
return
false
;
}
fifo_buffer_free
(
input
);
return
SendResponse
();
}
}
return
true
;
}
bool
HttpdClient
::
Read
()
{
size_t
max_length
;
GError
*
error
=
nullptr
;
GIOStatus
status
;
gsize
bytes_read
;
if
(
state
==
RESPONSE
)
{
/* the client has already sent the request, and he
must not send more */
char
buffer
[
1
];
status
=
g_io_channel_read_chars
(
channel
,
buffer
,
sizeof
(
buffer
),
&
bytes_read
,
nullptr
);
if
(
status
==
G_IO_STATUS_NORMAL
)
g_warning
(
"unexpected input from client"
);
return
false
;
}
char
*
p
=
(
char
*
)
fifo_buffer_write
(
input
,
&
max_length
);
if
(
p
==
nullptr
)
{
g_warning
(
"buffer overflow"
);
return
false
;
}
status
=
g_io_channel_read_chars
(
channel
,
p
,
max_length
,
&
bytes_read
,
&
error
);
switch
(
status
)
{
case
G_IO_STATUS_NORMAL
:
fifo_buffer_append
(
input
,
bytes_read
);
return
Received
();
case
G_IO_STATUS_AGAIN
:
/* try again later, after select() */
return
true
;
case
G_IO_STATUS_EOF
:
/* peer disconnected */
return
false
;
case
G_IO_STATUS_ERROR
:
/* I/O error */
g_warning
(
"failed to read from client: %s"
,
error
->
message
);
g_error_free
(
error
);
return
false
;
}
/* unreachable */
return
false
;
}
static
gboolean
httpd_client_in_event
(
G_GNUC_UNUSED
GIOChannel
*
source
,
GIOCondition
condition
,
gpointer
data
)
{
HttpdClient
*
client
=
(
HttpdClient
*
)
data
;
if
(
condition
==
G_IO_IN
&&
client
->
Read
())
{
return
true
;
}
else
{
client
->
LockClose
();
return
false
;
}
}
HttpdClient
::
HttpdClient
(
httpd_output
*
_httpd
,
int
_fd
,
bool
_metadata_supported
)
:
httpd
(
_httpd
),
channel
(
g_io_channel_new_socket
(
_fd
)),
input
(
fifo_buffer_new
(
4096
)),
state
(
REQUEST
),
dlna_streaming_requested
(
false
),
metadata_supported
(
_metadata_supported
),
metadata_requested
(
false
),
metadata_sent
(
true
),
metaint
(
8192
),
/*TODO: just a std value */
metadata
(
nullptr
),
metadata_current_position
(
0
),
metadata_fill
(
0
)
{
/* GLib is responsible for closing the file descriptor */
g_io_channel_set_close_on_unref
(
channel
,
true
);
/* NULL encoding means the stream is binary safe */
g_io_channel_set_encoding
(
channel
,
nullptr
,
nullptr
);
/* we prefer to do buffering */
g_io_channel_set_buffered
(
channel
,
false
);
read_source_id
=
g_io_add_watch
(
channel
,
GIOCondition
(
G_IO_IN
|
G_IO_ERR
|
G_IO_HUP
),
httpd_client_in_event
,
this
);
}
size_t
HttpdClient
::
GetQueueSize
()
const
{
if
(
state
!=
RESPONSE
)
return
0
;
size_t
size
=
0
;
for
(
auto
page
:
pages
)
size
+=
page
->
size
;
return
size
;
}
void
HttpdClient
::
CancelQueue
()
{
if
(
state
!=
RESPONSE
)
return
;
for
(
auto
page
:
pages
)
page_unref
(
page
);
pages
.
clear
();
if
(
write_source_id
!=
0
&&
current_page
==
nullptr
)
{
g_source_remove
(
write_source_id
);
write_source_id
=
0
;
}
}
static
GIOStatus
write_page_to_channel
(
GIOChannel
*
channel
,
const
struct
page
*
page
,
size_t
position
,
gsize
*
bytes_written_r
,
GError
**
error
)
{
assert
(
channel
!=
nullptr
);
assert
(
page
!=
nullptr
);
assert
(
position
<
page
->
size
);
return
g_io_channel_write_chars
(
channel
,
(
const
gchar
*
)
page
->
data
+
position
,
page
->
size
-
position
,
bytes_written_r
,
error
);
}
static
GIOStatus
write_n_bytes_to_channel
(
GIOChannel
*
channel
,
const
struct
page
*
page
,
size_t
position
,
gint
n
,
gsize
*
bytes_written_r
,
GError
**
error
)
{
GIOStatus
status
;
assert
(
channel
!=
nullptr
);
assert
(
page
!=
nullptr
);
assert
(
position
<
page
->
size
);
if
(
n
==
-
1
)
{
status
=
write_page_to_channel
(
channel
,
page
,
position
,
bytes_written_r
,
error
);
}
else
{
status
=
g_io_channel_write_chars
(
channel
,
(
const
gchar
*
)
page
->
data
+
position
,
n
,
bytes_written_r
,
error
);
}
return
status
;
}
int
HttpdClient
::
GetBytesTillMetaData
()
const
{
if
(
metadata_requested
&&
current_page
->
size
-
current_position
>
metaint
-
metadata_fill
)
return
metaint
-
metadata_fill
;
return
-
1
;
}
inline
bool
HttpdClient
::
Write
()
{
GError
*
error
=
nullptr
;
GIOStatus
status
;
gsize
bytes_written
;
const
ScopeLock
protect
(
httpd
->
mutex
);
assert
(
state
==
RESPONSE
);
if
(
write_source_id
==
0
)
/* another thread has removed the event source while
this thread was waiting for httpd->mutex */
return
false
;
if
(
current_page
==
nullptr
)
{
current_page
=
pages
.
front
();
pages
.
pop_front
();
current_position
=
0
;
}
const
gint
bytes_to_write
=
GetBytesTillMetaData
();
if
(
bytes_to_write
==
0
)
{
gint
metadata_to_write
;
metadata_to_write
=
metadata_current_position
;
if
(
!
metadata_sent
)
{
status
=
write_page_to_channel
(
channel
,
metadata
,
metadata_to_write
,
&
bytes_written
,
&
error
);
metadata_current_position
+=
bytes_written
;
if
(
metadata
->
size
-
metadata_current_position
==
0
)
{
metadata_fill
=
0
;
metadata_current_position
=
0
;
metadata_sent
=
true
;
}
}
else
{
struct
page
*
empty_meta
;
guchar
empty_data
=
0
;
empty_meta
=
page_new_copy
(
&
empty_data
,
1
);
status
=
write_page_to_channel
(
channel
,
empty_meta
,
metadata_to_write
,
&
bytes_written
,
&
error
);
metadata_current_position
+=
bytes_written
;
if
(
empty_meta
->
size
-
metadata_current_position
==
0
)
{
metadata_fill
=
0
;
metadata_current_position
=
0
;
}
}
bytes_written
=
0
;
}
else
{
status
=
write_n_bytes_to_channel
(
channel
,
current_page
,
current_position
,
bytes_to_write
,
&
bytes_written
,
&
error
);
}
switch
(
status
)
{
case
G_IO_STATUS_NORMAL
:
current_position
+=
bytes_written
;
assert
(
current_position
<=
current_page
->
size
);
if
(
metadata_requested
)
metadata_fill
+=
bytes_written
;
if
(
current_position
>=
current_page
->
size
)
{
page_unref
(
current_page
);
current_page
=
nullptr
;
if
(
pages
.
empty
())
{
/* all pages are sent: remove the
event source */
write_source_id
=
0
;
return
false
;
}
}
return
true
;
case
G_IO_STATUS_AGAIN
:
return
true
;
case
G_IO_STATUS_EOF
:
/* client has disconnected */
Close
();
return
false
;
case
G_IO_STATUS_ERROR
:
/* I/O error */
g_warning
(
"failed to write to client: %s"
,
error
->
message
);
g_error_free
(
error
);
Close
();
return
false
;
}
/* unreachable */
Close
();
return
false
;
}
static
gboolean
httpd_client_out_event
(
gcc_unused
GIOChannel
*
source
,
gcc_unused
GIOCondition
condition
,
gpointer
data
)
{
assert
(
condition
==
G_IO_OUT
);
HttpdClient
*
client
=
(
HttpdClient
*
)
data
;
return
client
->
Write
();
}
void
HttpdClient
::
PushPage
(
struct
page
*
page
)
{
if
(
state
!=
RESPONSE
)
/* the client is still writing the HTTP request */
return
;
page_ref
(
page
);
pages
.
push_back
(
page
);
if
(
write_source_id
==
0
)
write_source_id
=
g_io_add_watch
(
channel
,
G_IO_OUT
,
httpd_client_out_event
,
this
);
}
void
HttpdClient
::
PushMetaData
(
struct
page
*
page
)
{
if
(
metadata
)
{
page_unref
(
metadata
);
metadata
=
nullptr
;
}
g_return_if_fail
(
page
);
page_ref
(
page
);
metadata
=
page
;
metadata_sent
=
false
;
}
src/output/HttpdClient.hxx
0 → 100644
View file @
70879356
/*
* Copyright (C) 2003-2013 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_OUTPUT_HTTPD_CLIENT_HXX
#define MPD_OUTPUT_HTTPD_CLIENT_HXX
#include "gcc.h"
#include <glib.h>
#include <list>
#include <stddef.h>
struct
httpd_output
;
struct
page
;
class
HttpdClient
final
{
/**
* The httpd output object this client is connected to.
*/
httpd_output
*
const
httpd
;
/**
* The TCP socket.
*/
GIOChannel
*
channel
;
/**
* The GLib main loop source id for reading from the socket,
* and to detect errors.
*/
guint
read_source_id
;
/**
* The GLib main loop source id for writing to the socket. If
* 0, then there is no event source currently (because there
* are no queued pages).
*/
guint
write_source_id
;
/**
* For buffered reading. This pointer is only valid while the
* HTTP request is read.
*/
struct
fifo_buffer
*
input
;
/**
* The current state of the client.
*/
enum
{
/** reading the request line */
REQUEST
,
/** reading the request headers */
HEADERS
,
/** sending the HTTP response */
RESPONSE
,
}
state
;
/**
* A queue of #page objects to be sent to the client.
*/
std
::
list
<
page
*>
pages
;
/**
* The #page which is currently being sent to the client.
*/
page
*
current_page
;
/**
* The amount of bytes which were already sent from
* #current_page.
*/
size_t
current_position
;
/**
* If DLNA streaming was an option.
*/
bool
dlna_streaming_requested
;
/* ICY */
/**
* Do we support sending Icy-Metadata to the client? This is
* disabled if the httpd audio output uses encoder tags.
*/
bool
metadata_supported
;
/**
* If we should sent icy metadata.
*/
bool
metadata_requested
;
/**
* If the current metadata was already sent to the client.
*/
bool
metadata_sent
;
/**
* The amount of streaming data between each metadata block
*/
guint
metaint
;
/**
* The metadata as #page which is currently being sent to the client.
*/
page
*
metadata
;
/*
* The amount of bytes which were already sent from the metadata.
*/
size_t
metadata_current_position
;
/**
* The amount of streaming data sent to the client
* since the last icy information was sent.
*/
guint
metadata_fill
;
public
:
/**
* @param httpd the HTTP output device
* @param fd the socket file descriptor
*/
HttpdClient
(
httpd_output
*
httpd
,
int
_fd
,
bool
_metadata_supported
);
/**
* Note: this does not remove the client from the
* #httpd_output object.
*/
~
HttpdClient
();
/**
* Frees the client and removes it from the server's client list.
*/
void
Close
();
void
LockClose
();
/**
* Returns the total size of this client's page queue.
*/
gcc_pure
size_t
GetQueueSize
()
const
;
/**
* Clears the page queue.
*/
void
CancelQueue
();
bool
Read
();
/**
* Data has been received from the client and it is appended
* to the input buffer.
*/
bool
Received
();
/**
* Check if a complete line of input is present in the input
* buffer, and duplicates it. It is removed from the input
* buffer. The return value has to be freed with g_free().
*/
char
*
ReadLine
();
/**
* Handle a line of the HTTP request.
*/
bool
HandleLine
(
const
char
*
line
);
/**
* Switch the client to the "RESPONSE" state.
*/
void
BeginResponse
();
/**
* Sends the status line and response headers to the client.
*/
bool
SendResponse
();
gcc_pure
int
GetBytesTillMetaData
()
const
;
bool
Write
();
/**
* Appends a page to the client's queue.
*/
void
PushPage
(
page
*
page
);
/**
* Sends the passed metadata.
*/
void
PushMetaData
(
page
*
page
);
};
#endif
src/output/
httpd_internal.h
→
src/output/
HttpdInternal.hxx
View file @
70879356
...
...
@@ -25,14 +25,18 @@
#ifndef MPD_OUTPUT_HTTPD_INTERNAL_H
#define MPD_OUTPUT_HTTPD_INTERNAL_H
#include "HttpdClient.hxx"
#include "output_internal.h"
#include "timer.h"
#include "thread/Mutex.hxx"
#include <glib.h>
#include <forward_list>
#include <stdbool.h>
struct
httpd_c
lient
;
class
HttpdC
lient
;
struct
httpd_output
{
struct
audio_output
base
;
...
...
@@ -65,7 +69,7 @@ struct httpd_output {
* This mutex protects the listener socket and the client
* list.
*/
GMutex
*
mutex
;
mutable
Mutex
mutex
;
/**
* A #timer object to synchronize this output with the
...
...
@@ -105,7 +109,7 @@ struct httpd_output {
* A linked list containing all clients which are currently
* connected.
*/
GList
*
clients
;
std
::
forward_list
<
HttpdClient
>
clients
;
/**
* A temporary buffer for the httpd_output_read_page()
...
...
@@ -125,7 +129,7 @@ struct httpd_output {
*/
void
httpd_output_remove_client
(
struct
httpd_output
*
httpd
,
struct
httpd_c
lient
*
client
);
HttpdC
lient
*
client
);
/**
* Sends the encoder header to the client. This is called right after
...
...
@@ -133,6 +137,6 @@ httpd_output_remove_client(struct httpd_output *httpd,
*/
void
httpd_output_send_header
(
struct
httpd_output
*
httpd
,
struct
httpd_c
lient
*
client
);
HttpdC
lient
*
client
);
#endif
src/output/
httpd_output_plugin.c
→
src/output/
HttpdOutputPlugin.cxx
View file @
70879356
/*
* Copyright (C) 2003-201
1
The Music Player Daemon Project
* Copyright (C) 2003-201
3
The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
...
...
@@ -18,9 +18,9 @@
*/
#include "config.h"
#include "
httpd_output_plugin.h
"
#include "
httpd_internal.h
"
#include "
httpd_client.h
"
#include "
HttpdOutputPlugin.hxx
"
#include "
HttpdInternal.hxx
"
#include "
HttpdClient.hxx
"
#include "output_api.h"
#include "encoder_plugin.h"
#include "encoder_list.h"
...
...
@@ -60,9 +60,9 @@ httpd_output_quark(void)
*/
G_GNUC_PURE
static
bool
httpd_output_has_clients
(
const
struct
httpd_output
*
httpd
)
httpd_output_has_clients
(
const
httpd_output
*
httpd
)
{
return
httpd
->
clients
!=
NULL
;
return
!
httpd
->
clients
.
empty
()
;
}
/**
...
...
@@ -70,12 +70,10 @@ httpd_output_has_clients(const struct httpd_output *httpd)
*/
G_GNUC_PURE
static
bool
httpd_output_lock_has_clients
(
const
struct
httpd_output
*
httpd
)
httpd_output_lock_has_clients
(
const
httpd_output
*
httpd
)
{
g_mutex_lock
(
httpd
->
mutex
);
bool
result
=
httpd_output_has_clients
(
httpd
);
g_mutex_unlock
(
httpd
->
mutex
);
return
result
;
const
ScopeLock
protect
(
httpd
->
mutex
);
return
httpd_output_has_clients
(
httpd
);
}
static
void
...
...
@@ -83,32 +81,28 @@ httpd_listen_in_event(int fd, const struct sockaddr *address,
size_t
address_length
,
int
uid
,
void
*
ctx
);
static
bool
httpd_output_bind
(
struct
httpd_output
*
httpd
,
GError
**
error_r
)
httpd_output_bind
(
httpd_output
*
httpd
,
GError
**
error_r
)
{
httpd
->
open
=
false
;
g_mutex_lock
(
httpd
->
mutex
);
bool
success
=
server_socket_open
(
httpd
->
server_socket
,
error_r
);
g_mutex_unlock
(
httpd
->
mutex
);
return
success
;
const
ScopeLock
protect
(
httpd
->
mutex
);
return
server_socket_open
(
httpd
->
server_socket
,
error_r
);
}
static
void
httpd_output_unbind
(
struct
httpd_output
*
httpd
)
httpd_output_unbind
(
httpd_output
*
httpd
)
{
assert
(
!
httpd
->
open
);
g_mutex_lock
(
httpd
->
mutex
);
const
ScopeLock
protect
(
httpd
->
mutex
);
server_socket_close
(
httpd
->
server_socket
);
g_mutex_unlock
(
httpd
->
mutex
);
}
static
struct
audio_output
*
httpd_output_init
(
const
struct
config_param
*
param
,
GError
**
error
)
{
struct
httpd_output
*
httpd
=
g_new
(
struct
httpd_output
,
1
);
httpd_output
*
httpd
=
new
httpd_output
(
);
if
(
!
ao_base_init
(
&
httpd
->
base
,
&
httpd_output_plugin
,
param
,
error
))
{
g_free
(
httpd
);
return
NULL
;
...
...
@@ -174,50 +168,44 @@ httpd_output_init(const struct config_param *param,
httpd
->
content_type
=
"application/octet-stream"
;
}
httpd
->
mutex
=
g_mutex_new
();
return
&
httpd
->
base
;
}
static
void
httpd_output_finish
(
struct
audio_output
*
ao
)
{
struct
httpd_output
*
httpd
=
(
struct
httpd_output
*
)
ao
;
httpd_output
*
httpd
=
(
httpd_output
*
)
ao
;
if
(
httpd
->
metadata
)
page_unref
(
httpd
->
metadata
);
encoder_finish
(
httpd
->
encoder
);
server_socket_free
(
httpd
->
server_socket
);
g_mutex_free
(
httpd
->
mutex
);
ao_base_finish
(
&
httpd
->
base
);
g_free
(
httpd
)
;
delete
httpd
;
}
/**
* Creates a new #
httpd_c
lient object and adds it into the
* Creates a new #
HttpdC
lient object and adds it into the
* httpd_output.clients linked list.
*/
static
void
httpd_client_add
(
struct
httpd_output
*
httpd
,
int
fd
)
httpd_client_add
(
httpd_output
*
httpd
,
int
fd
)
{
struct
httpd_client
*
client
=
httpd_client_new
(
httpd
,
fd
,
httpd
->
encoder
->
plugin
->
tag
==
NULL
);
httpd
->
clients
=
g_list_prepend
(
httpd
->
clients
,
client
);
httpd
->
clients
.
emplace_front
(
httpd
,
fd
,
httpd
->
encoder
->
plugin
->
tag
==
NULL
);
httpd
->
clients_cnt
++
;
/* pass metadata to client */
if
(
httpd
->
metadata
)
httpd
_client_send_metadata
(
client
,
httpd
->
metadata
);
httpd
->
clients
.
front
().
PushMetaData
(
httpd
->
metadata
);
}
static
void
httpd_listen_in_event
(
int
fd
,
const
struct
sockaddr
*
address
,
size_t
address_length
,
G_GNUC_UNUSED
int
uid
,
void
*
ctx
)
{
struct
httpd_output
*
httpd
=
ctx
;
httpd_output
*
httpd
=
(
httpd_output
*
)
ctx
;
/* the listener socket has become readable - a client has
connected */
...
...
@@ -238,7 +226,6 @@ httpd_listen_in_event(int fd, const struct sockaddr *address,
progname
,
hostaddr
);
g_free
(
hostaddr
);
close_socket
(
fd
);
g_mutex_unlock
(
httpd
->
mutex
);
return
;
}
...
...
@@ -249,7 +236,7 @@ httpd_listen_in_event(int fd, const struct sockaddr *address,
(
void
)
address_length
;
#endif
/* HAVE_WRAP */
g_mutex_lock
(
httpd
->
mutex
);
const
ScopeLock
protect
(
httpd
->
mutex
);
if
(
fd
>=
0
)
{
/* can we allow additional client */
...
...
@@ -262,8 +249,6 @@ httpd_listen_in_event(int fd, const struct sockaddr *address,
}
else
if
(
fd
<
0
&&
errno
!=
EINTR
)
{
g_warning
(
"accept() failed: %s"
,
g_strerror
(
errno
));
}
g_mutex_unlock
(
httpd
->
mutex
);
}
/**
...
...
@@ -271,7 +256,7 @@ httpd_listen_in_event(int fd, const struct sockaddr *address,
* as a new #page object.
*/
static
struct
page
*
httpd_output_read_page
(
struct
httpd_output
*
httpd
)
httpd_output_read_page
(
httpd_output
*
httpd
)
{
if
(
httpd
->
unflushed_input
>=
65536
)
{
/* we have fed a lot of input into the encoder, but it
...
...
@@ -301,7 +286,7 @@ httpd_output_read_page(struct httpd_output *httpd)
}
static
bool
httpd_output_encoder_open
(
struct
httpd_output
*
httpd
,
httpd_output_encoder_open
(
httpd_output
*
httpd
,
struct
audio_format
*
audio_format
,
GError
**
error
)
{
...
...
@@ -321,7 +306,7 @@ httpd_output_encoder_open(struct httpd_output *httpd,
static
bool
httpd_output_enable
(
struct
audio_output
*
ao
,
GError
**
error_r
)
{
struct
httpd_output
*
httpd
=
(
struct
httpd_output
*
)
ao
;
httpd_output
*
httpd
=
(
httpd_output
*
)
ao
;
return
httpd_output_bind
(
httpd
,
error_r
);
}
...
...
@@ -329,7 +314,7 @@ httpd_output_enable(struct audio_output *ao, GError **error_r)
static
void
httpd_output_disable
(
struct
audio_output
*
ao
)
{
struct
httpd_output
*
httpd
=
(
struct
httpd_output
*
)
ao
;
httpd_output
*
httpd
=
(
httpd_output
*
)
ao
;
httpd_output_unbind
(
httpd
);
}
...
...
@@ -338,82 +323,75 @@ static bool
httpd_output_open
(
struct
audio_output
*
ao
,
struct
audio_format
*
audio_format
,
GError
**
error
)
{
struct
httpd_output
*
httpd
=
(
struct
httpd_output
*
)
ao
;
httpd_output
*
httpd
=
(
httpd_output
*
)
ao
;
assert
(
httpd
->
clients
.
empty
());
g_mutex_lock
(
httpd
->
mutex
);
const
ScopeLock
protect
(
httpd
->
mutex
);
/* open the encoder */
if
(
!
httpd_output_encoder_open
(
httpd
,
audio_format
,
error
))
{
g_mutex_unlock
(
httpd
->
mutex
);
if
(
!
httpd_output_encoder_open
(
httpd
,
audio_format
,
error
))
return
false
;
}
/* initialize other attributes */
httpd
->
clients
=
NULL
;
httpd
->
clients_cnt
=
0
;
httpd
->
timer
=
timer_new
(
audio_format
);
httpd
->
open
=
true
;
g_mutex_unlock
(
httpd
->
mutex
);
return
true
;
}
static
void
httpd_client_delete
(
gpointer
data
,
G_GNUC_UNUSED
gpointer
user_data
)
{
struct
httpd_client
*
client
=
data
;
httpd_client_free
(
client
);
}
static
void
httpd_output_close
(
struct
audio_output
*
ao
)
{
struct
httpd_output
*
httpd
=
(
struct
httpd_output
*
)
ao
;
httpd_output
*
httpd
=
(
httpd_output
*
)
ao
;
g_mutex_lock
(
httpd
->
mutex
);
const
ScopeLock
protect
(
httpd
->
mutex
);
httpd
->
open
=
false
;
timer_free
(
httpd
->
timer
);
g_list_foreach
(
httpd
->
clients
,
httpd_client_delete
,
NULL
);
g_list_free
(
httpd
->
clients
);
httpd
->
clients
.
clear
();
if
(
httpd
->
header
!=
NULL
)
page_unref
(
httpd
->
header
);
encoder_close
(
httpd
->
encoder
);
g_mutex_unlock
(
httpd
->
mutex
);
}
void
httpd_output_remove_client
(
struct
httpd_output
*
httpd
,
struct
httpd_client
*
client
)
httpd_output_remove_client
(
httpd_output
*
httpd
,
HttpdClient
*
client
)
{
assert
(
httpd
!=
NULL
);
assert
(
httpd
->
clients_cnt
>
0
);
assert
(
client
!=
NULL
);
httpd
->
clients
=
g_list_remove
(
httpd
->
clients
,
client
);
httpd
->
clients_cnt
--
;
for
(
auto
prev
=
httpd
->
clients
.
before_begin
(),
i
=
std
::
next
(
prev
);;
prev
=
i
,
i
=
std
::
next
(
prev
))
{
assert
(
i
!=
httpd
->
clients
.
end
());
if
(
&*
i
==
client
)
{
httpd
->
clients
.
erase_after
(
prev
);
httpd
->
clients_cnt
--
;
break
;
}
}
}
void
httpd_output_send_header
(
struct
httpd_output
*
httpd
,
struct
httpd_client
*
client
)
httpd_output_send_header
(
httpd_output
*
httpd
,
HttpdClient
*
client
)
{
if
(
httpd
->
header
!=
NULL
)
httpd_client_send
(
client
,
httpd
->
header
);
client
->
PushPage
(
httpd
->
header
);
}
static
unsigned
httpd_output_delay
(
struct
audio_output
*
ao
)
{
struct
httpd_output
*
httpd
=
(
struct
httpd_output
*
)
ao
;
httpd_output
*
httpd
=
(
httpd_output
*
)
ao
;
if
(
!
httpd_output_lock_has_clients
(
httpd
)
&&
httpd
->
base
.
pause
)
{
/* if there's no client and this output is paused,
...
...
@@ -433,51 +411,35 @@ httpd_output_delay(struct audio_output *ao)
:
0
;
}
static
void
httpd_client_check_queue
(
gpointer
data
,
G_GNUC_UNUSED
gpointer
user_data
)
{
struct
httpd_client
*
client
=
data
;
if
(
httpd_client_queue_size
(
client
)
>
256
*
1024
)
{
g_debug
(
"client is too slow, flushing its queue"
);
httpd_client_cancel
(
client
);
}
}
static
void
httpd_client_send_page
(
gpointer
data
,
gpointer
user_data
)
{
struct
httpd_client
*
client
=
data
;
struct
page
*
page
=
user_data
;
httpd_client_send
(
client
,
page
);
}
/**
* Broadcasts a page struct to all clients.
*/
static
void
httpd_output_broadcast_page
(
struct
httpd_output
*
httpd
,
struct
page
*
page
)
httpd_output_broadcast_page
(
httpd_output
*
httpd
,
struct
page
*
page
)
{
assert
(
page
!=
NULL
);
g_mutex_lock
(
httpd
->
mutex
);
g_list_foreach
(
httpd
->
clients
,
httpd_client_send_page
,
page
);
g_mutex_unlock
(
httpd
->
mutex
);
const
ScopeLock
protect
(
httpd
->
mutex
);
for
(
auto
&
client
:
httpd
->
clients
)
client
.
PushPage
(
page
);
}
/**
* Broadcasts data from the encoder to all clients.
*/
static
void
httpd_output_encoder_to_clients
(
struct
httpd_output
*
httpd
)
httpd_output_encoder_to_clients
(
httpd_output
*
httpd
)
{
struct
page
*
page
;
g_mutex_lock
(
httpd
->
mutex
);
g_list_foreach
(
httpd
->
clients
,
httpd_client_check_queue
,
NULL
);
g_mutex_unlock
(
httpd
->
mutex
);
httpd
->
mutex
.
lock
();
for
(
auto
&
client
:
httpd
->
clients
)
{
if
(
client
.
GetQueueSize
()
>
256
*
1024
)
{
g_debug
(
"client is too slow, flushing its queue"
);
client
.
CancelQueue
();
}
}
httpd
->
mutex
.
unlock
();
struct
page
*
page
;
while
((
page
=
httpd_output_read_page
(
httpd
))
!=
NULL
)
{
httpd_output_broadcast_page
(
httpd
,
page
);
page_unref
(
page
);
...
...
@@ -485,7 +447,7 @@ httpd_output_encoder_to_clients(struct httpd_output *httpd)
}
static
bool
httpd_output_encode_and_play
(
struct
httpd_output
*
httpd
,
httpd_output_encode_and_play
(
httpd_output
*
httpd
,
const
void
*
chunk
,
size_t
size
,
GError
**
error
)
{
if
(
!
encoder_write
(
httpd
->
encoder
,
chunk
,
size
,
error
))
...
...
@@ -502,7 +464,7 @@ static size_t
httpd_output_play
(
struct
audio_output
*
ao
,
const
void
*
chunk
,
size_t
size
,
GError
**
error_r
)
{
struct
httpd_output
*
httpd
=
(
struct
httpd_output
*
)
ao
;
httpd_output
*
httpd
=
(
httpd_output
*
)
ao
;
if
(
httpd_output_lock_has_clients
(
httpd
))
{
if
(
!
httpd_output_encode_and_play
(
httpd
,
chunk
,
size
,
error_r
))
...
...
@@ -519,10 +481,10 @@ httpd_output_play(struct audio_output *ao, const void *chunk, size_t size,
static
bool
httpd_output_pause
(
struct
audio_output
*
ao
)
{
struct
httpd_output
*
httpd
=
(
struct
httpd_output
*
)
ao
;
httpd_output
*
httpd
=
(
httpd_output
*
)
ao
;
if
(
httpd_output_lock_has_clients
(
httpd
))
{
static
const
char
silence
[
1020
];
static
const
char
silence
[
1020
]
=
{
0
}
;
return
httpd_output_play
(
ao
,
silence
,
sizeof
(
silence
),
NULL
)
>
0
;
}
else
{
...
...
@@ -531,18 +493,9 @@ httpd_output_pause(struct audio_output *ao)
}
static
void
httpd_send_metadata
(
gpointer
data
,
gpointer
user_data
)
{
struct
httpd_client
*
client
=
data
;
struct
page
*
icy_metadata
=
user_data
;
httpd_client_send_metadata
(
client
,
icy_metadata
);
}
static
void
httpd_output_tag
(
struct
audio_output
*
ao
,
const
struct
tag
*
tag
)
{
struct
httpd_output
*
httpd
=
(
struct
httpd_output
*
)
ao
;
httpd_output
*
httpd
=
(
httpd_output
*
)
ao
;
assert
(
tag
!=
NULL
);
...
...
@@ -581,43 +534,37 @@ httpd_output_tag(struct audio_output *ao, const struct tag *tag)
TAG_ARTIST
,
TAG_TITLE
,
TAG_NUM_OF_ITEM_TYPES
);
if
(
httpd
->
metadata
!=
NULL
)
{
g_mutex_lock
(
httpd
->
mutex
);
g_list_foreach
(
httpd
->
clients
,
httpd_send_metadata
,
httpd
->
metadata
);
g_mutex_unlock
(
httpd
->
mutex
);
const
ScopeLock
protect
(
httpd
->
mutex
);
for
(
auto
&
client
:
httpd
->
clients
)
client
.
PushMetaData
(
httpd
->
metadata
);
}
}
}
static
void
httpd_client_cancel_callback
(
gpointer
data
,
G_GNUC_UNUSED
gpointer
user_data
)
{
struct
httpd_client
*
client
=
data
;
httpd_client_cancel
(
client
);
}
static
void
httpd_output_cancel
(
struct
audio_output
*
ao
)
{
struct
httpd_output
*
httpd
=
(
struct
httpd_output
*
)
ao
;
httpd_output
*
httpd
=
(
httpd_output
*
)
ao
;
g_mutex_lock
(
httpd
->
mutex
);
g_list_foreach
(
httpd
->
clients
,
httpd_client_cancel_callback
,
NULL
);
g_mutex_unlock
(
httpd
->
mutex
);
const
ScopeLock
protect
(
httpd
->
mutex
);
for
(
auto
&
client
:
httpd
->
clients
)
client
.
CancelQueue
(
);
}
const
struct
audio_output_plugin
httpd_output_plugin
=
{
.
name
=
"httpd"
,
.
init
=
httpd_output_init
,
.
finish
=
httpd_output_finish
,
.
enable
=
httpd_output_enable
,
.
disable
=
httpd_output_disable
,
.
open
=
httpd_output_open
,
.
close
=
httpd_output_close
,
.
delay
=
httpd_output_delay
,
.
send_tag
=
httpd_output_tag
,
.
play
=
httpd_output_play
,
.
pause
=
httpd_output_pause
,
.
cancel
=
httpd_output_cancel
,
"httpd"
,
nullptr
,
httpd_output_init
,
httpd_output_finish
,
httpd_output_enable
,
httpd_output_disable
,
httpd_output_open
,
httpd_output_close
,
httpd_output_delay
,
httpd_output_tag
,
httpd_output_play
,
nullptr
,
httpd_output_cancel
,
httpd_output_pause
,
nullptr
,
};
src/output/
httpd_output_plugin.h
→
src/output/
HttpdOutputPlugin.hxx
View file @
70879356
/*
* Copyright (C) 2003-201
1
The Music Player Daemon Project
* Copyright (C) 2003-201
3
The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
...
...
@@ -17,8 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_HTTPD_OUTPUT_PLUGIN_H
#define MPD_HTTPD_OUTPUT_PLUGIN_H
#ifndef MPD_HTTPD_OUTPUT_PLUGIN_H
XX
#define MPD_HTTPD_OUTPUT_PLUGIN_H
XX
extern
const
struct
audio_output_plugin
httpd_output_plugin
;
...
...
src/output/httpd_client.c
deleted
100644 → 0
View file @
5822daa6
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "httpd_client.h"
#include "httpd_internal.h"
#include "util/fifo_buffer.h"
#include "page.h"
#include "icy_server.h"
#include "glib_socket.h"
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "httpd_output"
struct
httpd_client
{
/**
* The httpd output object this client is connected to.
*/
struct
httpd_output
*
httpd
;
/**
* The TCP socket.
*/
GIOChannel
*
channel
;
/**
* The GLib main loop source id for reading from the socket,
* and to detect errors.
*/
guint
read_source_id
;
/**
* The GLib main loop source id for writing to the socket. If
* 0, then there is no event source currently (because there
* are no queued pages).
*/
guint
write_source_id
;
/**
* For buffered reading. This pointer is only valid while the
* HTTP request is read.
*/
struct
fifo_buffer
*
input
;
/**
* The current state of the client.
*/
enum
{
/** reading the request line */
REQUEST
,
/** reading the request headers */
HEADERS
,
/** sending the HTTP response */
RESPONSE
,
}
state
;
/**
* A queue of #page objects to be sent to the client.
*/
GQueue
*
pages
;
/**
* The #page which is currently being sent to the client.
*/
struct
page
*
current_page
;
/**
* The amount of bytes which were already sent from
* #current_page.
*/
size_t
current_position
;
/**
* If DLNA streaming was an option.
*/
bool
dlna_streaming_requested
;
/* ICY */
/**
* Do we support sending Icy-Metadata to the client? This is
* disabled if the httpd audio output uses encoder tags.
*/
bool
metadata_supported
;
/**
* If we should sent icy metadata.
*/
bool
metadata_requested
;
/**
* If the current metadata was already sent to the client.
*/
bool
metadata_sent
;
/**
* The amount of streaming data between each metadata block
*/
guint
metaint
;
/**
* The metadata as #page which is currently being sent to the client.
*/
struct
page
*
metadata
;
/*
* The amount of bytes which were already sent from the metadata.
*/
size_t
metadata_current_position
;
/**
* The amount of streaming data sent to the client
* since the last icy information was sent.
*/
guint
metadata_fill
;
};
static
void
httpd_client_unref_page
(
gpointer
data
,
G_GNUC_UNUSED
gpointer
user_data
)
{
struct
page
*
page
=
data
;
page_unref
(
page
);
}
void
httpd_client_free
(
struct
httpd_client
*
client
)
{
assert
(
client
!=
NULL
);
if
(
client
->
state
==
RESPONSE
)
{
if
(
client
->
write_source_id
!=
0
)
g_source_remove
(
client
->
write_source_id
);
if
(
client
->
current_page
!=
NULL
)
page_unref
(
client
->
current_page
);
g_queue_foreach
(
client
->
pages
,
httpd_client_unref_page
,
NULL
);
g_queue_free
(
client
->
pages
);
}
else
fifo_buffer_free
(
client
->
input
);
if
(
client
->
metadata
)
page_unref
(
client
->
metadata
);
g_source_remove
(
client
->
read_source_id
);
g_io_channel_unref
(
client
->
channel
);
g_free
(
client
);
}
/**
* Frees the client and removes it from the server's client list.
*/
static
void
httpd_client_close
(
struct
httpd_client
*
client
)
{
assert
(
client
!=
NULL
);
httpd_output_remove_client
(
client
->
httpd
,
client
);
httpd_client_free
(
client
);
}
/**
* Switch the client to the "RESPONSE" state.
*/
static
void
httpd_client_begin_response
(
struct
httpd_client
*
client
)
{
assert
(
client
!=
NULL
);
assert
(
client
->
state
!=
RESPONSE
);
client
->
state
=
RESPONSE
;
client
->
write_source_id
=
0
;
client
->
pages
=
g_queue_new
();
client
->
current_page
=
NULL
;
httpd_output_send_header
(
client
->
httpd
,
client
);
}
/**
* Handle a line of the HTTP request.
*/
static
bool
httpd_client_handle_line
(
struct
httpd_client
*
client
,
const
char
*
line
)
{
assert
(
client
->
state
!=
RESPONSE
);
if
(
client
->
state
==
REQUEST
)
{
if
(
strncmp
(
line
,
"GET /"
,
5
)
!=
0
)
{
/* only GET is supported */
g_warning
(
"malformed request line from client"
);
return
false
;
}
line
=
strchr
(
line
+
5
,
' '
);
if
(
line
==
NULL
||
strncmp
(
line
+
1
,
"HTTP/"
,
5
)
!=
0
)
{
/* HTTP/0.9 without request headers */
httpd_client_begin_response
(
client
);
return
true
;
}
/* after the request line, request headers follow */
client
->
state
=
HEADERS
;
return
true
;
}
else
{
if
(
*
line
==
0
)
{
/* empty line: request is finished */
httpd_client_begin_response
(
client
);
return
true
;
}
if
(
g_ascii_strncasecmp
(
line
,
"Icy-MetaData: 1"
,
15
)
==
0
)
{
/* Send icy metadata */
client
->
metadata_requested
=
client
->
metadata_supported
;
return
true
;
}
if
(
g_ascii_strncasecmp
(
line
,
"transferMode.dlna.org: Streaming"
,
32
)
==
0
)
{
/* Send as dlna */
client
->
dlna_streaming_requested
=
true
;
/* metadata is not supported by dlna streaming, so disable it */
client
->
metadata_supported
=
false
;
client
->
metadata_requested
=
false
;
return
true
;
}
/* expect more request headers */
return
true
;
}
}
/**
* Check if a complete line of input is present in the input buffer,
* and duplicates it. It is removed from the input buffer. The
* return value has to be freed with g_free().
*/
static
char
*
httpd_client_read_line
(
struct
httpd_client
*
client
)
{
assert
(
client
!=
NULL
);
assert
(
client
->
state
!=
RESPONSE
);
const
char
*
p
,
*
newline
;
size_t
length
;
char
*
line
;
p
=
fifo_buffer_read
(
client
->
input
,
&
length
);
if
(
p
==
NULL
)
/* empty input buffer */
return
NULL
;
newline
=
memchr
(
p
,
'\n'
,
length
);
if
(
newline
==
NULL
)
/* incomplete line */
return
NULL
;
line
=
g_strndup
(
p
,
newline
-
p
);
fifo_buffer_consume
(
client
->
input
,
newline
-
p
+
1
);
/* remove trailing whitespace (e.g. '\r') */
return
g_strchomp
(
line
);
}
/**
* Sends the status line and response headers to the client.
*/
static
bool
httpd_client_send_response
(
struct
httpd_client
*
client
)
{
char
buffer
[
1024
];
GError
*
error
=
NULL
;
GIOStatus
status
;
gsize
bytes_written
;
assert
(
client
!=
NULL
);
assert
(
client
->
state
==
RESPONSE
);
if
(
client
->
dlna_streaming_requested
)
{
g_snprintf
(
buffer
,
sizeof
(
buffer
),
"HTTP/1.1 206 OK
\r\n
"
"Content-Type: %s
\r\n
"
"Content-Length: 10000
\r\n
"
"Content-RangeX: 0-1000000/1000000
\r\n
"
"transferMode.dlna.org: Streaming
\r\n
"
"Accept-Ranges: bytes
\r\n
"
"Connection: close
\r\n
"
"realTimeInfo.dlna.org: DLNA.ORG_TLAG=*
\r\n
"
"contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0
\r\n
"
"
\r\n
"
,
client
->
httpd
->
content_type
);
}
else
if
(
client
->
metadata_requested
)
{
gchar
*
metadata_header
;
metadata_header
=
icy_server_metadata_header
(
client
->
httpd
->
name
,
client
->
httpd
->
genre
,
client
->
httpd
->
website
,
client
->
httpd
->
content_type
,
client
->
metaint
);
g_strlcpy
(
buffer
,
metadata_header
,
sizeof
(
buffer
));
g_free
(
metadata_header
);
}
else
{
/* revert to a normal HTTP request */
g_snprintf
(
buffer
,
sizeof
(
buffer
),
"HTTP/1.1 200 OK
\r\n
"
"Content-Type: %s
\r\n
"
"Connection: close
\r\n
"
"Pragma: no-cache
\r\n
"
"Cache-Control: no-cache, no-store
\r\n
"
"
\r\n
"
,
client
->
httpd
->
content_type
);
}
status
=
g_io_channel_write_chars
(
client
->
channel
,
buffer
,
strlen
(
buffer
),
&
bytes_written
,
&
error
);
switch
(
status
)
{
case
G_IO_STATUS_NORMAL
:
case
G_IO_STATUS_AGAIN
:
return
true
;
case
G_IO_STATUS_EOF
:
/* client has disconnected */
httpd_client_close
(
client
);
return
false
;
case
G_IO_STATUS_ERROR
:
/* I/O error */
g_warning
(
"failed to write to client: %s"
,
error
->
message
);
g_error_free
(
error
);
httpd_client_close
(
client
);
return
false
;
}
/* unreachable */
httpd_client_close
(
client
);
return
false
;
}
/**
* Data has been received from the client and it is appended to the
* input buffer.
*/
static
bool
httpd_client_received
(
struct
httpd_client
*
client
)
{
assert
(
client
!=
NULL
);
assert
(
client
->
state
!=
RESPONSE
);
char
*
line
;
bool
success
;
while
((
line
=
httpd_client_read_line
(
client
))
!=
NULL
)
{
success
=
httpd_client_handle_line
(
client
,
line
);
g_free
(
line
);
if
(
!
success
)
{
assert
(
client
->
state
!=
RESPONSE
);
return
false
;
}
if
(
client
->
state
==
RESPONSE
)
{
if
(
!
fifo_buffer_is_empty
(
client
->
input
))
{
g_warning
(
"unexpected input from client"
);
return
false
;
}
fifo_buffer_free
(
client
->
input
);
return
httpd_client_send_response
(
client
);
}
}
return
true
;
}
static
bool
httpd_client_read
(
struct
httpd_client
*
client
)
{
char
*
p
;
size_t
max_length
;
GError
*
error
=
NULL
;
GIOStatus
status
;
gsize
bytes_read
;
if
(
client
->
state
==
RESPONSE
)
{
/* the client has already sent the request, and he
must not send more */
char
buffer
[
1
];
status
=
g_io_channel_read_chars
(
client
->
channel
,
buffer
,
sizeof
(
buffer
),
&
bytes_read
,
NULL
);
if
(
status
==
G_IO_STATUS_NORMAL
)
g_warning
(
"unexpected input from client"
);
return
false
;
}
p
=
fifo_buffer_write
(
client
->
input
,
&
max_length
);
if
(
p
==
NULL
)
{
g_warning
(
"buffer overflow"
);
return
false
;
}
status
=
g_io_channel_read_chars
(
client
->
channel
,
p
,
max_length
,
&
bytes_read
,
&
error
);
switch
(
status
)
{
case
G_IO_STATUS_NORMAL
:
fifo_buffer_append
(
client
->
input
,
bytes_read
);
return
httpd_client_received
(
client
);
case
G_IO_STATUS_AGAIN
:
/* try again later, after select() */
return
true
;
case
G_IO_STATUS_EOF
:
/* peer disconnected */
return
false
;
case
G_IO_STATUS_ERROR
:
/* I/O error */
g_warning
(
"failed to read from client: %s"
,
error
->
message
);
g_error_free
(
error
);
return
false
;
}
/* unreachable */
return
false
;
}
static
gboolean
httpd_client_in_event
(
G_GNUC_UNUSED
GIOChannel
*
source
,
GIOCondition
condition
,
gpointer
data
)
{
struct
httpd_client
*
client
=
data
;
struct
httpd_output
*
httpd
=
client
->
httpd
;
bool
ret
;
g_mutex_lock
(
httpd
->
mutex
);
if
(
condition
==
G_IO_IN
&&
httpd_client_read
(
client
))
{
ret
=
true
;
}
else
{
httpd_client_close
(
client
);
ret
=
false
;
}
g_mutex_unlock
(
httpd
->
mutex
);
return
ret
;
}
struct
httpd_client
*
httpd_client_new
(
struct
httpd_output
*
httpd
,
int
fd
,
bool
metadata_supported
)
{
struct
httpd_client
*
client
=
g_new
(
struct
httpd_client
,
1
);
client
->
httpd
=
httpd
;
client
->
channel
=
g_io_channel_new_socket
(
fd
);
/* GLib is responsible for closing the file descriptor */
g_io_channel_set_close_on_unref
(
client
->
channel
,
true
);
/* NULL encoding means the stream is binary safe */
g_io_channel_set_encoding
(
client
->
channel
,
NULL
,
NULL
);
/* we prefer to do buffering */
g_io_channel_set_buffered
(
client
->
channel
,
false
);
client
->
read_source_id
=
g_io_add_watch
(
client
->
channel
,
G_IO_IN
|
G_IO_ERR
|
G_IO_HUP
,
httpd_client_in_event
,
client
);
client
->
input
=
fifo_buffer_new
(
4096
);
client
->
state
=
REQUEST
;
client
->
dlna_streaming_requested
=
false
;
client
->
metadata_supported
=
metadata_supported
;
client
->
metadata_requested
=
false
;
client
->
metadata_sent
=
true
;
client
->
metaint
=
8192
;
/*TODO: just a std value */
client
->
metadata
=
NULL
;
client
->
metadata_current_position
=
0
;
client
->
metadata_fill
=
0
;
return
client
;
}
static
void
httpd_client_add_page_size
(
gpointer
data
,
gpointer
user_data
)
{
struct
page
*
page
=
data
;
size_t
*
size
=
user_data
;
*
size
+=
page
->
size
;
}
size_t
httpd_client_queue_size
(
const
struct
httpd_client
*
client
)
{
size_t
size
=
0
;
if
(
client
->
state
!=
RESPONSE
)
return
0
;
g_queue_foreach
(
client
->
pages
,
httpd_client_add_page_size
,
&
size
);
return
size
;
}
void
httpd_client_cancel
(
struct
httpd_client
*
client
)
{
if
(
client
->
state
!=
RESPONSE
)
return
;
g_queue_foreach
(
client
->
pages
,
httpd_client_unref_page
,
NULL
);
g_queue_clear
(
client
->
pages
);
if
(
client
->
write_source_id
!=
0
&&
client
->
current_page
==
NULL
)
{
g_source_remove
(
client
->
write_source_id
);
client
->
write_source_id
=
0
;
}
}
static
GIOStatus
write_page_to_channel
(
GIOChannel
*
channel
,
const
struct
page
*
page
,
size_t
position
,
gsize
*
bytes_written_r
,
GError
**
error
)
{
assert
(
channel
!=
NULL
);
assert
(
page
!=
NULL
);
assert
(
position
<
page
->
size
);
return
g_io_channel_write_chars
(
channel
,
(
const
gchar
*
)
page
->
data
+
position
,
page
->
size
-
position
,
bytes_written_r
,
error
);
}
static
GIOStatus
write_n_bytes_to_channel
(
GIOChannel
*
channel
,
const
struct
page
*
page
,
size_t
position
,
gint
n
,
gsize
*
bytes_written_r
,
GError
**
error
)
{
GIOStatus
status
;
assert
(
channel
!=
NULL
);
assert
(
page
!=
NULL
);
assert
(
position
<
page
->
size
);
if
(
n
==
-
1
)
{
status
=
write_page_to_channel
(
channel
,
page
,
position
,
bytes_written_r
,
error
);
}
else
{
status
=
g_io_channel_write_chars
(
channel
,
(
const
gchar
*
)
page
->
data
+
position
,
n
,
bytes_written_r
,
error
);
}
return
status
;
}
static
gint
bytes_left_till_metadata
(
struct
httpd_client
*
client
)
{
assert
(
client
!=
NULL
);
if
(
client
->
metadata_requested
&&
client
->
current_page
->
size
-
client
->
current_position
>
client
->
metaint
-
client
->
metadata_fill
)
return
client
->
metaint
-
client
->
metadata_fill
;
return
-
1
;
}
static
gboolean
httpd_client_out_event
(
GIOChannel
*
source
,
G_GNUC_UNUSED
GIOCondition
condition
,
gpointer
data
)
{
struct
httpd_client
*
client
=
data
;
struct
httpd_output
*
httpd
=
client
->
httpd
;
GError
*
error
=
NULL
;
GIOStatus
status
;
gsize
bytes_written
;
gint
bytes_to_write
;
g_mutex_lock
(
httpd
->
mutex
);
assert
(
condition
==
G_IO_OUT
);
assert
(
client
->
state
==
RESPONSE
);
if
(
client
->
write_source_id
==
0
)
{
/* another thread has removed the event source while
this thread was waiting for httpd->mutex */
g_mutex_unlock
(
httpd
->
mutex
);
return
false
;
}
if
(
client
->
current_page
==
NULL
)
{
client
->
current_page
=
g_queue_pop_head
(
client
->
pages
);
client
->
current_position
=
0
;
}
bytes_to_write
=
bytes_left_till_metadata
(
client
);
if
(
bytes_to_write
==
0
)
{
gint
metadata_to_write
;
metadata_to_write
=
client
->
metadata_current_position
;
if
(
!
client
->
metadata_sent
)
{
status
=
write_page_to_channel
(
source
,
client
->
metadata
,
metadata_to_write
,
&
bytes_written
,
&
error
);
client
->
metadata_current_position
+=
bytes_written
;
if
(
client
->
metadata
->
size
-
client
->
metadata_current_position
==
0
)
{
client
->
metadata_fill
=
0
;
client
->
metadata_current_position
=
0
;
client
->
metadata_sent
=
true
;
}
}
else
{
struct
page
*
empty_meta
;
guchar
empty_data
=
0
;
empty_meta
=
page_new_copy
(
&
empty_data
,
1
);
status
=
write_page_to_channel
(
source
,
empty_meta
,
metadata_to_write
,
&
bytes_written
,
&
error
);
client
->
metadata_current_position
+=
bytes_written
;
if
(
empty_meta
->
size
-
client
->
metadata_current_position
==
0
)
{
client
->
metadata_fill
=
0
;
client
->
metadata_current_position
=
0
;
}
}
bytes_written
=
0
;
}
else
{
status
=
write_n_bytes_to_channel
(
source
,
client
->
current_page
,
client
->
current_position
,
bytes_to_write
,
&
bytes_written
,
&
error
);
}
switch
(
status
)
{
case
G_IO_STATUS_NORMAL
:
client
->
current_position
+=
bytes_written
;
assert
(
client
->
current_position
<=
client
->
current_page
->
size
);
if
(
client
->
metadata_requested
)
client
->
metadata_fill
+=
bytes_written
;
if
(
client
->
current_position
>=
client
->
current_page
->
size
)
{
page_unref
(
client
->
current_page
);
client
->
current_page
=
NULL
;
if
(
g_queue_is_empty
(
client
->
pages
))
{
/* all pages are sent: remove the
event source */
client
->
write_source_id
=
0
;
g_mutex_unlock
(
httpd
->
mutex
);
return
false
;
}
}
g_mutex_unlock
(
httpd
->
mutex
);
return
true
;
case
G_IO_STATUS_AGAIN
:
g_mutex_unlock
(
httpd
->
mutex
);
return
true
;
case
G_IO_STATUS_EOF
:
/* client has disconnected */
httpd_client_close
(
client
);
g_mutex_unlock
(
httpd
->
mutex
);
return
false
;
case
G_IO_STATUS_ERROR
:
/* I/O error */
g_warning
(
"failed to write to client: %s"
,
error
->
message
);
g_error_free
(
error
);
httpd_client_close
(
client
);
g_mutex_unlock
(
httpd
->
mutex
);
return
false
;
}
/* unreachable */
httpd_client_close
(
client
);
g_mutex_unlock
(
httpd
->
mutex
);
return
false
;
}
void
httpd_client_send
(
struct
httpd_client
*
client
,
struct
page
*
page
)
{
if
(
client
->
state
!=
RESPONSE
)
/* the client is still writing the HTTP request */
return
;
page_ref
(
page
);
g_queue_push_tail
(
client
->
pages
,
page
);
if
(
client
->
write_source_id
==
0
)
client
->
write_source_id
=
g_io_add_watch
(
client
->
channel
,
G_IO_OUT
,
httpd_client_out_event
,
client
);
}
void
httpd_client_send_metadata
(
struct
httpd_client
*
client
,
struct
page
*
page
)
{
if
(
client
->
metadata
)
{
page_unref
(
client
->
metadata
);
client
->
metadata
=
NULL
;
}
g_return_if_fail
(
page
);
page_ref
(
page
);
client
->
metadata
=
page
;
client
->
metadata_sent
=
false
;
}
src/output/httpd_client.h
deleted
100644 → 0
View file @
5822daa6
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_OUTPUT_HTTPD_CLIENT_H
#define MPD_OUTPUT_HTTPD_CLIENT_H
#include <stdbool.h>
#include <stddef.h>
struct
httpd_client
;
struct
httpd_output
;
struct
page
;
/**
* Creates a new #httpd_client object
*
* @param httpd the HTTP output device
* @param fd the socket file descriptor
*/
struct
httpd_client
*
httpd_client_new
(
struct
httpd_output
*
httpd
,
int
fd
,
bool
metadata_supported
);
/**
* Frees memory and resources allocated by the #httpd_client object.
* This does not remove it from the #httpd_output object.
*/
void
httpd_client_free
(
struct
httpd_client
*
client
);
/**
* Returns the total size of this client's page queue.
*/
size_t
httpd_client_queue_size
(
const
struct
httpd_client
*
client
);
/**
* Clears the page queue.
*/
void
httpd_client_cancel
(
struct
httpd_client
*
client
);
/**
* Appends a page to the client's queue.
*/
void
httpd_client_send
(
struct
httpd_client
*
client
,
struct
page
*
page
);
/**
* Sends the passed metadata.
*/
void
httpd_client_send_metadata
(
struct
httpd_client
*
client
,
struct
page
*
page
);
#endif
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment