feat: dynamic http buffers

master
Sean McBride 3 years ago
parent 42b42e0f1c
commit 05dde38ea2

@ -23,16 +23,21 @@ struct http_request {
int header_count;
struct http_query_param query_params[HTTP_MAX_QUERY_PARAM_COUNT];
int query_params_count;
int header_length;
char *body;
int body_length;
int body_read_length; /* How far we've read */
int body_length_read; /* Amount read into buffer from socket */
/* additional members for http-parser */
int length_parsed; /* Amount parsed */
bool last_was_value; /* http-parser flag used to help the http-parser callbacks differentiate between header
fields and values to know when to allocate a new header */
bool header_end; /* boolean flag set when header processing is complete */
bool message_begin; /* boolean flag set when body processing begins */
bool message_end; /* boolean flag set when body processing is complete */
/* Runtime state used by WASI */
int cursor; /* Sandbox cursor (offset from body pointer) */
};
void http_request_print(struct http_request *http_request);

@ -18,8 +18,7 @@ struct route {
/* HTTP State */
uint32_t relative_deadline_us;
uint64_t relative_deadline; /* cycles */
size_t max_request_size;
size_t max_response_size;
size_t response_size;
char *response_content_type;
struct admissions_info admissions_info;
};
@ -51,8 +50,7 @@ http_router_add_route(struct http_router *router, struct route_config *config, s
.module = module,
.relative_deadline_us = config->relative_deadline_us,
.relative_deadline = (uint64_t)config->relative_deadline_us * runtime_processor_speed_MHz,
.max_request_size = config->http_req_size,
.max_response_size = config->http_resp_size,
.response_size = config->http_resp_size,
.response_content_type = config->http_resp_content_type
};

@ -49,16 +49,30 @@ http_session_init(struct http_session *session, int socket_descriptor, const str
/* Set the session as the data the http-parser has access to */
session->http_parser.data = &session->http_request;
memset(&session->http_request, 0, sizeof(struct http_parser));
int rc;
rc = vec_u8_init(&session->request, HTTP_SESSION_DEFAULT_REQUEST_RESPONSE_SIZE);
if (rc < 0) return -1;
rc = vec_u8_init(&session->response, HTTP_SESSION_DEFAULT_REQUEST_RESPONSE_SIZE);
/* Defer allocating response until we've matched a route */
session->response.buffer = NULL;
return 0;
}
static inline int
http_session_init_response_buffer(struct http_session *session, size_t capacity)
{
assert(session != NULL);
assert(session->response.buffer == NULL);
assert(capacity > 0);
int rc = vec_u8_init(&session->response, HTTP_SESSION_DEFAULT_REQUEST_RESPONSE_SIZE);
if (rc < 0) {
vec_u8_deinit(&session->request);
return -1;
}
return 0;
}
@ -174,11 +188,50 @@ http_session_receive(struct http_session *session, void_cb on_eagain)
http_parser *parser = &session->http_parser;
const http_parser_settings *settings = http_parser_settings_get();
if (request->length == request->capacity) {
/* If header parsing is complete, resize using content-length */
if (session->http_request.header_end && session->http_request.body != NULL) {
int header_size = (uint8_t *)session->http_request.body - session->request.buffer;
assert(header_size > 0);
debuglog("Header Size: %d\n", header_size);
debuglog("Body Length (Content-Length): %d\n", session->http_request.body_length);
int required_size = header_size + session->http_request.body_length;
assert(required_size > 0);
if (required_size > request->capacity) {
debuglog("vec_u8_resize\n");
uint8_t *old_buffer = request->buffer;
if (vec_u8_resize(request, required_size) != 0) {
debuglog("Failed to resize request vector to %d bytes\n", required_size);
goto err_nobufs;
}
if (old_buffer != request->buffer) {
/* buffer moved, so invalidate to reparse */
memset(&session->http_request, 0, sizeof(struct http_request));
http_parser_init(&session->http_parser, HTTP_REQUEST);
/* Set the session as the data the http-parser has access to */
session->http_parser.data = &session->http_request;
}
}
} else if (request->length == request->capacity) {
/* Otherwise, we have a huge header and should just grow */
debuglog("vec_u8_grow\n");
uint8_t *old_buffer = request->buffer;
if (vec_u8_grow(request) != 0) {
debuglog("Ran out of Request Buffer before message end\n");
debuglog("Failed to grow request buffer\n");
goto err_nobufs;
}
if (old_buffer != request->buffer) {
/* buffer moved, so invalidate to reparse */
memset(&session->http_request, 0, sizeof(struct http_request));
http_parser_init(&session->http_parser, HTTP_REQUEST);
/* Set the session as the data the http-parser has access to */
session->http_parser.data = &session->http_request;
}
}
ssize_t bytes_received = recv(session->socket, &request->buffer[request->length],
@ -213,16 +266,18 @@ http_session_receive(struct http_session *session, void_cb on_eagain)
}
assert(bytes_received > 0);
request->length += bytes_received;
#ifdef LOG_HTTP_PARSER
debuglog("http_parser_execute(%p, %p, %p, %zu\n)", parser, settings,
&session->request.buffer[session->request.length], bytes_received);
#endif
size_t bytes_parsed = http_parser_execute(parser, settings,
(const char *)&request->buffer[request->length],
(size_t)bytes_received);
size_t bytes_parsed =
http_parser_execute(parser, settings,
(const char *)&request->buffer[session->http_request.length_parsed],
(size_t)request->length - session->http_request.length_parsed);
if (bytes_parsed != (size_t)bytes_received) {
if (bytes_parsed < (size_t)bytes_received) {
debuglog("Error: %s, Description: %s\n",
http_errno_name((enum http_errno)session->http_parser.http_errno),
http_errno_description((enum http_errno)session->http_parser.http_errno));
@ -231,7 +286,7 @@ http_session_receive(struct http_session *session, void_cb on_eagain)
goto err;
}
request->length += bytes_parsed;
session->http_request.length_parsed += bytes_parsed;
}
#ifdef LOG_HTTP_PARSER

@ -48,8 +48,7 @@ parse_json(const char *json_buf, ssize_t json_buf_size, struct tenant_config **t
}
i = tenant_config_vec_parse(tenant_config_vec, &tenant_config_vec_len, json_buf, tokens, i, total_tokens);
assert(i == total_tokens - 1);
if (i != total_tokens - 1) goto json_parse_err;
done:
return tenant_config_vec_len;

@ -10,7 +10,6 @@ struct route_config {
uint8_t admissions_percentile;
uint32_t expected_execution_us;
uint32_t relative_deadline_us;
uint32_t http_req_size;
uint32_t http_resp_size;
char *http_resp_content_type;
};
@ -31,7 +30,6 @@ route_config_print(struct route_config *config)
printf("[Route] Admissions Percentile: %hhu\n", config->admissions_percentile);
printf("[Route] Expected Execution (us): %u\n", config->expected_execution_us);
printf("[Route] Relative Deadline (us): %u\n", config->relative_deadline_us);
printf("[Route] HTTP Request Size: %u\n", config->http_req_size);
printf("[Route] HTTP Response Size: %u\n", config->http_resp_size);
printf("[Route] HTTP Response Content Type: %s\n", config->http_resp_content_type);
}

@ -13,16 +13,18 @@ enum
route_config_json_key_admissions_percentile,
route_config_json_key_expected_execution_us,
route_config_json_key_relative_deadline_us,
route_config_json_key_http_req_size,
route_config_json_key_http_resp_size,
route_config_json_key_http_resp_content_type,
route_config_json_key_len
};
static const char *route_config_json_keys[route_config_json_key_len] = {
"route", "path", "admissions-percentile", "expected-execution-us", "relative-deadline-us",
"http-req-size", "http-resp-size", "http-resp-content-type"
};
static const char *route_config_json_keys[route_config_json_key_len] = { "route",
"path",
"admissions-percentile",
"expected-execution-us",
"relative-deadline-us",
"http-resp-size",
"http-resp-content-type" };
static inline int
route_config_parse(struct route_config *config, const char *json_buf, jsmntok_t *tokens, size_t tokens_base,
@ -77,13 +79,6 @@ route_config_parse(struct route_config *config, const char *json_buf, jsmntok_t
route_config_json_keys[route_config_json_key_relative_deadline_us],
&config->relative_deadline_us);
if (rc < 0) return -1;
} else if (strcmp(key, route_config_json_keys[route_config_json_key_http_req_size]) == 0) {
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;
int rc = parse_uint32_t(tokens[i], json_buf,
route_config_json_keys[route_config_json_key_http_req_size],
&config->http_req_size);
if (rc < 0) return -1;
} else if (strcmp(key, route_config_json_keys[route_config_json_key_http_resp_size]) == 0) {
if (!has_valid_type(tokens[i], key, JSMN_PRIMITIVE, json_buf)) return -1;

@ -36,15 +36,6 @@ tenant_alloc(struct tenant_config *config)
if (route_config->path == 0) panic("path field is required\n");
if (route_config->route == 0) panic("route field is required\n");
if (route_config->http_req_size > RUNTIME_HTTP_REQUEST_SIZE_MAX)
panic("request_size must be between 0 and %u, was %u\n",
(uint32_t)RUNTIME_HTTP_REQUEST_SIZE_MAX, route_config->http_req_size);
if (route_config->http_resp_size > RUNTIME_HTTP_RESPONSE_SIZE_MAX)
panic("response-size must be between 0 and %u, was %u\n",
(uint32_t)RUNTIME_HTTP_RESPONSE_SIZE_MAX, route_config->http_resp_size);
if (route_config->relative_deadline_us > (uint32_t)RUNTIME_RELATIVE_DEADLINE_US_MAX)
panic("Relative-deadline-us must be between 0 and %u, was %u\n",
(uint32_t)RUNTIME_RELATIVE_DEADLINE_US_MAX, route_config->relative_deadline_us);

@ -1,3 +1,6 @@
#include <inttypes.h>
#include <limits.h>
#include "debuglog.h"
#include "http.h"
#include "http_request.h"
@ -189,6 +192,23 @@ http_parser_settings_on_header_end(http_parser *parser)
#endif
http_request->header_end = true;
/* Search header for content length */
for (int i = 0; i < http_request->header_count; i++) {
if (strncasecmp(http_request->headers[i].key, "content-length", strlen("content-length")) == 0) {
intmax_t temp = strtoimax(http_request->headers[i].value, NULL, 10);
if (temp < 0 || temp > INT_MAX) {
/* TODO: Improve error handling to not crash runtime */
fprintf(stderr, "Unable to parse int for key %.*s\n",
http_request->headers[i].key_length, http_request->headers[i].key);
assert(0);
} else {
http_request->body_length = (int)temp;
}
}
}
return 0;
}
@ -212,18 +232,20 @@ http_parser_settings_on_body(http_parser *parser, const char *at, size_t length)
assert(http_request->header_end);
assert(!http_request->message_end);
if (!http_request->body) {
if (http_request->body == NULL) {
#ifdef LOG_HTTP_PARSER
debuglog("Setting start of body!\n");
#endif
/* If this is the first invocation of the callback, just set */
http_request->body = (char *)at;
http_request->body_length = length;
http_request->body = (char *)at;
http_request->cursor = 0;
http_request->body_length_read = length;
} else {
#ifdef LOG_HTTP_PARSER
debuglog("Appending to existing body!\n");
#endif
http_request->body_length += length;
assert(http_request->body_length > 0);
http_request->body_length_read += length;
}
#ifdef LOG_HTTP_PARSER

@ -23,5 +23,5 @@ http_request_print(struct http_request *http_request)
putchar('\n');
}
printf("Body Length %d\n", http_request->body_length);
printf("Body Read Length %d\n", http_request->body_read_length);
printf("Body Length Read %d\n", http_request->body_length_read);
}

@ -660,20 +660,20 @@ wasi_snapshot_preview1_backing_fd_read(wasi_context_t *context, __wasi_fd_t fd,
if (fd == STDIN_FILENO) {
struct sandbox *current_sandbox = current_sandbox_get();
struct http_request *current_request = &current_sandbox->http->http_request;
int old_read = current_request->body_read_length;
int old_read = current_request->cursor;
int bytes_to_read = current_request->body_length - old_read;
for (int i = 0; i < iovs_len; i++) {
if (bytes_to_read == 0) goto done;
int amount_to_copy = iovs[i].buf_len > bytes_to_read ? bytes_to_read : iovs[i].buf_len;
memcpy(iovs[i].buf, current_request->body + current_request->body_read_length, amount_to_copy);
current_request->body_read_length += amount_to_copy;
bytes_to_read = current_request->body_length - current_request->body_read_length;
memcpy(iovs[i].buf, current_request->body + current_request->cursor, amount_to_copy);
current_request->cursor += amount_to_copy;
bytes_to_read = current_request->body_length - current_request->cursor;
}
done:
*nwritten_retptr = current_request->body_read_length - old_read;
*nwritten_retptr = current_request->cursor - old_read;
return __WASI_ERRNO_SUCCESS;
}
@ -791,6 +791,12 @@ wasi_snapshot_preview1_backing_fd_write(wasi_context_t *context, __wasi_fd_t fd,
for (size_t i = 0; i < iovs_len; i++) {
buffer_remaining = s->http->response.capacity - s->http->response.length;
if (buffer_remaining < iovs[i].buf_len) {
vec_u8_grow(&s->http->response);
buffer_remaining = s->http->response.capacity - s->http->response.length;
}
if (buffer_remaining == 0) {
*nwritten_retptr = s->http->response.length - old_response_len;
return __WASI_ERRNO_FBIG;

@ -170,8 +170,10 @@ listener_thread_main(void *dummy)
http_session_alloc(client_socket, (const struct sockaddr *)&client_address);
/* Read HTTP request */
/* TODO: Use epoll on block instead of busy looping */
int rc = 0;
while ((rc = http_session_receive(session, NULL)) == -3) debuglog("Loop\n");
while ((rc = http_session_receive(session, NULL)) == -3)
;
if (rc == -2) {
debuglog("Request size exceeded Buffer\n");

@ -94,6 +94,12 @@ sandbox_prepare_execution_environment(struct sandbox *sandbox)
int rc;
rc = http_session_init_response_buffer(sandbox->http, sandbox->route->response_size);
if (rc < 0) {
error_message = "failed to response buffer";
goto err_globals_allocation_failed;
}
rc = sandbox_allocate_globals(sandbox);
if (rc < 0) {
error_message = "failed to allocate globals";

@ -14,13 +14,13 @@ debug:
.PHONY: client
client:
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/airplane1.bmp" "localhost:10000/random"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/automobile1.bmp" "localhost:10000/random"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/bird1.bmp" "localhost:10000/random"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/cat1.bmp" "localhost:10000/random"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/deer1.bmp" "localhost:10000/random"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/dog1.bmp" "localhost:10000/random"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/frog1.bmp" "localhost:10000/random"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/horse1.bmp" "localhost:10000/random"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/ship1.bmp" "localhost:10000/random"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/truck1.bmp" "localhost:10000/random"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/airplane1.bmp" "localhost:10000/rand"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/automobile1.bmp" "localhost:10000/rand"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/bird1.bmp" "localhost:10000/rand"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/cat1.bmp" "localhost:10000/rand"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/deer1.bmp" "localhost:10000/rand"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/dog1.bmp" "localhost:10000/rand"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/frog1.bmp" "localhost:10000/rand"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/horse1.bmp" "localhost:10000/rand"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/ship1.bmp" "localhost:10000/rand"
@curl -H 'Expect:' -H "Content-Type: text/plain" --data-binary "@../../../applications/wasm_apps/CMSIS_5_NN/images/bmp/truck1.bmp" "localhost:10000/rand"

@ -9,7 +9,6 @@
"admissions-percentile": 70,
"expected-execution-us": 6000,
"relative-deadline-us": 20000,
"http-req-size": 1024,
"http-resp-size": 1024,
"http-resp-content-type": "text/plain"
},
@ -19,7 +18,6 @@
"expected-execution-us": 10000000,
"admissions-percentile": 70,
"relative-deadline-us": 20000000,
"http-req-size": 1024,
"http-resp-size": 1024,
"http-resp-content-type": "text/plain"
}
@ -35,10 +33,9 @@
"admissions-percentile": 70,
"expected-execution-us": 6000,
"relative-deadline-us": 20000,
"http-req-size": 1024,
"http-resp-size": 1024,
"http-resp-content-type": "text/plain"
}
]
}
]
]

@ -15,3 +15,14 @@ debug:
--eval-command="handle SIGPIPE noprint nostop" \
--eval-command="set pagination off" \
--eval-command="run spec.json"
client-small:
cat shrinking_man_small.jpg | http --output res/small.jpg --download :10000/resize_small
client-medium:
cat shrinking_man_medium.jpg | http --output res/medium.jpg --download :10000/resize_medium
client-large:
cat shrinking_man_large.jpg | http --output res/large.jpg --download :10000/resize_large
client: client-small client-medium client-large

@ -26,7 +26,7 @@ run_functional_tests() {
ext="$RANDOM"
# Small
if curl -H 'Expect:' -H "Content-Type: image/jpg" --data-binary "@shrinking_man_small.jpg" --output "/tmp/result_${ext}_small.jpg" "${hostname}:10000" 2> /dev/null 1> /dev/null; then
if curl -H 'Expect:' -H "Content-Type: image/jpg" --data-binary "@shrinking_man_small.jpg" --output "/tmp/result_${ext}_small.jpg" "${hostname}:10000/resize_small" 2> /dev/null 1> /dev/null; then
pixel_differences="$(compare -identify -metric AE "/tmp/result_${ext}_small.jpg" expected_result_small.jpg null: 2>&1 > /dev/null)"
rm -f "/tmp/result_${ext}_small.jpg"
if [[ "$pixel_differences" != "0" ]]; then
@ -40,7 +40,7 @@ run_functional_tests() {
fi
# Medium
if curl -H 'Expect:' -H "Content-Type: image/jpg" --data-binary "@shrinking_man_medium.jpg" --output "/tmp/result_${ext}_medium.jpg" "${hostname}:10001" 2> /dev/null 1> /dev/null; then
if curl -H 'Expect:' -H "Content-Type: image/jpg" --data-binary "@shrinking_man_medium.jpg" --output "/tmp/result_${ext}_medium.jpg" "${hostname}:10000/resize_medium" 2> /dev/null 1> /dev/null; then
pixel_differences="$(compare -identify -metric AE "/tmp/result_${ext}_medium.jpg" expected_result_medium.jpg null: 2>&1 > /dev/null)"
rm -f "/tmp/result_${ext}_medium.jpg"
if [[ "$pixel_differences" != "0" ]]; then
@ -54,7 +54,7 @@ run_functional_tests() {
fi
# Large
if curl -H 'Expect:' -H "Content-Type: image/jpg" --data-binary "@shrinking_man_large.jpg" --output "/tmp/result_${ext}_large.jpg" "${hostname}:10002" 2> /dev/null 1> /dev/null; then
if curl -H 'Expect:' -H "Content-Type: image/jpg" --data-binary "@shrinking_man_large.jpg" --output "/tmp/result_${ext}_large.jpg" "${hostname}:10000/resize_large" 2> /dev/null 1> /dev/null; then
pixel_differences="$(compare -identify -metric AE "/tmp/result_${ext}_large.jpg" expected_result_large.jpg null: 2>&1 > /dev/null)"
rm -f "/tmp/result_${ext}_large.jpg"
if [[ "$pixel_differences" != "0" ]]; then
@ -95,7 +95,7 @@ run_perf_tests() {
done
((batch_id++))
hey -disable-compression -disable-keepalive -disable-redirects -n $batch_size -c 1 -cpus 1 -t 0 -o csv -m GET -D "shrinking_man_${workload}.jpg" "http://${hostname}:${port[$workload]}" > "$results_directory/${workload}_${batch_id}.csv" 2> /dev/null &
hey -disable-compression -disable-keepalive -disable-redirects -n $batch_size -c 1 -cpus 1 -t 0 -o csv -m GET -D "shrinking_man_${workload}.jpg" "http://${hostname}:10000${route[$workload]}" > "$results_directory/${workload}_${batch_id}.csv" 2> /dev/null &
done
pids=$(pgrep hey | tr '\n' ' ')
[[ -n $pids ]] && wait -f $pids
@ -160,10 +160,10 @@ experiment_client() {
validate_dependencies curl
declare -ar workloads=(small medium large)
declare -Ar port=(
[small]=10000
[medium]=10001
[large]=10002
declare -Ar route=(
[small]=/resize_small
[medium]=/resize_medium
[large]=/resize_large
)
framework_init "$@"

@ -1,32 +1,32 @@
[
{
"name": "resize_small",
"path": "resize_image.wasm.so",
"name": "gwu",
"port": 10000,
"expected-execution-us": 5000,
"relative-deadline-us": 50000,
"http-req-size": 1024000,
"http-resp-size": 1024000,
"http-resp-content-type": "image/png"
},
{
"name": "resize_medium",
"path": "resize_image.wasm.so",
"port": 10001,
"expected-execution-us": 5000,
"relative-deadline-us": 50000,
"http-req-size": 1024000,
"http-resp-size": 1024000,
"http-resp-content-type": "image/png"
},
{
"name": "resize_large",
"path": "resize_image.wasm.so",
"port": 10002,
"expected-execution-us": 5000,
"relative-deadline-us": 50000,
"http-req-size": 1524000,
"http-resp-size": 1524000,
"http-resp-content-type": "image/png"
"routes": [
{
"route": "/resize_small",
"path": "resize_image.wasm.so",
"expected-execution-us": 5000,
"relative-deadline-us": 50000,
"http-resp-size": 1024000,
"http-resp-content-type": "image/png"
},
{
"route": "/resize_medium",
"path": "resize_image.wasm.so",
"expected-execution-us": 5000,
"relative-deadline-us": 50000,
"http-resp-size": 1024000,
"http-resp-content-type": "image/png"
},
{
"route": "/resize_large",
"path": "resize_image.wasm.so",
"expected-execution-us": 5000,
"relative-deadline-us": 50000,
"http-resp-size": 1524000,
"http-resp-content-type": "image/png"
}
]
}
]
]
Loading…
Cancel
Save