_state.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. ################################################################
  2. # The core state machine
  3. ################################################################
  4. #
  5. # Rule 1: everything that affects the state machine and state transitions must
  6. # live here in this file. As much as possible goes into the table-based
  7. # representation, but for the bits that don't quite fit, the actual code and
  8. # state must nonetheless live here.
  9. #
  10. # Rule 2: this file does not know about what role we're playing; it only knows
  11. # about HTTP request/response cycles in the abstract. This ensures that we
  12. # don't cheat and apply different rules to local and remote parties.
  13. #
  14. #
  15. # Theory of operation
  16. # ===================
  17. #
  18. # Possibly the simplest way to think about this is that we actually have 5
  19. # different state machines here. Yes, 5. These are:
  20. #
  21. # 1) The client state, with its complicated automaton (see the docs)
  22. # 2) The server state, with its complicated automaton (see the docs)
  23. # 3) The keep-alive state, with possible states {True, False}
  24. # 4) The SWITCH_CONNECT state, with possible states {False, True}
  25. # 5) The SWITCH_UPGRADE state, with possible states {False, True}
  26. #
  27. # For (3)-(5), the first state listed is the initial state.
  28. #
  29. # (1)-(3) are stored explicitly in member variables. The last
  30. # two are stored implicitly in the pending_switch_proposals set as:
  31. # (state of 4) == (_SWITCH_CONNECT in pending_switch_proposals)
  32. # (state of 5) == (_SWITCH_UPGRADE in pending_switch_proposals)
  33. #
  34. # And each of these machines has two different kinds of transitions:
  35. #
  36. # a) Event-triggered
  37. # b) State-triggered
  38. #
  39. # Event triggered is the obvious thing that you'd think it is: some event
  40. # happens, and if it's the right event at the right time then a transition
  41. # happens. But there are somewhat complicated rules for which machines can
  42. # "see" which events. (As a rule of thumb, if a machine "sees" an event, this
  43. # means two things: the event can affect the machine, and if the machine is
  44. # not in a state where it expects that event then it's an error.) These rules
  45. # are:
  46. #
  47. # 1) The client machine sees all h11.events objects emitted by the client.
  48. #
  49. # 2) The server machine sees all h11.events objects emitted by the server.
  50. #
  51. # It also sees the client's Request event.
  52. #
  53. # And sometimes, server events are annotated with a _SWITCH_* event. For
  54. # example, we can have a (Response, _SWITCH_CONNECT) event, which is
  55. # different from a regular Response event.
  56. #
  57. # 3) The keep-alive machine sees the process_keep_alive_disabled() event
  58. # (which is derived from Request/Response events), and this event
  59. # transitions it from True -> False, or from False -> False. There's no way
  60. # to transition back.
  61. #
  62. # 4&5) The _SWITCH_* machines transition from False->True when we get a
  63. # Request that proposes the relevant type of switch (via
  64. # process_client_switch_proposals), and they go from True->False when we
  65. # get a Response that has no _SWITCH_* annotation.
  66. #
  67. # So that's event-triggered transitions.
  68. #
  69. # State-triggered transitions are less standard. What they do here is couple
  70. # the machines together. The way this works is, when certain *joint*
  71. # configurations of states are achieved, then we automatically transition to a
  72. # new *joint* state. So, for example, if we're ever in a joint state with
  73. #
  74. # client: DONE
  75. # keep-alive: False
  76. #
  77. # then the client state immediately transitions to:
  78. #
  79. # client: MUST_CLOSE
  80. #
  81. # This is fundamentally different from an event-based transition, because it
  82. # doesn't matter how we arrived at the {client: DONE, keep-alive: False} state
  83. # -- maybe the client transitioned SEND_BODY -> DONE, or keep-alive
  84. # transitioned True -> False. Either way, once this precondition is satisfied,
  85. # this transition is immediately triggered.
  86. #
  87. # What if two conflicting state-based transitions get enabled at the same
  88. # time? In practice there's only one case where this arises (client DONE ->
  89. # MIGHT_SWITCH_PROTOCOL versus DONE -> MUST_CLOSE), and we resolve it by
  90. # explicitly prioritizing the DONE -> MIGHT_SWITCH_PROTOCOL transition.
  91. #
  92. # Implementation
  93. # --------------
  94. #
  95. # The event-triggered transitions for the server and client machines are all
  96. # stored explicitly in a table. Ditto for the state-triggered transitions that
  97. # involve just the server and client state.
  98. #
  99. # The transitions for the other machines, and the state-triggered transitions
  100. # that involve the other machines, are written out as explicit Python code.
  101. #
  102. # It'd be nice if there were some cleaner way to do all this. This isn't
  103. # *too* terrible, but I feel like it could probably be better.
  104. #
  105. # WARNING
  106. # -------
  107. #
  108. # The script that generates the state machine diagrams for the docs knows how
  109. # to read out the EVENT_TRIGGERED_TRANSITIONS and STATE_TRIGGERED_TRANSITIONS
  110. # tables. But it can't automatically read the transitions that are written
  111. # directly in Python code. So if you touch those, you need to also update the
  112. # script to keep it in sync!
  113. from typing import cast, Dict, Optional, Set, Tuple, Type, Union
  114. from ._events import *
  115. from ._util import LocalProtocolError, Sentinel
  116. # Everything in __all__ gets re-exported as part of the h11 public API.
  117. __all__ = [
  118. "CLIENT",
  119. "SERVER",
  120. "IDLE",
  121. "SEND_RESPONSE",
  122. "SEND_BODY",
  123. "DONE",
  124. "MUST_CLOSE",
  125. "CLOSED",
  126. "MIGHT_SWITCH_PROTOCOL",
  127. "SWITCHED_PROTOCOL",
  128. "ERROR",
  129. ]
  130. class CLIENT(Sentinel, metaclass=Sentinel):
  131. pass
  132. class SERVER(Sentinel, metaclass=Sentinel):
  133. pass
  134. # States
  135. class IDLE(Sentinel, metaclass=Sentinel):
  136. pass
  137. class SEND_RESPONSE(Sentinel, metaclass=Sentinel):
  138. pass
  139. class SEND_BODY(Sentinel, metaclass=Sentinel):
  140. pass
  141. class DONE(Sentinel, metaclass=Sentinel):
  142. pass
  143. class MUST_CLOSE(Sentinel, metaclass=Sentinel):
  144. pass
  145. class CLOSED(Sentinel, metaclass=Sentinel):
  146. pass
  147. class ERROR(Sentinel, metaclass=Sentinel):
  148. pass
  149. # Switch types
  150. class MIGHT_SWITCH_PROTOCOL(Sentinel, metaclass=Sentinel):
  151. pass
  152. class SWITCHED_PROTOCOL(Sentinel, metaclass=Sentinel):
  153. pass
  154. class _SWITCH_UPGRADE(Sentinel, metaclass=Sentinel):
  155. pass
  156. class _SWITCH_CONNECT(Sentinel, metaclass=Sentinel):
  157. pass
  158. EventTransitionType = Dict[
  159. Type[Sentinel],
  160. Dict[
  161. Type[Sentinel],
  162. Dict[Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]], Type[Sentinel]],
  163. ],
  164. ]
  165. EVENT_TRIGGERED_TRANSITIONS: EventTransitionType = {
  166. CLIENT: {
  167. IDLE: {Request: SEND_BODY, ConnectionClosed: CLOSED},
  168. SEND_BODY: {Data: SEND_BODY, EndOfMessage: DONE},
  169. DONE: {ConnectionClosed: CLOSED},
  170. MUST_CLOSE: {ConnectionClosed: CLOSED},
  171. CLOSED: {ConnectionClosed: CLOSED},
  172. MIGHT_SWITCH_PROTOCOL: {},
  173. SWITCHED_PROTOCOL: {},
  174. ERROR: {},
  175. },
  176. SERVER: {
  177. IDLE: {
  178. ConnectionClosed: CLOSED,
  179. Response: SEND_BODY,
  180. # Special case: server sees client Request events, in this form
  181. (Request, CLIENT): SEND_RESPONSE,
  182. },
  183. SEND_RESPONSE: {
  184. InformationalResponse: SEND_RESPONSE,
  185. Response: SEND_BODY,
  186. (InformationalResponse, _SWITCH_UPGRADE): SWITCHED_PROTOCOL,
  187. (Response, _SWITCH_CONNECT): SWITCHED_PROTOCOL,
  188. },
  189. SEND_BODY: {Data: SEND_BODY, EndOfMessage: DONE},
  190. DONE: {ConnectionClosed: CLOSED},
  191. MUST_CLOSE: {ConnectionClosed: CLOSED},
  192. CLOSED: {ConnectionClosed: CLOSED},
  193. SWITCHED_PROTOCOL: {},
  194. ERROR: {},
  195. },
  196. }
  197. StateTransitionType = Dict[
  198. Tuple[Type[Sentinel], Type[Sentinel]], Dict[Type[Sentinel], Type[Sentinel]]
  199. ]
  200. # NB: there are also some special-case state-triggered transitions hard-coded
  201. # into _fire_state_triggered_transitions below.
  202. STATE_TRIGGERED_TRANSITIONS: StateTransitionType = {
  203. # (Client state, Server state) -> new states
  204. # Protocol negotiation
  205. (MIGHT_SWITCH_PROTOCOL, SWITCHED_PROTOCOL): {CLIENT: SWITCHED_PROTOCOL},
  206. # Socket shutdown
  207. (CLOSED, DONE): {SERVER: MUST_CLOSE},
  208. (CLOSED, IDLE): {SERVER: MUST_CLOSE},
  209. (ERROR, DONE): {SERVER: MUST_CLOSE},
  210. (DONE, CLOSED): {CLIENT: MUST_CLOSE},
  211. (IDLE, CLOSED): {CLIENT: MUST_CLOSE},
  212. (DONE, ERROR): {CLIENT: MUST_CLOSE},
  213. }
  214. class ConnectionState:
  215. def __init__(self) -> None:
  216. # Extra bits of state that don't quite fit into the state model.
  217. # If this is False then it enables the automatic DONE -> MUST_CLOSE
  218. # transition. Don't set this directly; call .keep_alive_disabled()
  219. self.keep_alive = True
  220. # This is a subset of {UPGRADE, CONNECT}, containing the proposals
  221. # made by the client for switching protocols.
  222. self.pending_switch_proposals: Set[Type[Sentinel]] = set()
  223. self.states: Dict[Type[Sentinel], Type[Sentinel]] = {CLIENT: IDLE, SERVER: IDLE}
  224. def process_error(self, role: Type[Sentinel]) -> None:
  225. self.states[role] = ERROR
  226. self._fire_state_triggered_transitions()
  227. def process_keep_alive_disabled(self) -> None:
  228. self.keep_alive = False
  229. self._fire_state_triggered_transitions()
  230. def process_client_switch_proposal(self, switch_event: Type[Sentinel]) -> None:
  231. self.pending_switch_proposals.add(switch_event)
  232. self._fire_state_triggered_transitions()
  233. def process_event(
  234. self,
  235. role: Type[Sentinel],
  236. event_type: Type[Event],
  237. server_switch_event: Optional[Type[Sentinel]] = None,
  238. ) -> None:
  239. _event_type: Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]] = event_type
  240. if server_switch_event is not None:
  241. assert role is SERVER
  242. if server_switch_event not in self.pending_switch_proposals:
  243. raise LocalProtocolError(
  244. "Received server {} event without a pending proposal".format(
  245. server_switch_event
  246. )
  247. )
  248. _event_type = (event_type, server_switch_event)
  249. if server_switch_event is None and _event_type is Response:
  250. self.pending_switch_proposals = set()
  251. self._fire_event_triggered_transitions(role, _event_type)
  252. # Special case: the server state does get to see Request
  253. # events.
  254. if _event_type is Request:
  255. assert role is CLIENT
  256. self._fire_event_triggered_transitions(SERVER, (Request, CLIENT))
  257. self._fire_state_triggered_transitions()
  258. def _fire_event_triggered_transitions(
  259. self,
  260. role: Type[Sentinel],
  261. event_type: Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]],
  262. ) -> None:
  263. state = self.states[role]
  264. try:
  265. new_state = EVENT_TRIGGERED_TRANSITIONS[role][state][event_type]
  266. except KeyError:
  267. event_type = cast(Type[Event], event_type)
  268. raise LocalProtocolError(
  269. "can't handle event type {} when role={} and state={}".format(
  270. event_type.__name__, role, self.states[role]
  271. )
  272. ) from None
  273. self.states[role] = new_state
  274. def _fire_state_triggered_transitions(self) -> None:
  275. # We apply these rules repeatedly until converging on a fixed point
  276. while True:
  277. start_states = dict(self.states)
  278. # It could happen that both these special-case transitions are
  279. # enabled at the same time:
  280. #
  281. # DONE -> MIGHT_SWITCH_PROTOCOL
  282. # DONE -> MUST_CLOSE
  283. #
  284. # For example, this will always be true of a HTTP/1.0 client
  285. # requesting CONNECT. If this happens, the protocol switch takes
  286. # priority. From there the client will either go to
  287. # SWITCHED_PROTOCOL, in which case it's none of our business when
  288. # they close the connection, or else the server will deny the
  289. # request, in which case the client will go back to DONE and then
  290. # from there to MUST_CLOSE.
  291. if self.pending_switch_proposals:
  292. if self.states[CLIENT] is DONE:
  293. self.states[CLIENT] = MIGHT_SWITCH_PROTOCOL
  294. if not self.pending_switch_proposals:
  295. if self.states[CLIENT] is MIGHT_SWITCH_PROTOCOL:
  296. self.states[CLIENT] = DONE
  297. if not self.keep_alive:
  298. for role in (CLIENT, SERVER):
  299. if self.states[role] is DONE:
  300. self.states[role] = MUST_CLOSE
  301. # Tabular state-triggered transitions
  302. joint_state = (self.states[CLIENT], self.states[SERVER])
  303. changes = STATE_TRIGGERED_TRANSITIONS.get(joint_state, {})
  304. self.states.update(changes)
  305. if self.states == start_states:
  306. # Fixed point reached
  307. return
  308. def start_next_cycle(self) -> None:
  309. if self.states != {CLIENT: DONE, SERVER: DONE}:
  310. raise LocalProtocolError(
  311. "not in a reusable state. self.states={}".format(self.states)
  312. )
  313. # Can't reach DONE/DONE with any of these active, but still, let's be
  314. # sure.
  315. assert self.keep_alive
  316. assert not self.pending_switch_proposals
  317. self.states = {CLIENT: IDLE, SERVER: IDLE}