1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
use std::time::Duration; use rotor::{Scope, Time}; use recvmode::RecvMode; use super::{Head, Request, ResponseError, ProtocolError}; use super::{Connection}; pub enum Task<M: Client> { Sleep(M, Time), Request(M, M::Requester), Close, } /// A state machine that allows to initiate a client-side HTTP request /// /// Used for all versions of HTTP. pub trait Client: Sized { type Requester: Requester; type Seed: Clone + Sized; fn create(seed: Self::Seed, scope: &mut Scope<<Self::Requester as Requester>::Context>) -> Self; /// The handler is invoked when connection is succeed or when previous /// request has just finished /// /// To initiate a request, return `Requester` as part of a return value. fn connection_idle(self, connection: &Connection, scope: &mut Scope<<Self::Requester as Requester>::Context>) -> Task<Self>; /// Error when establishing connection or connection closed when in idle fn connection_error(self, reason: &ProtocolError, scope: &mut Scope<<Self::Requester as Requester>::Context>); /// Standard rotor's wakeup handler /// /// If `connection.is_idle()` you may initiate a new request /// /// Note: currently we call this action only when there is no request /// beign active (otherwise wakeup goes to request state machine), but /// we may change it in future to allow request pipelining fn wakeup(self, connection: &Connection, scope: &mut Scope<<Self::Requester as Requester>::Context>) -> Task<Self>; /// Standard rotor's timeout handler /// /// If `connection.is_idle()` you may initiate a new request /// /// Note: currently we call this action only when there is no request /// beign active (otherwise timeout goes to request state machine), but /// we may change it in future to allow request pipelining fn timeout(self, connection: &Connection, scope: &mut Scope<<Self::Requester as Requester>::Context>) -> Task<Self>; /// Returns number of seconds to wait for connection to be established /// /// This timeout is not obeyed for `Persistent` connections fn connect_timeout(&self, _scope: &mut Scope<<Self::Requester as Requester>::Context>) -> Duration { Duration::new(15, 0) } /// Idle timeout /// /// Timeout for keep-alive connection to be idle fn idle_timeout(&self, _scope: &mut Scope<<Self::Requester as Requester>::Context>) -> Duration { Duration::new(120, 0) } } /// A handler of a single client-side HTTP /// /// Used for all versions of HTTP. /// /// Note: the interface allows you to receive the response before the whole /// request is sent to the server. This is only occasionally useful (for /// example if your server is some kind of encoding service that processes /// data chunk by chunk). But if response header for some reason is received /// before request is sent, the connection will be closed, as it probably /// means that either server is misbehaving or we encounter some /// out of sync behavior (which is enforced by rust type system so should /// never happen, unless there is a bug in rotor-http), otherwise we may /// have a cache poisoning security issue. /// /// Another property of this state machine is that when any event handler /// before `request_end()` returns None, the connection is closed (because we /// don't know whether response is valid and if it's too long to wait it's /// response). So in case you need to discard the response and it's more cheap /// than reopening a connection, you must read and ignore it yourself. pub trait Requester: Sized { type Context; /// Populates a request /// /// This receives a `Request` object, which is a "builder". When you /// add things to it they are written directly to the buffer. This way /// you don't have to allocate temporary memory for map of headers in /// case you construct request programmatically. /// /// While you can continue sending request *body* when response headers /// are received. You must either send request *headers* in this handler /// or arrange this state machine to be waken up, because no actions will /// be invoked later unless response headers are sent. fn prepare_request(self, req: &mut Request, scope: &mut Scope<Self::Context>) -> Option<Self>; /// Encountered when headers received /// /// Returns self, mode and timeout for reading whole response. /// /// This handler decides whether response is fully buffered or whether /// we need to read response body by chunk. It's recommended to return /// Buffered up to certain size, or at least for zero-length response. /// /// Note that `head` is passed here once, and forgotten by the /// protocol. If you need it later it's your responsibility to store it /// somewhere. fn headers_received(self, head: Head, request: &mut Request, scope: &mut Scope<Self::Context>) -> Option<(Self, RecvMode, Time)>; /// Called when full response is received in buffered mode /// /// Note: you can't continue with connection here. But you can finish /// the request (although, it's probably doesn't make too much sense) fn response_received(self, data: &[u8], request: &mut Request, scope: &mut Scope<Self::Context>); /// Called when response become invalid between `prepare_request()` /// and `response_received/response_end` /// /// This is useful mostly to notify the requestor that it will not have /// anything. Note this event doesnt' relate to any HTTP status codes. /// They are treated as normal responses by the state machine. /// /// It's never called on a timeout. fn bad_response(self, _error: &ResponseError, _scope: &mut Scope<Self::Context>) {} /// Received chunk of data /// /// Whey you return `Progressive(nbytes)` from headers received, you /// may expect than chunk will be at least of `nbytes` of length. But /// you must not rely on that for few reasons: /// /// 1. Last chunk of response body may be smaller /// 2. Chunk is read up to some buffer size, which is heuristically /// determined, and is usually larger than `nbytes` /// 3. Currently for chunked encoding we don't merge chunks, so last /// part of each chunk may be shorter as `nbytes` fn response_chunk(self, chunk: &[u8], request: &mut Request, scope: &mut Scope<Self::Context>) -> Option<Self>; /// End of response body, only for Progressive responses /// /// Note: you can't continue with connection here. But you can finish /// the request (although, it's probably doesn't make too much sense) fn response_end(self, request: &mut Request, scope: &mut Scope<Self::Context>); /// Request timeout occured /// /// Unless you've returned the new timeout connection will be closed after /// the event. fn timeout(self, request: &mut Request, scope: &mut Scope<Self::Context>) -> Option<(Self, Time)>; fn wakeup(self, request: &mut Request, scope: &mut Scope<Self::Context>) -> Option<Self>; /// Returns number of seconds between any read/write operation to wait /// until connection is closed as stalled /// /// This doesn't influence idle connections (where no active request is /// going on). Use `Client::idle_connection` to set the latter. fn byte_timeout(&self, _scope: &mut Scope<Self::Context>) -> Duration { Duration::new(120, 0) } }