[FFmpeg-devel] [GSoC] Proof-of-concept HTTP Server

Stephan Holljes klaxa1337 at googlemail.com
Thu Mar 26 03:51:39 CET 2015


I hope this time the patch is formatted correctly. I also attached it
in case it is corrupted again.
I changed the indentation of the code and used ffurl_open() instead of
creating my own listening socket.

I am still having some trouble with the Content-Type header. I would
guess creating functions like http_write_header() as the counterpart
for http_read_header() would be the most appropriate approach?


---
 libavformat/http.c | 33 ++++++++++++++++++++++++++++++++-
 1 file changed, 32 insertions(+), 1 deletion(-)

diff --git a/libavformat/http.c b/libavformat/http.c
index da3c9be..472d8dd 100644
--- a/libavformat/http.c
+++ b/libavformat/http.c
@@ -96,6 +96,8 @@ typedef struct HTTPContext {
     int send_expect_100;
     char *method;
     int reconnect;
+    int listen;
+    int header_sent;
 } HTTPContext;

 #define OFFSET(x) offsetof(HTTPContext, x)
@@ -127,6 +129,7 @@ static const AVOption options[] = {
     { "end_offset", "try to limit the request to bytes preceding this
offset", OFFSET(end_off), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0,
INT64_MAX, D },
     { "method", "Override the HTTP method", OFFSET(method),
AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E },
     { "reconnect", "auto reconnect after disconnect before EOF",
OFFSET(reconnect), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, D },
+    { "listen", "listen on HTTP", OFFSET(listen), AV_OPT_TYPE_INT, {
.i64 = 0 }, 0, 1, D },
     { NULL }
 };

@@ -300,7 +303,9 @@ static int http_open(URLContext *h, const char
*uri, int flags,
                      AVDictionary **options)
 {
     HTTPContext *s = h->priv_data;
-    int ret;
+    int port, ret = 0;
+    char hostname[1024];
+    char lower_url[100];

     if( s->seekable == 1 )
         h->is_streamed = 0;
@@ -321,6 +326,20 @@ static int http_open(URLContext *h, const char
*uri, int flags,
                    "No trailing CRLF found in HTTP header.\n");
     }

+    if (s->listen) {
+        av_url_split(NULL, 0, NULL, 0, hostname, sizeof(hostname), &port,
+                     NULL, 0, uri);
+        ff_url_join(lower_url, sizeof(lower_url), "tcp", NULL, hostname, port,
+                NULL);
+        av_dict_set(options, "listen", "1", AV_DICT_APPEND);
+        ret = ffurl_open(&s->hd, lower_url, AVIO_FLAG_READ_WRITE,
+                   &h->interrupt_callback, options);
+        if (ret < 0) {
+            return ret;
+        }
+
+        return ret;
+    }
     ret = http_open_cnx(h, options);
     if (ret < 0)
         av_dict_free(&s->chained_options);
@@ -1100,10 +1119,22 @@ static int http_read(URLContext *h, uint8_t
*buf, int size)
 static int http_write(URLContext *h, const uint8_t *buf, int size)
 {
     char temp[11] = "";  /* 32-bit hex + CRLF + nul */
+    char header[] = "HTTP 200 OK\r\nContent-Type:
application/octet-stream\r\n\r\n";
     int ret;
     char crlf[] = "\r\n";
     HTTPContext *s = h->priv_data;
+    if (s->listen) {
+        if (!s->header_sent) {
+            ret = ffurl_write(s->hd, header, strlen(header));
+            if (ret < 0) {
+                return ff_neterrno();
+            }
+            s->header_sent = 1;
+        }

+        ret = ffurl_write(s->hd, buf, size);
+        return ret < 0 ? ff_neterrno() : ret;
+    }
     if (!s->chunked_post) {
         /* non-chunked data is sent without any special encoding */
         return ffurl_write(s->hd, buf, size);
-- 
2.3.3

On Sun, Mar 22, 2015 at 10:33 AM, Nicolas George <george at nsup.org> wrote:
> Le primidi 1er germinal, an CCXXIII, Stephan Holljes a écrit :
>> Please comment.
>
> As Michael pointed out, the patch was mangled at sending. That happens with
> mail user agent that rely on "rich" text editor and do not let users control
> the formatting finely. Using git send-email or attaching the file will solve
> the problem. They also include authorship and date information.
>
>>
>> Regards,
>> Stephan Holljes
>>
>> ---
>>  libavformat/http.c |  113
>> ++++++++++++++++++++++++++++++++++++++--------------
>>  1 file changed, 83 insertions(+), 30 deletions(-)
>>
>> diff --git a/libavformat/http.c b/libavformat/http.c
>> index da3c9be..d61e4e2 100644
>> --- a/libavformat/http.c
>> +++ b/libavformat/http.c
>> @@ -96,8 +96,12 @@ typedef struct HTTPContext {
>>      int send_expect_100;
>>      char *method;
>>      int reconnect;
>> +    int listen;
>> +    int fd;
>> +    int header_sent;
>>  } HTTPContext;
>>
>> +
>>  #define OFFSET(x) offsetof(HTTPContext, x)
>>  #define D AV_OPT_FLAG_DECODING_PARAM
>>  #define E AV_OPT_FLAG_ENCODING_PARAM
>> @@ -127,6 +131,7 @@ static const AVOption options[] = {
>>      { "end_offset", "try to limit the request to bytes preceding this
>> offset", OFFSET(end_off), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D
>> },
>>      { "method", "Override the HTTP method", OFFSET(method),
>> AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E },
>>      { "reconnect", "auto reconnect after disconnect before EOF",
>> OFFSET(reconnect), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, D },
>> +    { "listen", "listen on HTTP", OFFSET(listen), AV_OPT_TYPE_INT, { .i64
>> = 0 }, 0, 1, D },
>>      { NULL }
>>  };
>>
>> @@ -299,8 +304,10 @@ int ff_http_averror(int status_code, int
>> default_averror)
>>  static int http_open(URLContext *h, const char *uri, int flags,
>>                       AVDictionary **options)
>>  {
>> +    struct addrinfo hints = { 0 }, *ai;
>>      HTTPContext *s = h->priv_data;
>> -    int ret;
>> +    int ret = -1, fd;
>> +    char portstr[] = "8080"; // allow non-root users for now
>>
>>      if( s->seekable == 1 )
>>          h->is_streamed = 0;
>> @@ -320,11 +327,39 @@ static int http_open(URLContext *h, const char *uri,
>> int flags,
>>              av_log(h, AV_LOG_WARNING,
>>                     "No trailing CRLF found in HTTP header.\n");
>>      }
>
>> +    if (s->listen) {
>> +        hints.ai_family = AF_UNSPEC;
>> +        hints.ai_socktype = SOCK_STREAM;
>> +        hints.ai_flags |= AI_PASSIVE;
>> +        ret = getaddrinfo(NULL, portstr, &hints, &ai);
>> +        if (ret) {
>> +            av_log(h, AV_LOG_ERROR, "borked");
>> +            return AVERROR(EIO);
>> +        }
>> +        fd = ff_socket(ai->ai_family,
>> +                       ai->ai_socktype,
>> +                       ai->ai_protocol);
>> +        if (fd < 0) {
>> +            ret = ff_neterrno();
>> +            freeaddrinfo(ai);
>> +            return -1;
>> +        }
>
> This part looks copied from tcp.c. It would be much better to use the API
> provided by TCP. In other words, just like the client part does:
>
>         err = ffurl_open(&s->hd, buf, AVIO_FLAG_READ_WRITE,
>                          &h->interrupt_callback, options);
>
> you should do the same thing for creating the listening socket, just adding
> the listen option. There are several reasons for doing that.
>
> First, you avoid reimplementing all the interrupt_callback logic and cie,
> which would be necessary for a complete implementation.
>
> Second, this code with getaddrinfo is slightly wrong (wrt multi-protocol
> hosts), so when it gets fixed in tcp.c, it would benefit to all the code.
>
>>
>> -    ret = http_open_cnx(h, options);
>> -    if (ret < 0)
>> -        av_dict_free(&s->chained_options);
>> -    return ret;
>> +        fd = ff_listen_bind(fd, ai->ai_addr, ai->ai_addrlen, -1, h);
>> +        if (fd < 0) {
>> +            freeaddrinfo(ai);
>> +            return fd;
>> +        }
>> +        h->is_streamed = 1;
>> +        s->fd = fd;
>> +        freeaddrinfo(ai);
>> +        return 0;
>> +    } else {
>> +        ret = http_open_cnx(h, options);
>> +        if (ret < 0)
>> +            av_dict_free(&s->chained_options);
>> +        return ret;
>> +    }
>>  }
>>
>>  static int http_getc(HTTPContext *s)
>> @@ -1102,25 +1137,40 @@ static int http_write(URLContext *h, const uint8_t
>> *buf, int size)
>>      char temp[11] = "";  /* 32-bit hex + CRLF + nul */
>>      int ret;
>>      char crlf[] = "\r\n";
>> +    char header[] = "HTTP 200 OK\r\n\r\n";
>>      HTTPContext *s = h->priv_data;
>> +    if (!s->listen) {
>> +        if (!s->chunked_post) {
>> +            /* non-chunked data is sent without any special encoding */
>> +            return ffurl_write(s->hd, buf, size);
>> +        }
>>
>> -    if (!s->chunked_post) {
>> -        /* non-chunked data is sent without any special encoding */
>> -        return ffurl_write(s->hd, buf, size);
>> -    }
>> -
>> -    /* silently ignore zero-size data since chunk encoding that would
>> -     * signal EOF */
>> -    if (size > 0) {
>> -        /* upload data using chunked encoding */
>> -        snprintf(temp, sizeof(temp), "%x\r\n", size);
>> +        /* silently ignore zero-size data since chunk encoding that would
>> +         * signal EOF */
>> +        if (size > 0) {
>> +            /* upload data using chunked encoding */
>> +            snprintf(temp, sizeof(temp), "%x\r\n", size);
>>
>> -        if ((ret = ffurl_write(s->hd, temp, strlen(temp))) < 0 ||
>> -            (ret = ffurl_write(s->hd, buf, size)) < 0          ||
>> -            (ret = ffurl_write(s->hd, crlf, sizeof(crlf) - 1)) < 0)
>> -            return ret;
>> +            if ((ret = ffurl_write(s->hd, temp, strlen(temp))) < 0 ||
>> +                (ret = ffurl_write(s->hd, buf, size)) < 0          ||
>> +                (ret = ffurl_write(s->hd, crlf, sizeof(crlf) - 1)) < 0)
>> +                return ret;
>> +        }
>> +        return size;
>> +    } else {
>> +        if (!s->header_sent) {
>> +            ret = send(s->fd, header, sizeof(header), MSG_NOSIGNAL);
>> +            s->header_sent = 1;
>> +            return ret < 0 ? ff_neterrno() : ret;
>> +        }
>> +        if (size > 0) {
>> +            ret = send(s->fd, buf, size, MSG_NOSIGNAL);
>> +            return ret < 0 ? ff_neterrno() : ret;
>> +        } else {
>> +            ret = -1;
>> +        }
>> +        return ret;
>
> When you add a test or a loop around a large portion of the code, do not
> reindent the existing code immediately, leave that for a second commit when
> the patch is finished. That makes it simpler to see what was actually
> changed.
>
> You can also consider using a more separate approach:
>
>     if (old_case) {
>     old_code
>     } else {
>         new_code
>     }
>
> can also be written:
>
>     if (new_case)
>         return do_new_code();
>
>>      }
>> -    return size;
>>  }
>>
>>  static int http_shutdown(URLContext *h, int flags)
>> @@ -1143,20 +1193,23 @@ static int http_close(URLContext *h)
>>  {
>>      int ret = 0;
>>      HTTPContext *s = h->priv_data;
>> -
>> +    if (!s->listen) {
>>  #if CONFIG_ZLIB
>> -    inflateEnd(&s->inflate_stream);
>> -    av_freep(&s->inflate_buffer);
>> +        inflateEnd(&s->inflate_stream);
>> +        av_freep(&s->inflate_buffer);
>>  #endif /* CONFIG_ZLIB */
>>
>> -    if (!s->end_chunked_post)
>> -        /* Close the write direction by sending the end of chunked
>> encoding. */
>> -        ret = http_shutdown(h, h->flags);
>> +        if (!s->end_chunked_post)
>> +            /* Close the write direction by sending the end of chunked
>> encoding. */
>> +            ret = http_shutdown(h, h->flags);
>>
>> -    if (s->hd)
>> -        ffurl_closep(&s->hd);
>> -    av_dict_free(&s->chained_options);
>> -    return ret;
>> +        if (s->hd)
>> +            ffurl_closep(&s->hd);
>> +        av_dict_free(&s->chained_options);
>> +        return ret;
>> +    } else {
>> +        return shutdown(s->fd, SHUT_RDWR);
>> +    }
>>  }
>>
>>  static int64_t http_seek_internal(URLContext *h, int64_t off, int whence,
>> int force_reconnect)
>
> Regards,
>
> --
>   Nicolas George
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0001-Implemented-simple-listen-mode-for-http.patch
Type: text/x-patch
Size: 3182 bytes
Desc: not available
URL: <https://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20150326/ec445568/attachment.bin>


More information about the ffmpeg-devel mailing list