mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-26 16:06:52 +00:00
validate: Implement a 'http-request' action type
Which is useable with our own HTTP server Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/8128>
This commit is contained in:
parent
7e1fd3b069
commit
294f1165fa
3 changed files with 348 additions and 0 deletions
|
@ -0,0 +1,271 @@
|
||||||
|
/* GStreamer
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Igalia S.L
|
||||||
|
* Author: Thibault Saunier <tsaunier@igalia.com>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Library General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library 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
|
||||||
|
* Library General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Library General Public
|
||||||
|
* License along with this library; if not, write to the
|
||||||
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||||
|
* Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
#include <glib.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#include <gst/gst.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
gboolean run_http_request (const GstStructure * args, GError ** error);
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
const gchar *method;
|
||||||
|
const gchar *host;
|
||||||
|
gint port;
|
||||||
|
const gchar *path;
|
||||||
|
const gchar *content_type;
|
||||||
|
const gchar *body;
|
||||||
|
gsize body_length;
|
||||||
|
} HttpRequestParams;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
gchar *body;
|
||||||
|
gsize body_length;
|
||||||
|
gint status_code;
|
||||||
|
} HttpResponse;
|
||||||
|
|
||||||
|
static void
|
||||||
|
http_response_clear (HttpResponse * response)
|
||||||
|
{
|
||||||
|
if (response) {
|
||||||
|
g_clear_pointer (&response->body, g_free);
|
||||||
|
response->body_length = 0;
|
||||||
|
response->status_code = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
parse_uri (const gchar * uri, gchar ** host, gint * port, gchar ** path,
|
||||||
|
GError ** error)
|
||||||
|
{
|
||||||
|
GUri *guri;
|
||||||
|
gboolean ret = FALSE;
|
||||||
|
|
||||||
|
guri = g_uri_parse (uri, G_URI_FLAGS_NONE, error);
|
||||||
|
if (!guri)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
*host = g_strdup (g_uri_get_host (guri));
|
||||||
|
if (!*host) {
|
||||||
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
|
||||||
|
"Invalid URI: missing host");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
*port = g_uri_get_port (guri);
|
||||||
|
if (*port == -1) {
|
||||||
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
|
||||||
|
"Invalid URI: missing port");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
*path = g_strdup (g_uri_get_path (guri));
|
||||||
|
if (!*path) {
|
||||||
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
|
||||||
|
"Invalid URI: missing path");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = TRUE;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (!ret) {
|
||||||
|
g_clear_pointer (host, g_free);
|
||||||
|
g_clear_pointer (path, g_free);
|
||||||
|
}
|
||||||
|
g_uri_unref (guri);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
send_http_request (const HttpRequestParams * params, HttpResponse * response,
|
||||||
|
GError ** error)
|
||||||
|
{
|
||||||
|
GSocketClient *client = NULL;
|
||||||
|
GSocketConnection *connection = NULL;
|
||||||
|
GOutputStream *output_stream;
|
||||||
|
GInputStream *input_stream;
|
||||||
|
GString *request = NULL;
|
||||||
|
gchar *host_port = NULL;
|
||||||
|
gboolean success = FALSE;
|
||||||
|
GString *response_str = NULL;
|
||||||
|
gchar buffer[4096];
|
||||||
|
gssize bytes_read;
|
||||||
|
|
||||||
|
// Construct request without leading newlines
|
||||||
|
request = g_string_new (NULL);
|
||||||
|
g_string_append_printf (request,
|
||||||
|
"%s %s HTTP/1.1\r\n"
|
||||||
|
"Host: %s:%d\r\n"
|
||||||
|
"Content-Type: %s\r\n",
|
||||||
|
params->method,
|
||||||
|
params->path, params->host, params->port, params->content_type);
|
||||||
|
|
||||||
|
if (params->body) {
|
||||||
|
g_string_append_printf (request,
|
||||||
|
"Content-Length: %zu\r\n\r\n%s\r\n", params->body_length, params->body);
|
||||||
|
} else {
|
||||||
|
g_string_append (request, "\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create client and establish connection
|
||||||
|
client = g_socket_client_new ();
|
||||||
|
host_port = g_strdup_printf ("%s:%d", params->host, params->port);
|
||||||
|
connection = g_socket_client_connect_to_host (client,
|
||||||
|
host_port, params->port, NULL, error);
|
||||||
|
|
||||||
|
if (!connection) {
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
output_stream = g_io_stream_get_output_stream (G_IO_STREAM (connection));
|
||||||
|
if (!g_output_stream_write_all (output_stream,
|
||||||
|
request->str, request->len, NULL, NULL, error)) {
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
// Read response
|
||||||
|
response_str = g_string_new (NULL);
|
||||||
|
input_stream = g_io_stream_get_input_stream (G_IO_STREAM (connection));
|
||||||
|
while ((bytes_read = g_input_stream_read (input_stream,
|
||||||
|
buffer, sizeof (buffer), NULL, error)) > 0) {
|
||||||
|
g_string_append_len (response_str, buffer, bytes_read);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*error) {
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
// Parse HTTP response
|
||||||
|
gchar **lines = g_strsplit (response_str->str, "\r\n", -1);
|
||||||
|
if (lines && lines[0]) {
|
||||||
|
gchar **status_parts = g_strsplit (lines[0], " ", 3);
|
||||||
|
if (status_parts && status_parts[1]) {
|
||||||
|
response->status_code = atoi (status_parts[1]);
|
||||||
|
}
|
||||||
|
g_strfreev (status_parts);
|
||||||
|
|
||||||
|
gint i;
|
||||||
|
for (i = 0; lines[i] != NULL; i++) {
|
||||||
|
if (strlen (lines[i]) == 0 && lines[i + 1] != NULL) {
|
||||||
|
response->body = g_strdup (lines[i + 1]);
|
||||||
|
response->body_length = strlen (response->body);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_strfreev (lines);
|
||||||
|
|
||||||
|
// Check if the status code indicates success (2xx)
|
||||||
|
if (response->status_code < 200 || response->status_code >= 300) {
|
||||||
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||||
|
"HTTP request failed with status %d: %s",
|
||||||
|
response->status_code,
|
||||||
|
response->body ? response->body : "No error message");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
success = TRUE;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
g_clear_pointer (&host_port, g_free);
|
||||||
|
if (request)
|
||||||
|
g_string_free (request, TRUE);
|
||||||
|
if (response_str)
|
||||||
|
g_string_free (response_str, TRUE);
|
||||||
|
g_clear_object (&connection);
|
||||||
|
g_clear_object (&client);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
run_http_request (const GstStructure * args, GError ** error)
|
||||||
|
{
|
||||||
|
const gchar *uri;
|
||||||
|
const gchar *method;
|
||||||
|
const gchar *body;
|
||||||
|
const gchar *headers;
|
||||||
|
gchar *host = NULL;
|
||||||
|
gchar *path = NULL;
|
||||||
|
gint port;
|
||||||
|
HttpRequestParams params = { 0 };
|
||||||
|
HttpResponse response = { 0 };
|
||||||
|
gboolean ret = FALSE;
|
||||||
|
|
||||||
|
g_return_val_if_fail (args != NULL, FALSE);
|
||||||
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||||||
|
|
||||||
|
// Get required parameters
|
||||||
|
uri = gst_structure_get_string (args, "uri");
|
||||||
|
if (!uri) {
|
||||||
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
|
||||||
|
"Missing 'uri' parameter");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
method = gst_structure_get_string (args, "method");
|
||||||
|
if (!method) {
|
||||||
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
|
||||||
|
"Missing 'method' parameter");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
// Parse URI
|
||||||
|
if (!parse_uri (uri, &host, &port, &path, error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
// Get optional parameters
|
||||||
|
body = gst_structure_get_string (args, "body");
|
||||||
|
if (!gst_structure_has_field (args, "headers"))
|
||||||
|
headers = "application/json";
|
||||||
|
else
|
||||||
|
headers = gst_structure_get_string (args, "headers");
|
||||||
|
|
||||||
|
// Prepare request parameters
|
||||||
|
params.method = method;
|
||||||
|
params.host = host;
|
||||||
|
params.port = port;
|
||||||
|
params.path = path;
|
||||||
|
params.content_type = headers;
|
||||||
|
params.body = body;
|
||||||
|
params.body_length = body ? strlen (body) : 0;
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
ret = send_http_request (¶ms, &response, error);
|
||||||
|
|
||||||
|
const gchar *expected_response =
|
||||||
|
gst_structure_get_string (args, "expected-response");
|
||||||
|
if (expected_response) {
|
||||||
|
if (g_strcmp0 (response.body, expected_response) != 0) {
|
||||||
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||||
|
"Expected response '%s' but got '%s'",
|
||||||
|
expected_response,
|
||||||
|
response.body ? response.body : "No error message");
|
||||||
|
ret = FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Cleanup
|
||||||
|
g_free (host);
|
||||||
|
g_free (path);
|
||||||
|
http_response_clear (&response);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
|
@ -66,6 +66,8 @@
|
||||||
#include <gst/validate/gst-validate-override-registry.h>
|
#include <gst/validate/gst-validate-override-registry.h>
|
||||||
#include <gst/validate/gst-validate-pipeline-monitor.h>
|
#include <gst/validate/gst-validate-pipeline-monitor.h>
|
||||||
|
|
||||||
|
extern gboolean run_http_request (const GstStructure * args, GError ** error);
|
||||||
|
|
||||||
#define GST_VALIDATE_SCENARIO_DIRECTORY "scenarios"
|
#define GST_VALIDATE_SCENARIO_DIRECTORY "scenarios"
|
||||||
|
|
||||||
#define DEFAULT_SEEK_TOLERANCE (1 * GST_MSECOND) /* tolerance seek interval
|
#define DEFAULT_SEEK_TOLERANCE (1 * GST_MSECOND) /* tolerance seek interval
|
||||||
|
@ -7523,6 +7525,25 @@ done:
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static GstValidateExecuteActionReturn
|
||||||
|
_execute_http_request (GstValidateScenario * scenario,
|
||||||
|
GstValidateAction * action)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
GstValidateExecuteActionReturn ret = GST_VALIDATE_EXECUTE_ACTION_OK;
|
||||||
|
|
||||||
|
if (!run_http_request (action->structure, &error)) {
|
||||||
|
GST_VALIDATE_REPORT_ACTION (scenario, action,
|
||||||
|
SCENARIO_ACTION_EXECUTION_ERROR,
|
||||||
|
"Failed to execute HTTP request: %s",
|
||||||
|
error ? error->message : "Unknown error");
|
||||||
|
g_clear_error (&error);
|
||||||
|
ret = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static GstValidateExecuteActionReturn
|
static GstValidateExecuteActionReturn
|
||||||
_execute_start_http_server (GstValidateScenario * scenario,
|
_execute_start_http_server (GstValidateScenario * scenario,
|
||||||
GstValidateAction * action)
|
GstValidateAction * action)
|
||||||
|
@ -9150,6 +9171,61 @@ register_action_types (void)
|
||||||
" using the `run-on-sub-pipeline` action type.",
|
" using the `run-on-sub-pipeline` action type.",
|
||||||
GST_VALIDATE_ACTION_TYPE_NONE);
|
GST_VALIDATE_ACTION_TYPE_NONE);
|
||||||
|
|
||||||
|
REGISTER_ACTION_TYPE("http-request", _execute_http_request,
|
||||||
|
((GstValidateActionParameter[]) {
|
||||||
|
{
|
||||||
|
.name = "uri",
|
||||||
|
.description = "The URI to send the request to",
|
||||||
|
.mandatory = TRUE,
|
||||||
|
.types = "string",
|
||||||
|
NULL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "method",
|
||||||
|
.description = "The HTTP method to use (GET, POST, etc)",
|
||||||
|
.mandatory = TRUE,
|
||||||
|
.types = "string",
|
||||||
|
NULL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "body",
|
||||||
|
.description = "The request body (for POST/PUT requests)",
|
||||||
|
.mandatory = FALSE,
|
||||||
|
.types = "string",
|
||||||
|
NULL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "headers",
|
||||||
|
.description = "The request headers as Content-Type",
|
||||||
|
.mandatory = FALSE,
|
||||||
|
.types = "string",
|
||||||
|
.def = "application/json",
|
||||||
|
NULL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "expected-response",
|
||||||
|
.description = "The exact expected response as a string",
|
||||||
|
.mandatory = FALSE,
|
||||||
|
.types = "string",
|
||||||
|
NULL
|
||||||
|
},
|
||||||
|
{NULL}
|
||||||
|
}),
|
||||||
|
"Send an HTTP request to a server.\n"
|
||||||
|
"\n"
|
||||||
|
"NOTE: This is not expected to be usebale on any server but the\n"
|
||||||
|
"one started with the `start-http-server` action.\n"
|
||||||
|
"\n"
|
||||||
|
"Example:\n"
|
||||||
|
"``` jproperties\n"
|
||||||
|
"http-request,\n"
|
||||||
|
" uri=\"http://127.0.0.1:$(http_server_port)/test\",\n"
|
||||||
|
" method=POST,\n"
|
||||||
|
" body=\"test data\",\n"
|
||||||
|
" headers=\"text/plain\"\n"
|
||||||
|
"```\n",
|
||||||
|
GST_VALIDATE_ACTION_TYPE_NONE);
|
||||||
|
|
||||||
REGISTER_ACTION_TYPE("start-http-server", _execute_start_http_server,
|
REGISTER_ACTION_TYPE("start-http-server", _execute_start_http_server,
|
||||||
((GstValidateActionParameter[]) {
|
((GstValidateActionParameter[]) {
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
runner_file = files('gst-validate-runner.c')
|
runner_file = files('gst-validate-runner.c')
|
||||||
gstvalidate_sources = files(
|
gstvalidate_sources = files(
|
||||||
'gst-validate-reporter.c',
|
'gst-validate-reporter.c',
|
||||||
|
'gst-validate-http-actions.c',
|
||||||
'gst-validate-mockdecryptor.c',
|
'gst-validate-mockdecryptor.c',
|
||||||
'gst-validate-monitor.c',
|
'gst-validate-monitor.c',
|
||||||
'gst-validate-element-monitor.c',
|
'gst-validate-element-monitor.c',
|
||||||
|
|
Loading…
Reference in a new issue