curl -w write-out in C#
-w / --write-out prints details about a finished transfer, such as the status code or how long it took. CurlDotNet supports curl’s complete write-out syntax in C#: the text is expanded after the transfer and appended to result.Body exactly where curl would print it, after the body and with no extra newline.
The curl command
using CurlDotNet;
var result = await Curl.ExecuteAsync(
"curl -s -o page.html -w '%{http_code} %{time_total}s %{size_download} bytes' https://httpbin.org/html");
Console.WriteLine(result.Body); // 200 0.412345s 3741 bytes
With -o the body goes to the file and Body holds only the -w text, which makes -o FILE -w FORMAT the easiest way to get just the numbers. Without -o, Body is the response body followed by the -w text, so start the format with \n if you want it on its own line.
Variables
Every variable curl documents for HTTP transfers is available, including %{num_retries} (how many --retry attempts were made; with --retry the -w text is written once, for the final attempt). The most used:
| Variable | Value |
|---|---|
%{http_code}, %{response_code} |
final HTTP status code |
%{http_version} |
1.1, 2 … |
%{url}, %{url_effective} |
the URL as given, and the last one fetched (after -L) |
%{redirect_url} |
the Location that was not followed (without -L) |
%{num_redirects}, %{num_connects}, %{num_headers} |
counts for the transfer |
%{content_type} |
the response Content-Type |
%{method}, %{scheme} |
the request method and URL scheme of the last request |
%{size_download}, %{size_upload}, %{size_header}, %{size_request} |
bytes |
%{speed_download}, %{speed_upload} |
bytes per second |
%{time_namelookup}, %{time_connect}, %{time_appconnect}, %{time_pretransfer}, %{time_starttransfer}, %{time_redirect}, %{time_total} |
seconds, six decimals like curl |
%{local_ip}, %{local_port}, %{remote_ip}, %{remote_port} |
connection end points |
%{exitcode}, %{errormsg} |
curl exit code and error text (0 and empty on success) |
%{filename_effective} |
the file -o / -O / -J wrote |
%{urlnum} |
index of the URL in a multi-URL command, from 0 |
%header{name} |
the value of one response header |
%{url.host}, %{url.path}, %{url.query} … |
parts of the URL; %{urle.*} gives the effective URL’s parts |
%% prints a percent sign, and \n, \r and \t are expanded. An unknown variable expands to nothing, as in curl.
Everything as JSON
%{json} prints all variables as one JSON object in curl’s variable order, and %{header_json} the response headers. As in curl, numeric variables (http_code, size_*, time_*, num_* …) are JSON numbers and a variable with no value is null, not a quoted string, so GetInt32() and GetDouble() work directly:
using System.Text.Json;
using CurlDotNet;
var result = await Curl.ExecuteAsync("curl -s -o get.json -w '%{json}' https://httpbin.org/get");
using var doc = JsonDocument.Parse(result.Body);
var root = doc.RootElement;
Console.WriteLine(root.GetProperty("http_code").GetInt32()); // 200
Console.WriteLine(root.GetProperty("time_total").GetDouble()); // seconds
Send output elsewhere
| Syntax | Effect |
|---|---|
%{stderr} |
the text after it goes to standard error (or the --stderr FILE), not to Body |
%{stdout} |
switches back to Body |
%output{file} |
the text after it is written to file; %output{>>file} appends |
%{onerror} |
the text after it is printed only when the transfer failed |
-w @format.txt |
reads the format from a file |
using CurlDotNet;
// Append one CSV line per request to a log file, keep Body clean
var result = await Curl.ExecuteAsync(
"curl -s -w '%output{>>timings.csv}%{url_effective},%{http_code},%{time_total}\\n' https://httpbin.org/get");
Console.WriteLine(File.ReadAllText("timings.csv"));
When the transfer fails
curl still expands -w when a transfer fails, which is where %{exitcode} and %{errormsg} are useful. CurlDotNet throws an exception instead of returning a result, so the text travels on the exception:
using CurlDotNet;
using CurlDotNet.Exceptions;
try
{
await Curl.ExecuteAsync("curl -f -s -w '%{http_code} exit=%{exitcode}' https://httpbin.org/status/503");
}
catch (CurlException ex)
{
Console.WriteLine(ex.Data[CurlException.WriteOutDataKey]); // 503 exit=22
Console.WriteLine(ex.StandardOutput); // everything curl would have printed, -w included
}
CurlException.StandardOutput is the complete standard output (-i headers, a --fail-with-body page, earlier URLs of the same command, and the -w text). ex.Data["CurlDotNet.WriteOut"] (the CurlException.WriteOutDataKey constant) holds only the -w part.
Several URLs
With several URLs, globs or --next groups, -w is expanded after each transfer and %{urlnum} tells them apart:
using CurlDotNet;
var result = await Curl.ExecuteAsync(
"curl -s -o 'out#1.txt' -w '%{urlnum} %{http_code}\\n' 'https://httpbin.org/status/{200,204}'");
Console.Write(result.Body);
// 0 200
// 1 204
Without -w
The same numbers are on the result as properties when you do not need curl’s text format: result.StatusCode, result.Headers and result.Timings (Total, StartTransfer, Connect and so on, in seconds).