Expand description
HTTP/1.1 client protocol
Sans-IO protocol impl, which means “writing” and “reading” are made via buffers rather than the Write/Read std traits.
The Call object attempts to encode correct HTTP/1.1 handling using
state variables, for example Call<'a, SendRequest> to represent the
lifecycle stage where we are to send the request.
The states are:
- Prepare - Preparing a request means 1) adding headers such as cookies. 2) acquiring the connection from a pool or opening a new socket (potentially wrappping in TLS)
- SendRequest - Send the first row, which is the method, path and version as well as the request headers
- SendBody - Send the request body
- Await100 - If there is an
Expect: 100-continueheader, the client should pause before sending the body - RecvResponse - Receive the response, meaning the status and version and the response headers
- RecvBody - Receive the response body
- Redirect - Handle redirects, potentially spawning new requests
- Cleanup - Return the connection to the pool or close it
┌──────────────────┐
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ▶│ Prepare │
└──────────────────┘
│ │
▼
│ ┌──────────────────┐
┌──│ SendRequest │──────────────┐
│ │ └──────────────────┘ │
│ │ │
│ │ ▼ ▼
│ ┌──────────────────┐ ┌──────────────────┐
│ │ │ SendBody │◀───│ Await100 │
│ └──────────────────┘ └──────────────────┘
│ │ │ │
│ ▼ │
│ └─▶┌──────────────────┐◀─────────────┘
┌─────────────│ RecvResponse │──┐
│ │ └──────────────────┘ │
│ │ │
│ ▼ ▼ │
┌──────────────────┐ ┌──────────────────┐ │
└ ─│ Redirect │◀───│ RecvBody │ │
└──────────────────┘ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
└────────────▶│ Cleanup │◀─┘
└──────────────────┘§Example
use ureq_proto::client::*;
use ureq_proto::http::Request;
let request = Request::put("https://example.test/my-path")
.header("Expect", "100-continue")
.header("x-foo", "bar")
.body(())
.unwrap();
// ********************************** Prepare
let mut call = Call::new(request).unwrap();
// Prepare with state from cookie jar. The uri
// is used to key the cookies.
let uri = call.uri();
// call.header("Cookie", "my_cookie1=value1");
// call.header("Cookie", "my_cookie2=value2");
// Obtain a connection for the uri, either a
// pooled connection from a previous http/1.1
// keep-alive, or open a new. The connection
// must be TLS wrapped if the scheme so indicate.
// let connection = todo!();
// Sans-IO means it does not use any
// Write trait or similar. Requests and request
// bodies are written to a buffer that in turn
// should be sent via the connection.
let mut output = vec![0_u8; 1024];
// ********************************** SendRequest
// Proceed to the next state writing the request.
let mut call = call.proceed();
let output_used = call.write(&mut output).unwrap();
assert_eq!(output_used, 107);
assert_eq!(&output[..output_used], b"\
PUT /my-path HTTP/1.1\r\n\
host: example.test\r\n\
transfer-encoding: chunked\r\n\
expect: 100-continue\r\n\
x-foo: bar\r\n\
\r\n");
// Check we can continue to send the body
assert!(call.can_proceed());
// ********************************** Await100
// In this example, we know the next state is Await100.
// A real client needs to match on the variants.
let mut call = match call.proceed() {
Ok(Some(SendRequestResult::Await100(v))) => v,
_ => panic!(),
};
// When awaiting 100, the client should run a timer and
// proceed to sending the body either when the server
// indicates it can receive the body, or the timer runs out.
// This boolean can be checked whether there's any point
// in keeping waiting for the timer to run out.
assert!(call.can_keep_await_100());
let input = b"HTTP/1.1 100 Continue\r\n\r\n";
let input_used = call.try_read_100(input).unwrap();
assert_eq!(input_used, 25);
assert!(!call.can_keep_await_100());
// ********************************** SendBody
// Proceeding is possible regardless of whether the
// can_keep_await_100() is true or false.
// A real client needs to match on the variants.
let mut call = match call.proceed() {
Ok(Await100Result::SendBody(v)) => v,
_ => panic!(),
};
let (input_used, o1) =
call.write(b"hello", &mut output).unwrap();
assert_eq!(input_used, 5);
// When doing transfer-encoding: chunked,
// the end of body must be signaled with
// an empty input. This is also valid for
// regular content-length body.
assert!(!call.can_proceed());
let (_, o2) = call.write(&[], &mut output[o1..]).unwrap();
let output_used = o1 + o2;
assert_eq!(output_used, 15);
assert_eq!(&output[..output_used], b"\
5\r\n\
hello\
\r\n\
0\r\n\
\r\n");
assert!(call.can_proceed());
// ********************************** RecvRequest
// Proceed to read the request.
let mut call = call.proceed().unwrap();
let part = b"HTTP/1.1 200 OK\r\nContent-Len";
let full = b"HTTP/1.1 200 OK\r\nContent-Length: 9\r\n\r\n";
// try_response can be used repeatedly until we
// get enough content including all headers.
let (input_used, maybe_response) =
call.try_response(part, false).unwrap();
assert_eq!(input_used, 0);
assert!(maybe_response.is_none());
let (input_used, maybe_response) =
call.try_response(full, false).unwrap();
assert_eq!(input_used, 38);
let response = maybe_response.unwrap();
// ********************************** RecvBody
// It's not possible to proceed until we
// have read a response.
let mut call = match call.proceed() {
Some(RecvResponseResult::RecvBody(v)) => v,
_ => panic!(),
};
let(input_used, output_used) =
call.read(b"hi there!", &mut output).unwrap();
assert_eq!(input_used, 9);
assert_eq!(output_used, 9);
assert_eq!(&output[..output_used], b"hi there!");
// ********************************** Cleanup
let call = match call.proceed() {
Some(RecvBodyResult::Cleanup(v)) => v,
_ => panic!(),
};
if call.must_close_connection() {
// connection.close();
} else {
// connection.return_to_pool();
}
Modules§
- state
- State types for the Call state machine.
Structs§
- Call
- A state machine for an HTTP request/response cycle.
Enums§
- Await100
Result - Possible state transitions after awaiting a 100 Continue response.
- Recv
Body Result - Possible state transitions after receiving a response body.
- Recv
Response Result - Possible state transitions after receiving a response.
- Redirect
Auth Headers - Strategy for preserving authorization headers during redirects.
- Send
Request Result - Possible state transitions after sending a request.
Constants§
- MAX_
RESPONSE_ HEADERS - Max number of headers to parse from an HTTP response