tool_writeout: add new writeout variable, %{num_headers}

This variable gives the number of headers.

Closes #5947
This commit is contained in:
anio 2020-09-09 15:05:49 +00:00 committed by Daniel Stenberg
parent 4e66207c58
commit 0c1e767e83
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
14 changed files with 191 additions and 23 deletions

View File

@ -503,7 +503,7 @@ Client
status and exits. status and exits.
When the operation is done, the `ourWriteOut()` function in `src/writeout.c` When the operation is done, the `ourWriteOut()` function in `src/writeout.c`
may be called to report about the operation. That function is using the may be called to report about the operation. That function is mostly using the
`curl_easy_getinfo()` function to extract useful information from the curl `curl_easy_getinfo()` function to extract useful information from the curl
session. session.

View File

@ -67,6 +67,10 @@ The http method used in the most recent HTTP request (Added in 7.72.0)
.B num_connects .B num_connects
Number of new connects made in the recent transfer. (Added in 7.12.3) Number of new connects made in the recent transfer. (Added in 7.12.3)
.TP .TP
.B num_headers
The number of response headers in the most recent request (restarted at each
redirect). Note that the status line IS NOT a header. (Added in 7.73.0)
.TP
.B num_redirects .B num_redirects
Number of redirects that were followed in the request. (Added in 7.12.3) Number of redirects that were followed in the request. (Added in 7.12.3)
.TP .TP

View File

@ -179,7 +179,17 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
if(!outs->stream && !tool_create_output_file(outs, per->config)) if(!outs->stream && !tool_create_output_file(outs, per->config))
return failure; return failure;
} }
if(hdrcbdata->config->writeout) {
char *value = memchr(ptr, ':', cb);
if(value) {
if(per->was_last_header_empty)
per->num_headers = 0;
per->was_last_header_empty = FALSE;
per->num_headers++;
}
else if(ptr[0] == '\r' || ptr[0] == '\n')
per->was_last_header_empty = TRUE;
}
if(hdrcbdata->config->show_headers && if(hdrcbdata->config->show_headers &&
(protocol & (protocol &
(CURLPROTO_HTTP|CURLPROTO_HTTPS|CURLPROTO_RTSP|CURLPROTO_FILE))) { (CURLPROTO_HTTP|CURLPROTO_HTTPS|CURLPROTO_RTSP|CURLPROTO_FILE))) {

View File

@ -622,7 +622,7 @@ static CURLcode post_per_transfer(struct GlobalConfig *global,
fputs("\n", per->progressbar.out); fputs("\n", per->progressbar.out);
if(config->writeout) if(config->writeout)
ourWriteOut(per->curl, &per->outs, config->writeout); ourWriteOut(per->curl, per, config->writeout);
/* Close the outs file */ /* Close the outs file */
if(outs->fopened && outs->stream) { if(outs->fopened && outs->stream) {

View File

@ -51,6 +51,8 @@ struct per_transfer {
struct OutStruct etag_save; struct OutStruct etag_save;
struct InStruct input; struct InStruct input;
struct HdrCbData hdrcbdata; struct HdrCbData hdrcbdata;
long num_headers;
bool was_last_header_empty;
char errorbuffer[CURL_ERROR_SIZE]; char errorbuffer[CURL_ERROR_SIZE];
bool added; /* set TRUE when added to the multi handle */ bool added; /* set TRUE when added to the multi handle */

View File

@ -38,6 +38,8 @@ static const struct writeoutvar variables[] = {
CURLINFO_RESPONSE_CODE, JSON_LONG}, CURLINFO_RESPONSE_CODE, JSON_LONG},
{"response_code", VAR_HTTP_CODE, 0, {"response_code", VAR_HTTP_CODE, 0,
CURLINFO_RESPONSE_CODE, JSON_LONG}, CURLINFO_RESPONSE_CODE, JSON_LONG},
{"num_headers", VAR_NUM_HEADERS, 0,
0, JSON_LONG},
{"http_connect", VAR_HTTP_CODE_PROXY, 0, {"http_connect", VAR_HTTP_CODE_PROXY, 0,
CURLINFO_HTTP_CONNECTCODE, JSON_LONG}, CURLINFO_HTTP_CONNECTCODE, JSON_LONG},
{"time_total", VAR_TOTAL_TIME, 0, {"time_total", VAR_TOTAL_TIME, 0,
@ -104,7 +106,7 @@ static const struct writeoutvar variables[] = {
0, JSON_NONE} 0, JSON_NONE}
}; };
void ourWriteOut(CURL *curl, struct OutStruct *outs, const char *writeinfo) void ourWriteOut(CURL *curl, struct per_transfer *per, const char *writeinfo)
{ {
FILE *stream = stdout; FILE *stream = stdout;
const char *ptr = writeinfo; const char *ptr = writeinfo;
@ -156,6 +158,9 @@ void ourWriteOut(CURL *curl, struct OutStruct *outs, const char *writeinfo)
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &longinfo)) curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &longinfo))
fprintf(stream, "%03ld", longinfo); fprintf(stream, "%03ld", longinfo);
break; break;
case VAR_NUM_HEADERS:
fprintf(stream, "%ld", per->num_headers);
break;
case VAR_HTTP_CODE_PROXY: case VAR_HTTP_CODE_PROXY:
if(CURLE_OK == if(CURLE_OK ==
curl_easy_getinfo(curl, CURLINFO_HTTP_CONNECTCODE, curl_easy_getinfo(curl, CURLINFO_HTTP_CONNECTCODE,
@ -275,14 +280,14 @@ void ourWriteOut(CURL *curl, struct OutStruct *outs, const char *writeinfo)
fprintf(stream, "%ld", longinfo); fprintf(stream, "%ld", longinfo);
break; break;
case VAR_EFFECTIVE_FILENAME: case VAR_EFFECTIVE_FILENAME:
if(outs->filename) if(per->outs.filename)
fprintf(stream, "%s", outs->filename); fputs(per->outs.filename, stream);
break; break;
case VAR_PRIMARY_IP: case VAR_PRIMARY_IP:
if(CURLE_OK == if(CURLE_OK ==
curl_easy_getinfo(curl, CURLINFO_PRIMARY_IP, curl_easy_getinfo(curl, CURLINFO_PRIMARY_IP,
&stringp)) &stringp))
fprintf(stream, "%s", stringp); fputs(stringp, stream);
break; break;
case VAR_PRIMARY_PORT: case VAR_PRIMARY_PORT:
if(CURLE_OK == if(CURLE_OK ==
@ -294,7 +299,7 @@ void ourWriteOut(CURL *curl, struct OutStruct *outs, const char *writeinfo)
if(CURLE_OK == if(CURLE_OK ==
curl_easy_getinfo(curl, CURLINFO_LOCAL_IP, curl_easy_getinfo(curl, CURLINFO_LOCAL_IP,
&stringp)) &stringp))
fprintf(stream, "%s", stringp); fputs(stringp, stream);
break; break;
case VAR_LOCAL_PORT: case VAR_LOCAL_PORT:
if(CURLE_OK == if(CURLE_OK ==
@ -329,7 +334,7 @@ void ourWriteOut(CURL *curl, struct OutStruct *outs, const char *writeinfo)
if(CURLE_OK == if(CURLE_OK ==
curl_easy_getinfo(curl, CURLINFO_SCHEME, curl_easy_getinfo(curl, CURLINFO_SCHEME,
&stringp)) &stringp))
fprintf(stream, "%s", stringp); fputs(stringp, stream);
break; break;
case VAR_STDOUT: case VAR_STDOUT:
stream = stdout; stream = stdout;
@ -338,7 +343,7 @@ void ourWriteOut(CURL *curl, struct OutStruct *outs, const char *writeinfo)
stream = stderr; stream = stderr;
break; break;
case VAR_JSON: case VAR_JSON:
ourWriteOutJSON(variables, curl, outs, stream); ourWriteOutJSON(variables, curl, per, stream);
default: default:
break; break;
} }

View File

@ -22,6 +22,7 @@
* *
***************************************************************************/ ***************************************************************************/
#include "tool_setup.h" #include "tool_setup.h"
#include "tool_operate.h"
typedef enum { typedef enum {
VAR_NONE, /* must be the first */ VAR_NONE, /* must be the first */
@ -38,6 +39,7 @@ typedef enum {
VAR_HTTP_CODE, VAR_HTTP_CODE,
VAR_HTTP_CODE_PROXY, VAR_HTTP_CODE_PROXY,
VAR_HEADER_SIZE, VAR_HEADER_SIZE,
VAR_NUM_HEADERS,
VAR_REQUEST_SIZE, VAR_REQUEST_SIZE,
VAR_EFFECTIVE_METHOD, VAR_EFFECTIVE_METHOD,
VAR_EFFECTIVE_URL, VAR_EFFECTIVE_URL,
@ -80,6 +82,6 @@ struct writeoutvar {
jsontype jsontype; jsontype jsontype;
}; };
void ourWriteOut(CURL *curl, struct OutStruct *outs, const char *writeinfo); void ourWriteOut(CURL *curl, struct per_transfer *per, const char *writeinfo);
#endif /* HEADER_CURL_TOOL_WRITEOUT_H */ #endif /* HEADER_CURL_TOOL_WRITEOUT_H */

View File

@ -103,13 +103,20 @@ static int writeString(FILE *str, CURL *curl, const char *key, CURLINFO ci)
return 0; return 0;
} }
static int writeLong(FILE *str, CURL *curl, const char *key, CURLINFO ci) static int writeLong(FILE *str, CURL *curl, const char *key, CURLINFO ci,
struct per_transfer *per, const struct writeoutvar *wovar)
{ {
long val = 0; if(wovar->id == VAR_NUM_HEADERS) {
if(CURLE_OK == curl_easy_getinfo(curl, ci, &val)) { fprintf(str, "\"%s\":%ld", key, per->num_headers);
fprintf(str, "\"%s\":%ld", key, val);
return 1; return 1;
} }
else {
long val = 0;
if(CURLE_OK == curl_easy_getinfo(curl, ci, &val)) {
fprintf(str, "\"%s\":%ld", key, val);
return 1;
}
}
return 0; return 0;
} }
@ -149,12 +156,13 @@ static int writeVersion(FILE *str, CURL *curl, const char *key, CURLINFO ci)
} }
void ourWriteOutJSON(const struct writeoutvar mappings[], CURL *curl, void ourWriteOutJSON(const struct writeoutvar mappings[], CURL *curl,
struct OutStruct *outs, FILE *stream) struct per_transfer *per, FILE *stream)
{ {
int i; int i;
fputs("{", stream); fputs("{", stream);
for(i = 0; mappings[i].name != NULL; i++) { for(i = 0; mappings[i].name != NULL; i++) {
const struct writeoutvar *wovar = &mappings[i];
const char *name = mappings[i].name; const char *name = mappings[i].name;
CURLINFO cinfo = mappings[i].cinfo; CURLINFO cinfo = mappings[i].cinfo;
int ok = 0; int ok = 0;
@ -168,7 +176,7 @@ void ourWriteOutJSON(const struct writeoutvar mappings[], CURL *curl,
ok = writeString(stream, curl, name, cinfo); ok = writeString(stream, curl, name, cinfo);
break; break;
case JSON_LONG: case JSON_LONG:
ok = writeLong(stream, curl, name, cinfo); ok = writeLong(stream, curl, name, cinfo, per, wovar);
break; break;
case JSON_OFFSET: case JSON_OFFSET:
ok = writeOffset(stream, curl, name, cinfo); ok = writeOffset(stream, curl, name, cinfo);
@ -177,7 +185,7 @@ void ourWriteOutJSON(const struct writeoutvar mappings[], CURL *curl,
ok = writeTime(stream, curl, name, cinfo); ok = writeTime(stream, curl, name, cinfo);
break; break;
case JSON_FILENAME: case JSON_FILENAME:
ok = writeFilename(stream, name, outs->filename); ok = writeFilename(stream, name, per->outs.filename);
break; break;
case JSON_VERSION: case JSON_VERSION:
ok = writeVersion(stream, curl, name, cinfo); ok = writeVersion(stream, curl, name, cinfo);

View File

@ -24,7 +24,7 @@
#include "tool_setup.h" #include "tool_setup.h"
#include "tool_writeout.h" #include "tool_writeout.h"
void ourWriteOutJSON(const struct writeoutvar mappings[], void ourWriteOutJSON(const struct writeoutvar mappings[], CURL *curl,
CURL *curl, struct OutStruct *outs, FILE *stream); struct per_transfer *per, FILE *stream);
#endif /* HEADER_CURL_TOOL_WRITEOUT_H */ #endif /* HEADER_CURL_TOOL_WRITEOUT_H */

View File

@ -225,4 +225,4 @@ test2080 \
test2100 \ test2100 \
\ \
test3000 test3001 test3002 test3003 test3004 test3005 test3006 test3007 \ test3000 test3001 test3002 test3003 test3004 test3005 test3006 test3007 \
test3008 test3009 test3010 test3011 test3012 test3013 test3008 test3009 test3010 test3011 test3012 test3013 test3014 test3015

View File

@ -26,7 +26,7 @@ http
</server> </server>
<name> <name>
Check if %{scheme} returns HTTP Check if %{http_version} returns 1.1
</name> </name>
<command> <command>
http://%HOSTIP:%HTTPPORT/1439 --write-out '%{http_version}' http://%HOSTIP:%HTTPPORT/1439 --write-out '%{http_version}'

58
tests/data/test3014 Normal file
View File

@ -0,0 +1,58 @@
<testcase>
<info>
<keywords>
HTTP
--write-out
</keywords>
</info>
# Server-side
<reply>
<data nocheck="yes">
HTTP/1.1 200 OK
Date: Thu, 09 Nov 2010 14:49:00 GMT
Content-Length: 9
Connection: close
Content-Type: text/plain
testdata
</data>
</reply>
# Client-side
<client>
<server>
http
</server>
<name>
Check if %{num_headers} returns correct number of headers
</name>
<command>
http://%HOSTIP:%HTTPPORT/1439 --write-out '%{num_headers}'
</command>
</client>
# Verify data
<verify>
<stdout nonewline="yes">
HTTP/1.1 200 OK
Date: Thu, 09 Nov 2010 14:49:00 GMT
Content-Length: 9
Connection: close
Content-Type: text/plain
testdata
4
</stdout>
<protocol>
GET /1439 HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Accept: */*
</protocol>
<strip>
^User-Agent:.*
</strip>
</verify>
</testcase>

79
tests/data/test3015 Normal file
View File

@ -0,0 +1,79 @@
<testcase>
<info>
<keywords>
HTTP
HTTP GET
followlocation
chunked Transfer-Encoding
--write-out
</keywords>
</info>
#
# Server-side
<reply>
<data nocheck="yes">
HTTP/1.1 302 OK
Date: Sun, 13 Sep 2020 15:00 GMT
Content-Length: 8
Connection: close
Content-Type: text/plain
Location: ./30150001
monster
</data>
<data1 nocheck="yes">
HTTP/1.1 200 OK
Date: Sun, 13 Sep 2020 15:00 GMT
Transfer-Encoding: chunked
Connection: close
Content-Type: text/plain; charset=us-ascii
0007
bigger
0008
monster
0
</data1>
</reply>
#
# Client-side
<client>
<server>
http
</server>
<name>
HTTP GET -w num_headers with redirected fetch (2 connects)
</name>
<command>
http://%HOSTIP:%HTTPPORT/3015 -w "%{num_headers}\n" -L -o/dev/null
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<strip>
^User-Agent:.*
</strip>
<protocol>
GET /3015 HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Accept: */*
GET /30150001 HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Accept: */*
</protocol>
<stdout>
4
</stdout>
</verify>
</testcase>

View File

@ -61,7 +61,7 @@ Accept: */*
</protocol> </protocol>
<stdout nonewline="yes"> <stdout nonewline="yes">
{"url_effective":"http://%HOSTIP:%HTTPPORT/970","method":"GET","http_code":200,"response_code":200,"http_connect":0,"time_total":0.000013,"time_namelookup":0.000013,"time_connect":0.000013,"time_appconnect":0.000013,"time_pretransfer":0.000013,"time_starttransfer":0.000013,"size_header":4019,"size_request":4019,"size_download":445,"size_upload":0,"speed_download":13,"speed_upload":13,"content_type":"text/html","num_connects":1,"time_redirect":0.000013,"num_redirects":0,"ssl_verify_result":0,"proxy_ssl_verify_result":0,"filename_effective":"log/out970","remote_ip":"%HOSTIP","remote_port":%HTTPPORT,"local_ip":"127.0.0.1","local_port":13,"http_version":"1.1","scheme":"HTTP","curl_version":"curl-unit-test-fake-version"} {"url_effective":"http://%HOSTIP:%HTTPPORT/970","method":"GET","http_code":200,"response_code":200,"num_headers":9,"http_connect":0,"time_total":0.000013,"time_namelookup":0.000013,"time_connect":0.000013,"time_appconnect":0.000013,"time_pretransfer":0.000013,"time_starttransfer":0.000013,"size_header":4019,"size_request":4019,"size_download":445,"size_upload":0,"speed_download":13,"speed_upload":13,"content_type":"text/html","num_connects":1,"time_redirect":0.000013,"num_redirects":0,"ssl_verify_result":0,"proxy_ssl_verify_result":0,"filename_effective":"log/out970","remote_ip":"%HOSTIP","remote_port":%HTTPPORT,"local_ip":"127.0.0.1","local_port":13,"http_version":"1.1","scheme":"HTTP","curl_version":"curl-unit-test-fake-version"}
</stdout> </stdout>
</verify> </verify>
</testcase> </testcase>