Add proper server support for http/1.1 Expect header

This commit is contained in:
Jeremy Lakeman 2017-05-17 12:07:50 +09:30
parent a473304c06
commit e376c1cb80
9 changed files with 79 additions and 68 deletions

View File

@ -34,6 +34,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "strbuf_helpers.h"
#include "net.h"
#include "mem.h"
#include "version_servald.h"
#define BOUNDARY_STRING_MAXLEN 70 // legislated limit from RFC-1341
@ -108,6 +109,7 @@ void http_request_init(struct http_request *r, int sockfd)
r->request_content_remaining = CONTENT_LENGTH_UNKNOWN;
r->response.header.content_length = CONTENT_LENGTH_UNKNOWN;
r->response.header.resource_length = CONTENT_LENGTH_UNKNOWN;
r->response.header.minor_version = 1;
r->alarm.stats = &http_server_stats;
r->alarm.function = http_server_poll;
assert(r->idle_timeout >= 0);
@ -1089,6 +1091,23 @@ static int http_request_parse_header(struct http_request *r)
goto malformed;
}
_rewind(r);
if (_skip_literal_nocase(r, "Expect:")) {
if (r->request_header.expect){
IDEBUGF(r->debug, "Skipping duplicate HTTP header Expect: %s", alloca_toprint(50, sol, r->end - sol));
r->cursor = nextline;
_commit(r);
return 0;
}
_skip_optional_space(r);
uint32_t code;
_parse_uint32(r, &code);
if (code==100)
r->request_header.expect=1;
r->cursor = nextline;
_commit(r);
return 0;
}
_rewind(r);
if (_skip_literal_nocase(r, "Authorization:")) {
if (r->request_header.authorization.scheme != NOAUTH) {
IDEBUGF(r->debug, "Skipping duplicate HTTP header Authorization: %s", alloca_toprint(50, sol, r->end - sol));
@ -1141,6 +1160,37 @@ malformed:
return 400;
}
/* If the client is trying to post data and has supplied an "Expect: 100..." header
* And we haven't rejected the request yet
* Then we need to send a 100 continue response header before parsing the request body
*/
static int http_request_start_continue(struct http_request *r){
const char *msg = "HTTP/1.0 100 Continue\r\n\r\n";
static size_t msg_len = 0;
if (msg_len == 0)
msg_len = strlen(msg);
if (r->response_sent < msg_len){
ssize_t written = write_nonblock(r->alarm.poll.fd, msg + r->response_sent, msg_len - r->response_sent);
if (written == -1) {
IDEBUG(r->debug, "HTTP socket write error, closing connection");
http_request_finalise(r);
return -1;
}
IDEBUGF(r->debug, "Wrote %zu bytes of 100 Continue response to HTTP socket", (size_t) written);
r->response_sent += written;
if (r->response_sent < msg_len)
return 0;
}
r->response_sent=0;
r->parser = http_request_parse_body_form_data;
r->form_data_state = START;
if (_run_out(r))
return 100;
return 0;
}
/* If parsing completes, then sets r->parser to the next parsing function and returns 0. If parsing
* cannot complete due to running out of data, returns 0 without changing r->parser, so this
* function will be called again once more data has been read. Returns a 4nn or 5nn HTTP result
@ -1187,8 +1237,13 @@ static int http_request_start_body(struct http_request *r)
r->verb, r->request_header.content_type.type, r->request_header.content_type.subtype);
return 400;
}
r->parser = http_request_parse_body_form_data;
r->form_data_state = START;
if (r->request_header.expect && _run_out(r)){
r->parser = http_request_start_continue;
return 0;
}else{
r->parser = http_request_parse_body_form_data;
r->form_data_state = START;
}
} else {
IDEBUGF(r->debug, "Unsupported HTTP %s request: Content-Type %s not supported",
r->verb, alloca_mime_content_type(&r->request_header.content_type));
@ -2114,7 +2169,9 @@ static int _render_response(struct http_request *r)
}
assert(hr.header.content_type != NULL);
assert(hr.header.content_type[0]);
strbuf_sprintf(sb, "HTTP/1.0 %03u %s\r\n", hr.status_code, hr.reason);
strbuf_sprintf(sb, "HTTP/1.%d %03u %s\r\n", hr.header.minor_version, hr.status_code, hr.reason);
strbuf_puts(sb, "Connection: Close\r\n");
strbuf_sprintf(sb, "Server: servald %s\r\n", version_servald);
strbuf_sprintf(sb, "Content-Type: %s", hr.header.content_type);
if (hr.header.boundary) {
strbuf_puts(sb, "; boundary=");
@ -2247,12 +2304,14 @@ static void http_request_start_response(struct http_request *r)
http_request_finalise(r);
RETURNVOID;
}
// Drain the rest of the request that has not been received yet (eg, if sending an error response
// provoked while parsing the early part of a partially-received request). If a read error
// occurs, the connection is closed so the phase changes to DONE.
http_request_drain(r);
if (r->phase != RECEIVE)
RETURNVOID;
if (!r->request_header.expect){
// Drain the rest of the request that has not been received yet (eg, if sending an error response
// provoked while parsing the early part of a partially-received request). If a read error
// occurs, the connection is closed so the phase changes to DONE.
http_request_drain(r);
if (r->phase != RECEIVE)
RETURNVOID;
}
// Ensure conformance to HTTP standards.
if (r->response.status_code == 401 && r->response.header.www_authenticate.scheme == NOAUTH) {
WHY("HTTP 401 response missing WWW-Authenticate header, sending 500 Server Error instead");

View File

@ -102,9 +102,11 @@ struct http_request_headers {
struct http_origin origin;
struct http_range content_ranges[5];
struct http_client_authorization authorization;
bool_t expect;
};
struct http_response_headers {
uint8_t minor_version;
http_size_t content_length;
http_size_t content_range_start; // range_end = range_start + content_length - 1
http_size_t resource_length; // size of entire resource

View File

@ -377,6 +377,8 @@ static int rhizome_direct_enquiry(httpd_request *r, const char *remainder)
return 404;
if (r->http.verb != HTTP_VERB_POST)
return 405;
// backwards compatibility, rhizome_fetch used to allow HTTP/1.0 responses only
r->http.response.header.minor_version=0;
r->http.form_data.handle_mime_part_start = rhizome_direct_process_mime_start;
r->http.form_data.handle_mime_part_end = rhizome_direct_process_mime_end;
r->http.form_data.handle_mime_part_header = rhizome_direct_process_mime_part_header;

View File

@ -1544,8 +1544,9 @@ int unpack_http_response(char *response, struct http_response_parts *parts)
parts->content_length = HTTP_RESPONSE_CONTENT_LENGTH_UNSET;
parts->content_start = NULL;
char *p = NULL;
if (!str_startswith(response, "HTTP/1.0 ", (const char **)&p)) {
DEBUGF(rhizome_rx, "Malformed HTTP reply: missing HTTP/1.0 preamble");
if (!str_startswith(response, "HTTP/1.0 ", (const char **)&p)
&& !str_startswith(response, "HTTP/1.1 ", (const char **)&p)) {
DEBUGF(rhizome_rx, "Malformed HTTP reply: missing HTTP version preamble");
RETURN(-1);
}
if (!(isdigit(p[0]) && isdigit(p[1]) && isdigit(p[2]) && p[3] == ' ')) {

View File

@ -48,6 +48,8 @@ static int rhizome_file_page(httpd_request *r, const char *remainder)
int ret = rhizome_response_content_init_filehash(r, &filehash);
if (ret)
return ret;
// backwards compatibility, rhizome_fetch used to allow HTTP/1.0 responses only
r->http.response.header.minor_version=0;
http_request_response_generated(&r->http, 200, CONTENT_TYPE_BLOB, rhizome_payload_content);
return 1;
}
@ -67,6 +69,8 @@ static int manifest_by_prefix_page(httpd_request *r, const char *remainder)
return 500;
switch(rhizome_retrieve_manifest_by_prefix(prefix.binary, prefix_len, r->manifest)){
case RHIZOME_BUNDLE_STATUS_SAME:
// backwards compatibility, rhizome_fetch used to allow HTTP/1.0 responses only
r->http.response.header.minor_version=0;
http_request_response_static(&r->http, 200, CONTENT_TYPE_BLOB, (const char *)r->manifest->manifestdata, r->manifest->manifest_all_bytes);
return 1;
case RHIZOME_BUNDLE_STATUS_NEW:

View File

@ -52,7 +52,6 @@ teardown() {
doc_MeshMBRestSend="Restful send of a broadcast message"
test_MeshMBRestSend() {
executeOk curl \
-H "Expect:" \
--silent --fail --show-error \
--output sendmessage.json \
--basic --user harry:potter \
@ -71,7 +70,6 @@ setup_MeshMBRestList() {
}
test_MeshMBRestList() {
executeOk curl \
-H "Expect:" \
--silent --fail --show-error \
--output listmessages.json \
--dump-header http.headers \
@ -106,7 +104,6 @@ setup_MeshMBRestFollow() {
}
test_MeshMBRestFollow() {
executeOk curl \
-H "Expect:" \
--silent --fail --show-error \
--output follow.json \
--basic --user harry:potter \
@ -116,7 +113,6 @@ test_MeshMBRestFollow() {
assertStdoutGrep --matches=1 ":$IDA2:$SIDA2::[0-9]\+:Message 2\$"
assertStdoutGrep --matches=0 ":$IDA3:$SIDA3::[0-9]\+:Message 3\$"
executeOk curl \
-H "Expect:" \
--silent --fail --show-error \
--output follow.json \
--basic --user harry:potter \
@ -126,7 +122,6 @@ test_MeshMBRestFollow() {
assertStdoutGrep --matches=1 ":$IDA2:$SIDA2::[0-9]\+:Message 2\$"
assertStdoutGrep --matches=1 ":$IDA3:$SIDA3::[0-9]\+:Message 3\$"
executeOk curl \
-H "Expect:" \
--silent --fail --show-error \
--output follow.json \
--basic --user harry:potter \
@ -149,7 +144,6 @@ setup_MeshMBRestFeeds() {
}
test_MeshMBRestFeeds() {
executeOk curl \
-H "Expect:" \
--silent --fail --show-error \
--output feedlist.json \
--dump-header http.headers \
@ -181,7 +175,6 @@ setup_MeshMBRestEmptyActivity() {
}
test_MeshMBRestEmptyActivity() {
executeOk --timeout=10 curl \
-H "Expect:" \
--silent --fail --show-error \
--output activity.json \
--dump-header http.headers \
@ -226,7 +219,6 @@ setup_MeshMBRestActivity() {
}
test_MeshMBRestActivity() {
executeOk curl \
-H "Expect:" \
--silent --fail --show-error \
--output activity.json \
--dump-header http.headers \
@ -262,7 +254,6 @@ setup_MeshMBRestNewActivity() {
executeOk_servald meshmb follow $IDA1 $IDA2
executeOk_servald meshmb follow $IDA1 $IDA3
executeOk curl \
-H "Expect:" \
--silent --fail --show-error \
--output activity.json \
--dump-header http.headers \

View File

@ -418,7 +418,6 @@ setup_MeshmsSend() {
}
test_MeshmsSend() {
executeOk curl \
-H "Expect:" \
--silent --fail --show-error \
--output sendmessage.json \
--basic --user harry:potter \
@ -427,7 +426,6 @@ test_MeshmsSend() {
executeOk_servald meshms list messages $SIDA1 $SIDA2
assertStdoutGrep --matches=1 ':>:Hello World'
executeOk curl \
-H "Expect:" \
--silent --fail --show-error \
--output sendmessage.json \
--basic --user ron:weasley \
@ -445,7 +443,6 @@ setup_MeshmsSendMissingMessage() {
}
test_MeshmsSendMissingMessage() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -468,7 +465,6 @@ setup_MeshmsSendDuplicateMessage() {
}
test_MeshmsSendDuplicateMessage() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -492,7 +488,6 @@ setup_MeshmsSendMessageMissingContentType() {
}
test_MeshmsSendMessageMissingContentType() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -515,7 +510,6 @@ setup_MeshmsSendMessageUnsupportedContentType() {
}
test_MeshmsSendMessageUnsupportedContentType() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -538,7 +532,6 @@ setup_MeshmsSendMessageMissingCharset() {
}
test_MeshmsSendMessageMissingCharset() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -561,7 +554,6 @@ setup_MeshmsSendMessageUnsupportedCharset() {
}
test_MeshmsSendMessageUnsupportedCharset() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -584,7 +576,6 @@ setup_MeshmsSendNoIdentity() {
}
test_MeshmsSendNoIdentity() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -616,7 +607,6 @@ setup_MeshmsReadAllConversations() {
}
test_MeshmsReadAllConversations() {
executeOk curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http_body \
--basic --user harry:potter \
@ -645,7 +635,6 @@ setup_MeshmsPostSpuriousContent() {
}
test_MeshmsPostSpuriousContent() {
executeOk curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http_body \
--basic --user harry:potter \
@ -679,7 +668,6 @@ setup_MeshmsReadAllMessages() {
}
test_MeshmsReadAllMessages() {
executeOk curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http_body \
--basic --user harry:potter \
@ -708,7 +696,6 @@ setup_MeshmsReadMessage() {
}
test_MeshmsReadMessage() {
executeOk curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http_body \
--basic --user harry:potter \
@ -720,7 +707,6 @@ test_MeshmsReadMessage() {
executeOk_servald meshms list conversations $SIDA2
assertStdoutGrep --stderr --matches=1 ":$SIDA1:unread:45:22\$"
executeOk curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output read.json \
--basic --user harry:potter \
@ -759,7 +745,6 @@ test_sendFlood() {
do
tfw_log "Sending message $i $j"
curl \
-H "Expect:" \
--silent --fail --show-error \
--output /dev/null \
--basic --user harry:potter \

View File

@ -609,7 +609,6 @@ EOF
}
test_HttpImport() {
executeOk curl \
-H "Expect:" \
--silent --fail --show-error \
--output http.output \
--dump-header http.headers \
@ -638,7 +637,6 @@ setup_HttpAddLocal() {
test_HttpAddLocal() {
echo 'File file1' >file1
executeOk curl \
-H "Expect:" \
--silent \
--form 'data=@file1' "http://${addr_localhost}:$PORTA/rhizome/secretaddfile" \
--output file1.manifest

View File

@ -567,7 +567,6 @@ test_RhizomeInsert() {
authorargs=()
[ -n "${author[$n]}" ] && authorargs=(--form "bundle-author=${author[$n]}")
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output file$n.manifest \
--dump-header http.header$n \
@ -633,7 +632,6 @@ test_RhizomeInsert() {
for n in 1 2 3 4; do
$SED -e '/^version=/d;/^date=/d;/^filehash=/d;/^filesize=/d' xfile$n.manifest >nmanifest$n
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output nfile$n.manifest \
--dump-header http.headers$n \
@ -667,7 +665,6 @@ test_RhizomeInsertBoundaries() {
create_file file$n $((7200 + ($n * 50)))
>manifest$n
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output file$n.manifest \
--dump-header http.header$n \
@ -694,7 +691,6 @@ setup_RhizomeInsertAnon() {
}
test_RhizomeInsertAnon() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output ifile2.manifest \
--dump-header http.header \
@ -724,7 +720,6 @@ setup_RhizomeInsertAnonPassphrase() {
test_RhizomeInsertAnonPassphrase() {
# Create the bundle with file1
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output ifile1.manifest \
--dump-header http.header \
@ -749,7 +744,6 @@ test_RhizomeInsertAnonPassphrase() {
assert_rhizome_list --fromhere=0 --manifest=ifile1.manifest file1
# Update the bundle to file2
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output ifile2.manifest \
--dump-header http.header \
@ -784,7 +778,6 @@ setup_RhizomeInsertPassphrase() {
test_RhizomeInsertPassphrase() {
# Create the bundle with file1
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output ifile1.manifest \
--dump-header http.header \
@ -810,7 +803,6 @@ test_RhizomeInsertPassphrase() {
assert_rhizome_list --fromhere=1 --manifest=ifile1.manifest file1
# Update the bundle to file2
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output ifile2.manifest \
--dump-header http.header \
@ -846,7 +838,6 @@ setup_RhizomeInsertEmpty() {
}
test_RhizomeInsertEmpty() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output empty.manifest \
--dump-header http.header \
@ -882,7 +873,6 @@ setup_RhizomeUpdateToEmpty() {
}
test_RhizomeUpdateToEmpty() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output empty.manifest \
--dump-header http.header \
@ -912,7 +902,6 @@ setup_RhizomeInsertLarge() {
}
test_RhizomeInsertLarge() {
execute --timeout=120 curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output file1.manifest \
--dump-header http.header \
@ -942,7 +931,6 @@ setup_RhizomeInsertMissingManifest() {
}
test_RhizomeInsertMissingManifest() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -966,7 +954,6 @@ setup_RhizomeInsertManifestOverflow() {
}
test_RhizomeInsertManifestOverflow() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -990,7 +977,6 @@ setup_RhizomeInsertIncorrectManifestType() {
}
test_RhizomeInsertIncorrectManifestType() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -1014,7 +1000,6 @@ setup_RhizomeInsertIncorrectManifestFormat() {
}
test_RhizomeInsertIncorrectManifestFormat() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -1040,7 +1025,6 @@ setup_RhizomeInsertDuplicateManifest() {
}
test_RhizomeInsertDuplicateManifest() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -1066,7 +1050,6 @@ setup_RhizomeInsertJournalForbidden() {
}
test_RhizomeInsertJournalForbidden() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -1092,7 +1075,6 @@ setup_RhizomeInsertMissingPayload() {
}
test_RhizomeInsertMissingPayload() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -1117,7 +1099,6 @@ setup_RhizomeInsertDuplicatePayload() {
}
test_RhizomeInsertDuplicatePayload() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -1143,7 +1124,6 @@ setup_RhizomeInsertPartOrder() {
}
test_RhizomeInsertPartOrder() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -1168,7 +1148,6 @@ setup_RhizomeInsertPartUnsupported() {
}
test_RhizomeInsertPartUnsupported() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -1197,7 +1176,6 @@ setup_RhizomeInsertIncorrectFilesize() {
}
test_RhizomeInsertIncorrectFilesize() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -1213,7 +1191,6 @@ test_RhizomeInsertIncorrectFilesize() {
assertJq http.body 'contains({"rhizome_payload_status_code": 3})'
assertJqGrep --ignore-case http.body '.rhizome_payload_status_message' 'payload size.*contradicts manifest'
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -1240,7 +1217,6 @@ setup_RhizomeInsertIncorrectFilehash() {
}
test_RhizomeInsertIncorrectFilehash() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -1270,7 +1246,6 @@ setup_RhizomeImport() {
}
test_RhizomeImport() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -1283,7 +1258,6 @@ test_RhizomeImport() {
assertJq http.body 'contains({"http_status_code": 201})'
assertJq http.body 'contains({"http_status_message": "Created"})'
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
@ -1312,7 +1286,6 @@ setup_RhizomeJournalAppend() {
}
test_RhizomeJournalAppend() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output file1.manifest \
--dump-header http.header \
@ -1369,7 +1342,6 @@ test_RhizomeJournalAppend() {
executeOk_servald rhizome extract file "$BID" file1x
assert --message="extracted payload is correct" diff file1 file1x
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output file2.manifest \
--dump-header http.headers \
@ -1409,7 +1381,6 @@ setup_RhizomeJournalAppendSharedPayload() {
}
test_RhizomeJournalAppendSharedPayload() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output file1.manifest \
--dump-header http.header \
@ -1430,7 +1401,6 @@ test_RhizomeJournalAppendSharedPayload() {
assert [ $(ls "$SERVALINSTANCE_PATH/blob" | wc -l) -eq 2 ]
extract_http_header BID http.header Serval-Rhizome-Bundle-Id "$rexp_manifestid"
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output file2.manifest \
--dump-header http.header \
@ -1463,7 +1433,6 @@ setup_RhizomeAppendNonJournalForbidden() {
}
test_RhizomeAppendNonJournalForbidden() {
execute curl \
-H "Expect:" \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \