[FFmpeg-devel] [PATCH 6/8] lavf/http: increase range for listen, handle connection closing accordingly, add http_accept, add http_handshake and move handshake logic there
Stephan Holljes
klaxa1337 at googlemail.com
Tue Jul 21 23:47:55 CEST 2015
On Tue, Jul 21, 2015 at 12:14 PM, Nicolas George <george at nsup.org> wrote:
> Le tridi 3 thermidor, an CCXXIII, Stephan Holljes a écrit :
>> From 2dc2be7e8576fd064579d37c75c343a6f18c068c Mon Sep 17 00:00:00 2001
>> From: Stephan Holljes <klaxa1337 at googlemail.com>
>> Date: Fri, 3 Jul 2015 02:28:56 +0200
>
>> Subject: [PATCH 6/8] lavf/http: increase range for listen, handle connection
>> closing accordingly, add http_accept, add http_handshake and move handshake
>> logic there
>
> Nit: Git practice advise to have a short first line and add details later,
> with lines wrapped not too long (git log adds intendation).
>
>>
>> Signed-off-by: Stephan Holljes <klaxa1337 at googlemail.com>
>> ---
>> libavformat/http.c | 127 ++++++++++++++++++++++++++++++++++++++++++++---------
>> 1 file changed, 106 insertions(+), 21 deletions(-)
>>
>> diff --git a/libavformat/http.c b/libavformat/http.c
>> index 676bfd5..b8016a7 100644
>> --- a/libavformat/http.c
>> +++ b/libavformat/http.c
>> @@ -25,6 +25,7 @@
>> #include <zlib.h>
>> #endif /* CONFIG_ZLIB */
>>
>> +#include "libavutil/avassert.h"
>> #include "libavutil/avstring.h"
>> #include "libavutil/opt.h"
>>
>> @@ -44,6 +45,9 @@
>> * path names). */
>> #define BUFFER_SIZE MAX_URL_SIZE
>> #define MAX_REDIRECTS 8
>
>> +#define HTTP_ONESHOT 1
>> +#define HTTP_MUTLI 2
>> +#define HTTP_MULTI_CLIENT 4
>
> Are they supposed to be flags? Otherwise: where did 3 go?
I wasn't sure if in the future something might want to filter the
different server modes so using 3 would mess up bitmasking. By
introducing a new field as you suggested, this would become obsolete
anyway.
>
>>
>> typedef struct HTTPContext {
>> const AVClass *class;
>> @@ -97,6 +101,7 @@ typedef struct HTTPContext {
>> char *method;
>> int reconnect;
>> int listen;
>> + char *resource;
>> } HTTPContext;
>>
>> #define OFFSET(x) offsetof(HTTPContext, x)
>> @@ -128,7 +133,9 @@ 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 or set the expected HTTP method from a client", OFFSET(method), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | 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 | E },
>
>> + { "listen", "listen on HTTP", OFFSET(listen), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 4, D | E },
>
> Does it make sense for the application/user to set 4? If not, then a
> separate field may be better.
This would probably make more sense. I am somewhat hesitant to just
add more and more fields to the HTTPContext; am I worrying too much
about memory footprint here?
>
>> + { "resource", "The resource requested by a client", OFFSET(resource), AV_OPT_TYPE_STRING, { 0 }, 0, 0, E },
>
>> + { "http_code", "The http code to send to a client", OFFSET(http_code), AV_OPT_TYPE_INT, { .i64 = 0}, 0, 599, E},
>
> Nit: Since this is HTTP anyway, the name is redundant. Maybe "reply_code"?
I merely reused the already present field http_code, should this be avoided?
>
>> { NULL }
>> };
>>
>> @@ -299,32 +306,87 @@ int ff_http_averror(int status_code, int default_averror)
>> return default_averror;
>> }
>>
>
>> +static int http_write_header(URLContext* h, int status_code)
>
> The name is misleading: it does not only write the header, it also writes a
> single-line reply, except in the 200 case. More on that later.
>
>> +{
>> + int ret;
>> + const char *message;
>
>> + // Maybe this should be done more elegantly?
>> + static const char bad_request[] = "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\nContent-Length: 17\r\n400 Bad Request\r\n";
>> + static const char forbidden[] = "HTTP/1.1 403 Forbidden\r\nContent-Type: text/plain\r\nContent-Length: 15\r\n\r\n403 Forbidden\r\n";
>> + static const char not_found[] = "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: 15\r\n\r\n404 Not Found\r\n";
>> + static const char internal_server_error[] = "HTTP/1.1 500 Internal server error\r\nContent-Type: text/plain\r\nContent-Length: 25\r\n\r\n500 Internal server error\r\n";
>
> Well, all the reply strings have the following format:
>
> "HTTP/1.1 %03d %s\r\n"
> "Content-Type: application/octet-stream\r\n"
> "Content-Length: %d\r\n"
> "\r\n"
> "%03d %s\r\n"
>
> So you can probably have just "int reply_code" and "const char *reply_text"
> and fill in in a local buffer.
http_connect() uses s->buffer for the same purpose. Should I just reuse that?
>
>> + static const char ok[] = "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nTransfer-Encoding: chunked\r\n\r\n";
>> + av_log(h, AV_LOG_TRACE, "err: %d\n", status_code);
>
>> + if (status_code == 200) {
>> + message = ok;
>> + goto end;
>> + }
>
> It could go back inside the switch. That avoids the goto.
>
>> + switch(status_code) {
>> + case AVERROR_HTTP_BAD_REQUEST:
>
> Nit: usual style is "switch<space>{" and case indented at the same level.
Ok. Should this style be introduced when possible? Then I will make
the switch-style in http.c consistent once this patchset is done.
>
>> + message = bad_request;
>> + break;
>> + case AVERROR_HTTP_FORBIDDEN:
>> + message = forbidden;
>> + break;
>> + case AVERROR_HTTP_NOT_FOUND:
>> + message = not_found;
>> + break;
>> + default:
>> + message = internal_server_error;
>> + }
>> +end:
>
>> + if ((ret = ffurl_write(h, message, strlen(message))) < 0)
>> + return ret;
>> + // Avoid returning a positive value from ffurl_write()
>> + ret = ret > 0 ? 0 : ret;
>> + return ret;
>
> If I read the logic correctly, ret is always 0 when it reaches return.
Yes, I see that now too. Redundant code removed.
>
>> +}
>> +
>> static void handle_http_errors(URLContext *h, int error)
>> {
>> - static const char bad_request[] = "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\n\r\n400 Bad Request\r\n";
>> - static const char internal_server_error[] = "HTTP/1.1 500 Internal server error\r\nContent-Type: text/plain\r\n\r\n500 Internal server error\r\n";
>> HTTPContext *s = h->priv_data;
>> - if (h->is_connected) {
>> - switch(error) {
>> - case AVERROR_HTTP_BAD_REQUEST:
>> - ffurl_write(s->hd, bad_request, strlen(bad_request));
>> - break;
>> - default:
>> - av_log(h, AV_LOG_ERROR, "Unhandled HTTP error.\n");
>> - ffurl_write(s->hd, internal_server_error, strlen(internal_server_error));
>> + URLContext *c = s->hd;
>> + av_assert0(error < 0);
>> + http_write_header(c, error);
>> +}
>> +
>> +static int http_handshake(URLContext *c)
>> +{
>> + int ret, err, new_location;
>> + HTTPContext *ch = c->priv_data;
>
>> + URLContext *cl = ch->hd;
>
> By the way: why "cl"?
_C_lient _L_ower protocol. Is there something better that you can suggest?
While we're at it: What does the "hd" in HTTPContext abbreviate? I
can't think of anything but http descriptor?
>
>> + for (;;) {
>> + if ((ret = ffurl_handshake(cl)) < 0)
>> + return ret;
>> + if (ret == 0)
>> + break;
>> + }
>
> You are killing the handshake split. The application may want to close the
> client immediately, before reading the request, based solely on the IP
> address (assuming the TCP protocol returns it, not urgent). Even worse: with
> TLS and Server Name Indication, the application must chose a certificate
> between two steps of the TLS handshake (not yet implemented, of course). You
> must not loop here, you must return each time.
>
> The code could look like something like that:
>
> case (handshake_step) {
> case SLAVE_PROTOCOL:
> ret = ffurl_handshake(cl);
> if (!ret)
> handshake_step = READ_HEADERS;
> return ret;
> case READ_HEADERS:
> ...
> }
Alright, I didn't think about this enough, probably because I am very
unfamiliar with how handshakes in the lower protocols (especially TLS)
work, I will read up on that.
>
>> + if (!ch->end_header) {
>
>> + if ((err = http_read_header(c, &new_location)) < 0)
>> + goto fail;
>
> If reading the headers fails, IMHO no reply should be sent.
Right, fixed.
>
>> + }
>
>> + if (ch->http_code) {
>> + if (ch->http_code == 200) {
>> + http_write_header(cl, 200);
>> + return 0;
>> }
>
> Does that mean the application must set http_code? That would make it
> impossible to make an application that works transparently with several
> protocols.
Right now, yes.
>
>> + err = ff_http_averror(ch->http_code, AVERROR(EIO));
>> + goto fail;
>
> If the application has requested an error reply sent to the client, then
> from the point of view of the application, this is success.
>
> There is also a protocol concern here: the application can not send a "206
> Partial Content" reply, because for anything except 200 the code
> automatically sends a single-line body reply. For that matter, sending a
> fancy 404 page is impossible too.
>
> I think the following convention could work and be convenient:
>
> - 100 <= http_code < 600 -> return just the header, let the application send
> the body;
>
> - http_code < 0 -> translate AVERROR to HTTP as closely as possible and send
> a built-in reply body;
>
> - anything else: return AVERROR(EINVAL) immediately.
>
> And 200 should just be the default value.
This is a great suggestion, I had trouble deciding where to use HTTP
status codes and where to use AVERROR HTTP errors.
>
> (And later, someone can work with Andrey to make sure all three-digit codes
> map to AVERROR codes; maybe something like that
> #define AVERROR_THREE_DIGIT_STATUS(n) FFERRTAG(0xF8, \
> '0' + (n) / 100,
> '0' + (n) / 10 % 10,
> '0' + (n) % 10)
> )
>
>> }
>> + return 1;
>> +fail:
>> + handle_http_errors(c, err);
>> + return err;
>> }
>>
>> static int http_listen(URLContext *h, const char *uri, int flags,
>> AVDictionary **options) {
>> HTTPContext *s = h->priv_data;
>> int ret;
>> - static const char header[] = "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nTransfer-Encoding: chunked\r\n\r\n";
>> char hostname[1024], proto[10];
>> char lower_url[100];
>> const char *lower_proto = "tcp";
>> - int port, new_location;
>> + int port;
>> s->chunked_post = 1;
>> av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port,
>> NULL, 0, uri);
>> @@ -332,18 +394,17 @@ static int http_listen(URLContext *h, const char *uri, int flags,
>> lower_proto = "tls";
>> ff_url_join(lower_url, sizeof(lower_url), lower_proto, NULL, hostname, port,
>> NULL);
>> - av_dict_set(options, "listen", "1", 0);
>> + if ((ret = av_dict_set_int(options, "listen", s->listen, 0)) < 0)
>> + goto fail;
>> if ((ret = ffurl_open(&s->hd, lower_url, AVIO_FLAG_READ_WRITE,
>> &h->interrupt_callback, options)) < 0)
>> goto fail;
>> - if ((ret = http_read_header(h, &new_location)) < 0)
>> - goto fail;
>> - if ((ret = ffurl_write(s->hd, header, strlen(header))) < 0)
>> - goto fail;
>> - return 0;
>> -
>> + if (s->listen == HTTP_ONESHOT) { /* single client */
>> + s->http_code = 200;
>> + // Setting the http_code to 200 should ensure that http_handshake() returns 0.
>> + ret = http_handshake(h);
>> + }
>> fail:
>> - handle_http_errors(h, ret);
>> av_dict_free(&s->chained_options);
>> return ret;
>> }
>> @@ -382,6 +443,26 @@ static int http_open(URLContext *h, const char *uri, int flags,
>> return ret;
>> }
>>
>> +static int http_accept(URLContext *s, URLContext **c)
>> +{
>> + int ret;
>> + HTTPContext *sc = s->priv_data;
>> + HTTPContext *cc;
>> + URLContext *sl = sc->hd;
>> + URLContext *cl;
>> + av_assert0(sc->listen);
>> + if ((ret = ffurl_alloc(c, s->filename, s->flags & (AVIO_FLAG_READ_WRITE | AVIO_FLAG_DIRECT),
>> + &sl->interrupt_callback)) < 0)
>> + goto fail;
>> + cc = (*c)->priv_data;
>> + if ((ret = ffurl_accept(sl, &cl)) < 0)
>> + goto fail;
>> + cc->hd = cl;
>
>> + cc->listen = HTTP_MULTI_CLIENT;
>
> This is used only once: does it make sense?
If cc->listen is not set, process_line() will act like a client and
send client responses to a client. That does not make sense.
The usage of HTTP_MULTI_CLIENT can be substituted by anything but 0
(after re-reading the code, I am fairly certain even 1 can be used),
and as it was mentioned before this is better handled by a separate
field and thus also becomes obsolete.
>
>> +fail:
>> + return ret;
>> +}
>> +
>> static int http_getc(HTTPContext *s)
>> {
>> int len;
>> @@ -597,6 +678,7 @@ static int process_line(URLContext *h, char *line, int line_count,
>> "(%s autodetected %s received)\n", auto_method, method);
>> return ff_http_averror(400, AVERROR(EIO));
>> }
>> + s->method = av_strdup(method);
>> }
>>
>> // HTTP resource
>> @@ -607,6 +689,7 @@ static int process_line(URLContext *h, char *line, int line_count,
>> p++;
>> *(p++) = '\0';
>> av_log(h, AV_LOG_TRACE, "Requested resource: %s\n", resource);
>> + s->resource = av_strdup(resource);
>>
>> // HTTP version
>> while (av_isspace(*p))
>> @@ -1346,6 +1429,8 @@ HTTP_CLASS(http);
>> URLProtocol ff_http_protocol = {
>> .name = "http",
>> .url_open2 = http_open,
>> + .url_accept = http_accept,
>> + .url_handshake = http_handshake,
>> .url_read = http_read,
>> .url_write = http_write,
>> .url_seek = http_seek,
>
> Regards,
>
> --
> Nicolas George
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
Regards,
Stephan
More information about the ffmpeg-devel
mailing list