[go: up one dir, main page]

Module client

Module client 

Source
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-continue header, 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§

Await100Result
Possible state transitions after awaiting a 100 Continue response.
RecvBodyResult
Possible state transitions after receiving a response body.
RecvResponseResult
Possible state transitions after receiving a response.
RedirectAuthHeaders
Strategy for preserving authorization headers during redirects.
SendRequestResult
Possible state transitions after sending a request.

Constants§

MAX_RESPONSE_HEADERS
Max number of headers to parse from an HTTP response